Селектори или блокове за обратни извиквания в библиотека 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
Благодаря, Джош, погледнах Програмисти и си помислих да публикувам това там, но изглеждаше, че въпросите са много по-независими от езика. Тъй като това беше специфично за език/система, мислех, че ще отиде тук, но както споменах във въпроса, със сигурност не съм сигурен в това.   -  person jimbo    schedule 15.06.2012


Отговори (4)


Лично аз мразя използването на делегати. Поради структурата на object-C, той наистина затрупва кода. Ако трябва да създам отделен обект/да добавя протокол, само за да бъда уведомен за едно от вашите събития, и трябва да внедря 5/6. Поради тази причина предпочитам блокове.

Докато те (блоковете) имат своите недостатъци (напр. управлението на паметта може да бъде трудно). Те могат лесно да се разширяват, лесни са за изпълнение и просто имат смисъл в повечето ситуации.

Докато дизайнерските структури на Apple могат да използват метода подател-делегат, това е само за обратна съвместимост. По-новите API на Apple използват блокове (напр. CoreData), защото те са бъдещето на goal-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.

Основното предимство на селекторите е простото управление на паметта - стига клиентът да се регистрира и дерегистрира правилно, не е необходимо да се тревожи за изтичане на памет. С блоковете управлението на паметта може да стане сложно в зависимост от това какво прави клиентът вътре в блока. Също така е по-лесно да тествате метода за обратно извикване. Блоковете със сигурност могат да бъдат написани така, че да могат да се тестват, но това не е обичайна практика от това, което съм виждал.

Основното предимство на блоковете е гъвкавостта - клиентът може лесно да препраща към локални променливи, без да ги прави ivari.

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

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

Нещо за библиотеката е, че можете само до известна степен да предвидите как ще бъде използвана. така че трябва да предоставите решение, което е възможно най-просто и отворено — и познато на потребителите.

  • За мен всичко това пасва най-добре на делегирането. Въпреки че сте прав, че може да има само слушател (делегат), това не означава ограничение, тъй като потребителят може да напише клас като делегат, който знае за всички желани слушатели и ги информира. Разбира се, можете да предоставите клас за регистрация. който ще извика методите на делегиране на всички регистрирани обекти.
  • Блоковете са също толкова добри.
  • това, което наричате селектори, се нарича цел/действие и е просто, но мощно.
  • KVO изглежда не е оптимално решение и за мен, тъй като вероятно би отслабило капсулирането или би довело до грешен умствен модел за това как да използвате класовете на вашата библиотека.
  • NSNotifications е хубаво да информират за определени събития, но потребителите не трябва да бъдат принуждавани да ги използват, тъй като са доста неформални. и вашите класове няма да могат да разберат дали има някой включен.

някои полезни мисли относно API-Design: 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