Подтвердить что ты не робот

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

Вопрос

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

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

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

Ответ 3

Отличная запись!

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

Что касается аспекта восприятия памяти слушателей, моя попытка решить это (в значительной степени используя Майка Эша MAKVONotificationCenter), (как показано здесь), чтобы безопасно удалить слушателей в обоих направлениях.

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

Ответ 4

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

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

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