Вопрос
Мы разрабатываем пользовательскую систему сообщений EventEmitter в Objective-C. Чтобы слушатели предоставляли обратные вызовы, следует ли нам Blocks или Selectors и почему?
Что бы вы предпочли использовать в качестве разработчика, потребляющего стороннюю библиотеку? Что, по-видимому, наиболее соответствует траектории, рекомендациям и практикам Apple?
Фон
Мы разрабатываем новый iOS SDK в Objective-C, который другие сторонние пользователи будут использовать для добавления функциональности в свое приложение. Большая часть нашего SDK потребует передачи событий слушателям.
Есть пять моделей, которые я знаю для выполнения обратных вызовов в Objective-C, три из которых не подходят:
- NSNotificationCenter - не может использоваться, поскольку он не гарантирует, что наблюдатели заказа будут уведомлены и потому, что нет возможности для наблюдателей чтобы другие наблюдатели не получали событие (например,
stopPropagation()
в JavaScript). - Key-Value Observing - не кажется хорошей архитектурной подгонкой, так как у нас действительно есть передача сообщений, а не всегда "state" bound.
- Делегаты и источники данных - в нашем случае обычно будет много слушателей, а не один, который по праву можно назвать делегатом.
И два из них являются соперниками:
- Selectors - в этой модели вызывающие абоненты предоставляют селектор и цель, которые совместно вызываются для обработки события.
- Blocks - в iOS 4 блоки позволяют передавать функции без привязки к объекту, подобному наблюдателю/селектору шаблон.
Это может показаться эзотерическим мнением, но я чувствую, что существует объективный "правильный" ответ, который я просто слишком неопытен в определении Objective-C. Если там есть лучший сайт StackExchange для этого вопроса, пожалуйста, помогите мне, переместив его там.
ОБНОВЛЕНИЕ № 1 - Апрель 2013
Мы выбрали блоки как средство для определения обратных вызовов для наших обработчиков событий. Мы в значительной степени довольны этим выбором и не планируем удалять поддержку на основе блоков. Он имел два заметных недостатка: управление памятью и проектный импеданс.
Управление памятью
Блоки наиболее легко используются в стеке. Создание долгоживущих блоков путем их копирования в кучу создает интересные проблемы управления памятью.
Блоки, которые вызывают вызовы методам на содержащем объекте, неявно увеличивают количество ссылок self
. Предположим, у вас есть сеттер для свойства name
вашего класса, если вы вызываете name = @"foo"
внутри блока, компилятор рассматривает это как [self setName:@"foo"]
и сохраняет self
, чтобы он не был освобожден, пока блок все еще вокруг.
Реализация средства EventEmitter означает наличие долгоживущих блоков. Чтобы предотвратить неявное сохранение, пользователю эмиттера необходимо создать ссылку __block
для self
вне блока, например:
__block *YourClass this = self;
[emitter on:@"eventName" callBlock:...
[this setName:@"foo"];...
}];
Единственная проблема с этим подходом заключается в том, что this
может быть освобожден до вызова обработчика. Поэтому пользователи должны отменить регистрацию своих слушателей при освобождении.
Конструктивное сопротивление
Опытные разработчики Objective-C ожидают взаимодействия с библиотеками с использованием привычных шаблонов. Делегаты - чрезвычайно знакомая модель, и поэтому канонические разработчики ожидают ее использования.
К счастью, шаблоны делегатов и блочные слушатели не являются взаимоисключающими. Хотя наш эмитент должен быть способен обрабатывать прослушиватели из многих мест (наличие одного делегата не будет работать), мы все равно можем предоставить интерфейс, который позволит разработчикам взаимодействовать с эмиттером, как если бы их класс был делегатом.
Мы еще не реализовали это, но мы, вероятно, будем основываться на запросах пользователей.
ОБНОВЛЕНИЕ # 2 - Октябрь 2013
Я больше не работаю над проектом, который породил этот вопрос, довольно счастливо вернулся на родину JavaScript.
Умные разработчики, которые взяли на себя этот проект, решили полностью отказаться от нашего специального блочного EventEmitter. Предстоящий выпуск переключился на ReactiveCocoa.
Это дает им шаблон сигнализации более высокого уровня, чем ранее предоставленная библиотека EventEmitter, и позволяет им инкапсулировать состояние внутри обработчиков сигналов лучше, чем наши обработчики событий на основе блоков или методы на уровне классов.