Несколько лет назад команда Apple опубликовала интересную статью Повышение производительности за счет сокращения динамической отправки. Эта статья довольно интересна для прочтения, в которой выделяются тонкие аспекты языка Swift и его компилятора.
В сегодняшней статье я хочу рассказать о производительности в Swift и как на нее влияет контроль доступа. Контроль доступа — это механизм, который иногда упускают из виду начинающие разработчики. Цель данной статьи — показать вам, насколько важно, обдумывать сам код, который вы пишете, и о том, как каждая строка кода впишется в большую картину.
Управление доступом в языке Swift несложно изучить. Уровни доступа и их определения немного изменились за последние годы, и теперь, я думаю, есть надежное решение для контроля доступа в Swift.
Если вы используете Objective-C, возможно потребуется некоторое время, прежде чем вы поймете преимущества управления доступом. Эффективное использование уровней доступа — это одно из понятий, которое отделяет начинающего разработчика от более опытного. Позвольте мне показать вам, почему это так.
Управление доступом может показаться не очень полезным, если вы работаете самостоятельно или в небольшой команде. Это правда, что управление доступом действительно полезно, если вы разрабатываете фреймворк, библиотеку или SDK, интегрированные в другие проекты программного обеспечения. Однако, если вы думаете, что контроль доступа полезен или необходим только в том случае, если вы работаете над базой исходного кода для распространения и использования третьими лицами, то вы заблуждаетесь.
Контроль доступа имеет много преимуществ, некоторые из которых легко упустить из виду. Очевидным преимуществом при правильном применении контроля доступа является коммуникация. Присоединив ключевое слово private к методу экземпляра класса, вы неявно сообщаете, что метод экземпляра не должен быть переопределен подклассами. С одним тщательно выбранным ключевым словом ваш код говорит сам за себя. Каждый разработчик понимает, почему метод экземпляра нельзя переопределить, если он объявлен приватным.
Тем не менее, контроль доступа имеет побочные эффекты, о которых не знают многие начинающие разработчики Swift. Знаете ли вы, что компилятор проверяет уровни доступа, которые вы применяли для оптимизации производительности вашего кода? Вот о чем я хочу поговорить сегодня.
Чтобы понять, как управление доступом может привести к более эффективному программному обеспечению, нам необходимо отклониться от темы и поговорить об Отправке Метода в Swift. Не волнуйтесь. Я буду рассматривать только основы. Это всего лишь техническое отклонение от маршрута, но я обещаю Вам, что будет интересно.
При вызове метода объекта или выполнении доступа к одному из его свойств, этому объекту отправляется сообщение. Время выполнения должно определить, какой метод соответствует сообщению. Взгляните на этот пример.
Мы вызываем метод makeKeyAndVisible() для объекта window, экземпляр UIWindow. Во время выполнения сообщение отправляется объекту window. Хотя вам и может показаться очевидным, какой метод необходимо вызвать для сообщения, но это не всегда так.
Что произойдет, если мы имеем дело с подклассом UIWindow, который замещает метод makeKeyAndVisible()? Время выполнения должно определить, нужно ли вызвать метод makeKeyAndVisible() для подкласса или супер класса.
Method dispatch — это набор правил, которые использует среда выполнения для определения метода, который должен вызываться для данного сообщения. Swift полагается на три типа: прямая отправка(direct dispatch), отправка таблицы(table dispatch) и отправка сообщения(message dispatch). Прямой отправкой также называется статическая отправка. Отправка сообщений и таблиц — это типы динамической отправки.
Рассылка сообщений — это полномочия Objective-C и среды выполнения Objective-C. Каждое отправленное сообщение отправляется динамически. Что это значит? Среда выполнения Objective-C показывает, какой метод вызывать для сообщения во время выполнения, проверяя иерархию классов. Вот почему Objective-C — такой динамичный язык. Динамичность Objective-C — это также способность нескольких функций Cocoa, включая Key-Value Observing, так и модель поведения.
Для динамической отправки есть один важный недостаток. Поскольку среда выполнения должна определить, какой метод вызывать для сообщения, динамическая отправка сама по себе медленна по сравнению с прямой отправкой. Другими словами, динамическая отправка идет с небольшими накладными расходами.
Статическая отправка, также известная как прямая отправка, но при этом имеет собственные отличия. Компилятор может определить во время компиляции, какой метод следует вызывать для сообщения. Как уже следует из названия, это не динамичная отправка. То, что потеряно в гибкости и динамизме, достигается в производительности.
Исполняемой среде не нужно определять, какой метод вызывать во время выполнения. Небольшая производительность, связанная с динамической отправкой, просто отсутствует при использовании прямой отправки.
Хоть я и не буду копать глубже в изучении отправки методов, вам нужно запомнить одно — статическая отправка более результативна, чем динамическая отправка. Для повышения производительности, задача компилятора заключается в том, чтобы, как можно больше продвигать вызовы методов от динамической до статической отправок.
В то время, как Objective-C полагается исключительно на отправку сообщений, Swift использует комбинацию прямой, табличной отправки и отправки сообщений. Swift отдает предпочтение статической отправке. Чтобы сосредоточиться на обсуждении, я рассматриваю статическую и динамическую отправку в оставшейся части этой статьи.
Наследование является мощной парадигмой, но в тоже время компилятору становится сложней определить, как именно метод вызвать. Взгляните на этот пример.
Класс ViewController определяет метод fetchNotes(). Вероятно, вы знаете, что методы и свойства объявляются внутренними по умолчанию, а значит, метод или свойство доступны другим объектам, которые определены в том же модуле. Достаточно ли объявлять fetchNotes(), как internal? Это имеет определенную зависимость.
Поскольку мы привязали ключевое слово internal к методу fetchNotes(), подкласс ViewController может переопределить метод fetchNotes(). В результате компилятор не может определить какую реализацию выполнить при вызове метода fetchNotes(). Среда выполнения требует динамически отправлять вызовы метода fetchNotes()
Когда более опытные разработчики смотрят на код, который они пишут, они анализируют, как он вписывается в проект, над которым они работают. Реализация метода является лишь частью решения. Нужно ли, чтобы подклассы ViewController могли переопределить метод fetchNotes()? Если ответ отрицательный, тогда вы должны прикрепить ключевое слово private или fileprivate. Это не только имеет смысл в контексте контроля доступа, но и повышает производительность. Почему это так?
Когда компилятор проверяет метод fetchNotes(), он понимает, что он объявлен приватным, подразумевая, что этот метод не может быть переопределен подклассом. Компилятор подбирает эту подсказку и безопасно выводит final на объявление метода. Всякий раз, когда ключевое слово final присоединяется к объявлению метода, вызовы этого метода могут быть отправлены статически вместо динамического способа, что приводит к небольшому увеличению производительности.
Данная статья не будет полной без упоминания оптимизации всего модуля. Компилятор Swift — это удивительное творение программной инженерии, и он обладает множеством фантастических функций, о которых мы не знаем. Одной из этих замечательных функций является оптимизация всего модуля.
Оптимизация модуля по умолчанию отключена для отладочных сборок. Это приводит к сокращению времени компиляции, но вы платите цену за время сохранения. Без оптимизации всего целого модуля каждый файл в вашем проекте скомпилирован отдельно, без учета остальной базы исходных кодов. Это хорошо во время разработки.
Однако, когда вы создаете свой проект для дальнейшего распространения, оптимизация всего модуля позволяет оптимизировать производительность вашего приложения. Компилятор больше не обрабатывает каждый файл отдельно. Он создает головоломку, которая является вашим проектом. Что это значит и почему это важно?
Еще раз посмотрим на фрагмент кода, который я показал вам раньше. Помните, что вызов fetchNotes() динамически отправляется во время выполнения. Если включена оптимизация всего модуля, то это не так. Когда компилятор проверяет весь модуль, ваш проект и определяет, как каждый файл вписывается в большую картинку, он обнаруживает отсутствие подклассов ViewController, которые переопределяют метод fetchNotes(). Это означает, что компилятор может сделать вывод final на объявлении метода fetchNotes().
Ключевое слово final означает, что метод или свойство не может быть переопределено в подклассах. Результат, который мы видели ранее, состоит в том, что вызовы fetchNotes() могут быть отправлены статическим способом, даже если fetchNotes() не объявляется приватным. Умный компилятор. Не правда ли?
Я часто пишу о развитии разработчика, подчеркивая, насколько важно инвестировать в образование. Изучение более тонких деталей языка Swift изменило меня, как разработчика. Код, который я пишу сегодня, отличается от кода, который я написал год назад.
Хотя отправка метода может показаться передовой темой, я считаю, что это так же важно, как изучение автоматического подсчета ссылок или протокол ориентированного программирования. Язык Swift легко уловить, и это хорошо. Если вы серьезно относитесь к тому, чтобы стать отличным разработчиком, важно продолжить обучение и расширить горизонты.
В сегодняшней статье я хочу рассказать о производительности в Swift и как на нее влияет контроль доступа. Контроль доступа — это механизм, который иногда упускают из виду начинающие разработчики. Цель данной статьи — показать вам, насколько важно, обдумывать сам код, который вы пишете, и о том, как каждая строка кода впишется в большую картину.
Немного о управлении доступом
Управление доступом в языке Swift несложно изучить. Уровни доступа и их определения немного изменились за последние годы, и теперь, я думаю, есть надежное решение для контроля доступа в Swift.
Если вы используете Objective-C, возможно потребуется некоторое время, прежде чем вы поймете преимущества управления доступом. Эффективное использование уровней доступа — это одно из понятий, которое отделяет начинающего разработчика от более опытного. Позвольте мне показать вам, почему это так.
Больше, чем определение уровней доступа
Управление доступом может показаться не очень полезным, если вы работаете самостоятельно или в небольшой команде. Это правда, что управление доступом действительно полезно, если вы разрабатываете фреймворк, библиотеку или SDK, интегрированные в другие проекты программного обеспечения. Однако, если вы думаете, что контроль доступа полезен или необходим только в том случае, если вы работаете над базой исходного кода для распространения и использования третьими лицами, то вы заблуждаетесь.
Контроль доступа имеет много преимуществ, некоторые из которых легко упустить из виду. Очевидным преимуществом при правильном применении контроля доступа является коммуникация. Присоединив ключевое слово private к методу экземпляра класса, вы неявно сообщаете, что метод экземпляра не должен быть переопределен подклассами. С одним тщательно выбранным ключевым словом ваш код говорит сам за себя. Каждый разработчик понимает, почему метод экземпляра нельзя переопределить, если он объявлен приватным.
Улучшение производительности в Swift
Тем не менее, контроль доступа имеет побочные эффекты, о которых не знают многие начинающие разработчики Swift. Знаете ли вы, что компилятор проверяет уровни доступа, которые вы применяли для оптимизации производительности вашего кода? Вот о чем я хочу поговорить сегодня.
Чтобы понять, как управление доступом может привести к более эффективному программному обеспечению, нам необходимо отклониться от темы и поговорить об Отправке Метода в Swift. Не волнуйтесь. Я буду рассматривать только основы. Это всего лишь техническое отклонение от маршрута, но я обещаю Вам, что будет интересно.
Чем же на самом деле является Method Dispatch.
При вызове метода объекта или выполнении доступа к одному из его свойств, этому объекту отправляется сообщение. Время выполнения должно определить, какой метод соответствует сообщению. Взгляните на этот пример.
window.makeKeyAndVisible()
Мы вызываем метод makeKeyAndVisible() для объекта window, экземпляр UIWindow. Во время выполнения сообщение отправляется объекту window. Хотя вам и может показаться очевидным, какой метод необходимо вызвать для сообщения, но это не всегда так.
Что произойдет, если мы имеем дело с подклассом UIWindow, который замещает метод makeKeyAndVisible()? Время выполнения должно определить, нужно ли вызвать метод makeKeyAndVisible() для подкласса или супер класса.
Method dispatch — это набор правил, которые использует среда выполнения для определения метода, который должен вызываться для данного сообщения. Swift полагается на три типа: прямая отправка(direct dispatch), отправка таблицы(table dispatch) и отправка сообщения(message dispatch). Прямой отправкой также называется статическая отправка. Отправка сообщений и таблиц — это типы динамической отправки.
Динамическая отправка/Dynamic Dispatch
Рассылка сообщений — это полномочия Objective-C и среды выполнения Objective-C. Каждое отправленное сообщение отправляется динамически. Что это значит? Среда выполнения Objective-C показывает, какой метод вызывать для сообщения во время выполнения, проверяя иерархию классов. Вот почему Objective-C — такой динамичный язык. Динамичность Objective-C — это также способность нескольких функций Cocoa, включая Key-Value Observing, так и модель поведения.
Для динамической отправки есть один важный недостаток. Поскольку среда выполнения должна определить, какой метод вызывать для сообщения, динамическая отправка сама по себе медленна по сравнению с прямой отправкой. Другими словами, динамическая отправка идет с небольшими накладными расходами.
Статическая отправка/Static Dispatch
Статическая отправка, также известная как прямая отправка, но при этом имеет собственные отличия. Компилятор может определить во время компиляции, какой метод следует вызывать для сообщения. Как уже следует из названия, это не динамичная отправка. То, что потеряно в гибкости и динамизме, достигается в производительности.
Исполняемой среде не нужно определять, какой метод вызывать во время выполнения. Небольшая производительность, связанная с динамической отправкой, просто отсутствует при использовании прямой отправки.
Оптимизация производительности
Хоть я и не буду копать глубже в изучении отправки методов, вам нужно запомнить одно — статическая отправка более результативна, чем динамическая отправка. Для повышения производительности, задача компилятора заключается в том, чтобы, как можно больше продвигать вызовы методов от динамической до статической отправок.
Оптимизация через контроль доступа
В то время, как Objective-C полагается исключительно на отправку сообщений, Swift использует комбинацию прямой, табличной отправки и отправки сообщений. Swift отдает предпочтение статической отправке. Чтобы сосредоточиться на обсуждении, я рассматриваю статическую и динамическую отправку в оставшейся части этой статьи.
Наследование является мощной парадигмой, но в тоже время компилятору становится сложней определить, как именно метод вызвать. Взгляните на этот пример.
import UIKit
class ViewController: UIViewController {
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// Fetch Notes
fetchNotes()
}
// MARK: - Helper Methods
func fetchNotes() {
...
}
}
Класс ViewController определяет метод fetchNotes(). Вероятно, вы знаете, что методы и свойства объявляются внутренними по умолчанию, а значит, метод или свойство доступны другим объектам, которые определены в том же модуле. Достаточно ли объявлять fetchNotes(), как internal? Это имеет определенную зависимость.
Поскольку мы привязали ключевое слово internal к методу fetchNotes(), подкласс ViewController может переопределить метод fetchNotes(). В результате компилятор не может определить какую реализацию выполнить при вызове метода fetchNotes(). Среда выполнения требует динамически отправлять вызовы метода fetchNotes()
Анализ кода, который вы пишете
Когда более опытные разработчики смотрят на код, который они пишут, они анализируют, как он вписывается в проект, над которым они работают. Реализация метода является лишь частью решения. Нужно ли, чтобы подклассы ViewController могли переопределить метод fetchNotes()? Если ответ отрицательный, тогда вы должны прикрепить ключевое слово private или fileprivate. Это не только имеет смысл в контексте контроля доступа, но и повышает производительность. Почему это так?
Когда компилятор проверяет метод fetchNotes(), он понимает, что он объявлен приватным, подразумевая, что этот метод не может быть переопределен подклассом. Компилятор подбирает эту подсказку и безопасно выводит final на объявление метода. Всякий раз, когда ключевое слово final присоединяется к объявлению метода, вызовы этого метода могут быть отправлены статически вместо динамического способа, что приводит к небольшому увеличению производительности.
Полная оптимизация модуля
Данная статья не будет полной без упоминания оптимизации всего модуля. Компилятор Swift — это удивительное творение программной инженерии, и он обладает множеством фантастических функций, о которых мы не знаем. Одной из этих замечательных функций является оптимизация всего модуля.
Оптимизация модуля по умолчанию отключена для отладочных сборок. Это приводит к сокращению времени компиляции, но вы платите цену за время сохранения. Без оптимизации всего целого модуля каждый файл в вашем проекте скомпилирован отдельно, без учета остальной базы исходных кодов. Это хорошо во время разработки.
Однако, когда вы создаете свой проект для дальнейшего распространения, оптимизация всего модуля позволяет оптимизировать производительность вашего приложения. Компилятор больше не обрабатывает каждый файл отдельно. Он создает головоломку, которая является вашим проектом. Что это значит и почему это важно?
Еще раз посмотрим на фрагмент кода, который я показал вам раньше. Помните, что вызов fetchNotes() динамически отправляется во время выполнения. Если включена оптимизация всего модуля, то это не так. Когда компилятор проверяет весь модуль, ваш проект и определяет, как каждый файл вписывается в большую картинку, он обнаруживает отсутствие подклассов ViewController, которые переопределяют метод fetchNotes(). Это означает, что компилятор может сделать вывод final на объявлении метода fetchNotes().
Ключевое слово final означает, что метод или свойство не может быть переопределено в подклассах. Результат, который мы видели ранее, состоит в том, что вызовы fetchNotes() могут быть отправлены статическим способом, даже если fetchNotes() не объявляется приватным. Умный компилятор. Не правда ли?
Продолжаем обучение.
Я часто пишу о развитии разработчика, подчеркивая, насколько важно инвестировать в образование. Изучение более тонких деталей языка Swift изменило меня, как разработчика. Код, который я пишу сегодня, отличается от кода, который я написал год назад.
Хотя отправка метода может показаться передовой темой, я считаю, что это так же важно, как изучение автоматического подсчета ссылок или протокол ориентированного программирования. Язык Swift легко уловить, и это хорошо. Если вы серьезно относитесь к тому, чтобы стать отличным разработчиком, важно продолжить обучение и расширить горизонты.