Селекторы или блоки для обратных вызовов в библиотеке Objective-C


Вопрос

Мы разрабатываем пользовательскую систему сообщений EventEmitter inspired в Objective-C. Для слушателей, чтобы обеспечить обратные вызовы, должны ли мы требовать блоки или селекторы и почему?

Что бы вы предпочли использовать, как разработчик, потребляющий стороннюю библиотеку? Который кажется наиболее в соответствии с траекторией от Apple, руководящих принципов и практики?

Фон

Мы разрабатываем совершенно новый iOS SDK в Objective-C, который третьи лица будут использовать для внедрения функциональности в свое приложение. Большая часть нашего SDK потребует передачи событий слушателям.

Я знаю пять паттернов для выполнения обратных вызовов в Objective-C, три из которых не подходят:
  • NSNotificationCenter - не может использоваться, потому что он не гарантирует, что наблюдатели порядка будут уведомлены, и потому что нет никакого способа для наблюдателей предотвратить получение другими наблюдателями события (например, stopPropagation() будет в яваскрипт).
  • наблюдение за ключами-значениями - не похоже на хорошую архитектурную подгонку, поскольку то, что мы действительно имеем, - это передача сообщений, не всегда связанная с "состоянием".
  • делегаты и источники данных - в нашем случае обычно будет много слушателей, а не один, которого можно было бы по праву назвать делегатом.

И двое из которых являются претендентами:

  • селекторы - в рамках этой модели вызывающие устройства предоставляют селектор и целевые объекты, которые совместно вызываются для обработки события.
  • блоки - введенные в 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, ранее предоставленная, и позволяет им инкапсулировать состояние внутри обработчиков сигналов лучше, чем наше блочное событие обработчики или методы уровня класса сделали это.
4 19

4 ответа:

Лично я ненавижу использовать делегатов. Из-за того, как objective-C структурирован, он действительно загромождает код, если мне нужно создать отдельный объект / добавить протокол только для уведомления об одном из ваших событий, и я должен реализовать 5/6. По этой причине я предпочитаю блоки.

Хотя у них (блоков) есть свои недостатки (например, управление памятью может быть сложным). Они легко расширяемы, просты в реализации и просто имеют смысл в большинстве ситуаций.

В то время как apple конструктивные структуры могут использовать метод sender-delegate, это только для обратной совместимости. Более поздние API Apple используют блоки (например, CoreData), потому что они являются будущим objective-C. Хотя они могут загромождать код при использовании за бортом, это также позволяет использовать более простые "анонимные делегаты", что невозможно в objective C.

В конце концов, это действительно сводится к этому: Готовы ли вы отказаться от некоторых старых, более устаревших платформ в обмен на использование блоков против а делегат? Одним из главных преимуществ делегата является то, что он гарантированно работает в любой версии objc-runtime, тогда как блоки являются более поздним дополнением к языку.

Насколько NSNotificationCenter/KVO они оба полезны и имеют свои цели, но как делегат они не предназначены для использования. Ни один из них не может отправить результат обратно отправителю, и для некоторых ситуаций это жизненно важно (например, -webView:shouldLoadRequest:).

Я думаю, что правильнее всего реализовать и то, и другое, использовать его в качестве клиента и посмотреть, что кажется наиболее естественным. Оба подхода имеют свои преимущества, и это действительно зависит от контекста и того, как вы ожидаете использовать SDK.

Основным преимуществом селекторов является простое управление памятью-до тех пор, пока клиент регистрирует и отменяет регистрацию правильно, ему не нужно беспокоиться об утечках памяти. С блоками управление памятью может стать сложным, в зависимости от того, что клиент делает внутри блок. Также проще провести модульное тестирование метода обратного вызова. блоки, безусловно, могут быть написаны, чтобы быть проверяемыми , но это не обычная практика из того, что я видел.

Основным преимуществом блоков является гибкость-клиент может легко ссылаться на локальные переменные, не делая их иварными.

Поэтому я думаю, что это просто зависит от случая использования-нет никакого "объективного правильного ответа" на такой общий вопрос дизайна.

Великолепное сочинение!

Исходя из написания большого количества JavaScript, событийное Программирование чувствует себя намного чище, чем иметь делегатов взад и вперед, по моему личному мнению.

Что касается аспекта управления памятью слушателей, то моя попытка решить эту проблему (сильно отталкиваясь от Маквонотификационого Центра Майка Эша ), сводит на нет как реализацию вызывающего, так и реализацию излучателя dealloc (, как показано здесь), чтобы безопасно удалить слушателей обоими способами.

Я не совсем конечно, насколько безопасен этот подход, но идея состоит в том, чтобы попробовать его, пока он не сломается.

Особенность библиотеки в том, что вы можете только в некоторой степени предвидеть, как она будет использоваться. поэтому вам нужно предоставить решение, которое будет максимально простым и открытым - и знакомым пользователям.

  • для меня все это лучше всего подходит для делегирования. Хотя вы правы, что он может иметь только на слушателе (делегате), это не означает никаких ограничений, так как пользователь может написать класс как делегат, который знает обо всех желаемых слушателях и информирует их. Конечно, вы можете обеспечить регистрацию класс. это вызовет методы делегата для всех зарегистрированных объектов.
  • блоки так же хороши.
  • То, что вы называете селекторами, называется целью/действием и просто, но мощно.
  • KVO, по-видимому, не является оптимальным решением для меня, так как это может ослабить инкапсуляцию или привести к ментальной модели wrog использования классов вашей библиотеки.
  • NSNotifications приятно информировать о некоторых событиях, но пользователи не должны быть вынуждены их использовать, так как они довольно неформальный. и ваши классы не смогут узнать, есть ли кто-то настроенный.

Некоторые полезные мысли по API-дизайну: http://mattgemmell.com/2012/05/24/api-design/