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

Вопрос

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


person jimbo    schedule 13.06.2012    source источник
comment
Учитывая, что это вопрос дизайна/белой доски, Разработка программного обеспечения могла бы быть лучшим местом для этого - просто чтобы сообщить вам о другом варианте.   -  person jscs    schedule 14.06.2012
comment
Спасибо, Джош, я посмотрел на Programmers и подумал о том, чтобы опубликовать это там, но это выглядело как вопросы, не зависящие от языка. Поскольку это зависит от языка/системы, я думал, что это пойдет сюда, но, как я уже упоминал в вопросе, я, конечно, не уверен в этом.   -  person jimbo    schedule 15.06.2012


Ответы (4)


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

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

Хотя структуры дизайна Apple могут использовать метод отправителя-делегата, это только для обратной совместимости. Более поздние API-интерфейсы Apple используют блоки (например, CoreData), потому что они — будущее Objective-C. Хотя они могут загромождать код при использовании за бортом, они также позволяют использовать более простые «анонимные делегаты», что невозможно в объективном C.

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

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

person Richard J. Ross III    schedule 13.06.2012
comment
Мы ориентируемся на iOS 4.x и выше, поэтому блоки должны поддерживаться на наших целях. - person jimbo; 14.06.2012

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

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

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

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

person Christopher Pickslay    schedule 14.06.2012
comment
Спасибо, что обратили внимание на проблемы с управлением памятью; это хорошо учитывать. Реализация и того, и другого, а также проведение A/B-тестирования, к сожалению, не сработает для этого проекта, поскольку почти невозможно извлечь функции из библиотеки, когда у вас есть пользователи (что у нас будет по бизнес-причинам почти сразу). - person jimbo; 15.06.2012
comment
Я не имел в виду, что вы должны делать A/B-тест. Я имею в виду, что вы должны написать клиентский код для него самостоятельно. Это похоже на разработку через тестирование — использование ваших интерфейсов в качестве клиента помогает вам создавать лучшие интерфейсы. - person Christopher Pickslay; 15.06.2012

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

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

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

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

person Goos    schedule 15.04.2013

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

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

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

person vikingosegundo    schedule 13.06.2012
comment
Спасибо за ответ. В нашем случае делегирование на самом деле не соответствует, потому что через него будет передаваться множество разных сущностей с множеством разных обработчиков, прослушивающих разные виды событий. Я прочитал статью Мэтта. К сожалению, он вообще не упоминает блоки. - person jimbo; 14.06.2012
comment
Я все еще думаю, что делегирование/протоколы вам подойдут. сообщения, которые будут отправлены, должны будут соответствовать определенному протоколу. Это может быть и неформальным. Не имеет большого значения, должен ли отправитель упаковать свое сообщение в определенный блок или в соответствии с определенным протоколом. Я не говорю, что вы не должны использовать блоки. т. е. AFNetworking отлично с этим справляется. но с блоками вы должны предвидеть, как пользователь захочет их использовать, поскольку вы должны предложить ему параметры блоков (да, я знаю о __block, но это действительно не элегантно). - person vikingosegundo; 14.06.2012