Вопрос
Мы разрабатываем пользовательскую систему сообщений EventEmitter на языке Objective-C. . Чтобы слушатели могли предоставлять обратные вызовы, мы должны требовать блоки или селекторы а> а почему?
Что бы вы предпочли использовать как разработчик, использующий стороннюю библиотеку? Что кажется наиболее соответствующим траектории, руководящим принципам и практикам Apple?
Фон
Мы разрабатываем совершенно новый iOS SDK на языке Objective-C, который другие третьи стороны будут использовать для встраивания функций в свои приложения. Большая часть нашего SDK потребует передачи событий слушателям.
Я знаю пять шаблонов для выполнения обратных вызовов в Objective-C, три из которых не подходят:
- NSNotificationCenter - нельзя использовать, потому что это не гарантирует, что наблюдатели порядка будут уведомлены, и потому что наблюдатели не могут предотвратить получение события другими наблюдателями (как
stopPropagation()
в JavaScript). - Наблюдение за ключом и значением — не Это не кажется хорошим архитектурным соответствием, поскольку то, что мы действительно имеем, - это передача сообщений, не всегда привязанная к «состоянию».
- Делегаты и источники данных - в нашем случае слушателей обычно будет много, а не один, который можно было бы с полным правом назвать делегатом.
И два из которых являются претендентами:
- Селекторы — согласно этой модели , вызывающие объекты предоставляют селектор и цель, которые совместно вызываются для обработки события.
- Блоки — введены в 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, ранее предоставленная, и позволяет им инкапсулировать состояние внутри обработчиков сигналов лучше, чем это делали наши блочные обработчики событий или методы уровня класса.