Observer Pattern с строгой типизацией или зачем нам нужен Objective-C++

от автора

Уже много копий было сломанно о тему «обработка событий в Objective-C», о делегировании событий (к примеру, viewWillAppear:(BOOL)animated ), о том как это не удобно, когда надо слушать их одновременно в разных местах программы.

Я хочу предложить Вам свою реализацию шаблона Observer, который использует мощь C++0x и позволяет объявлять сигналы с жёстко типизированным списком параметров, например, вот так:

	new TLSignal<NSString *, BOOL>(self); 

Т.к. мои знания С++ довольно таки скудны, то буду признателен любым советам по улучшению данного кода.

Заинтересовавшихся прошу под кат.

Суть проблемы

Часто встречаются ситуации, когда необходимо уведомить другие объекты о каком-либо событии (свойство изменилось, смена состояния и так далее), при этом необходимо иметь возможность соблюдать соответствие «One-to-many», чтобы событие одновременно могли получить несколько слушателей. По этой причине вариант с делегатами сразу отпадает.
Какие варианты предлагают нам встроенные средства Objective-C?

  1. Notification Center — безусловно, у NC есть своя область применения, но он больше подходит для глобальных событий внутри всей программы, так же идентификация событий идёт по строковому идентификатору, что не есть хорошо. Ну и производительность такого решения оставляет желать лучшего, как и API.
  2. Key-Value Observing(KVO) — частный случай события, когда какое-то свойство объекта меняется. Позволяет делать чудесные вещи вроде Bindings, но API сводит все его прелести на нет.

Observer pattern

Лучше статьи на википедии думаю у меня рассказать не получится, поэтому я лишь уточню детали:

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

Итак, объявление сигнала выглядит вот так:

	new TLSignal<NSString *, BOOL>(self); 

Что означает, что:

  • Мы создали сигнал, который принимает 2 параметра: строку (NSString) и булеву (BOOL)
  • Target-ом для нашего сигнала будет self, т.е. объект, в котором этот сигнал создаётся.

При этом блок, слушающий данное событие, будет выглядеть вот так:

auto observerBlock = ^(id target, NSString *stringParam, BOOL boolParam) 	{ 		NSLog(@"%@ %@ %d", target, stringParam, boolParam); 	}; 

Обратите внимание на 1ый параметр id target, он передаётся всегда и равен «держателю» данного сигнала.

Пример использования

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

ExampleViewController.h

#import <UIKit/UIKit.h> #import "Signals.h"  @interface ExampleViewController : UIViewController  @property (nonatomic, readonly) TLSignal<UIView *> *viewDidLoadSignal;  @end 

Здесь всё просто, объявляем сигнал с идентификатором viewDidLoadSignal, который будет передавать экземляр UIView слушателям.

ExampleViewController.mm

#import "ExampleViewController.h"  @implementation ExampleViewController  @synthesize viewDidLoadSignal = _viewDidLoadSignal;  -(TLSignal<UIView *> *)viewDidLoadSignal { 	if(!_viewDidLoadSignal) 	{ 		// Лениво создаём экземпляр сигнала и передаём ему в конструктор ссылку на себя (источник события) 		viewDidLoadSignal = new TLSignal<UIView *>(self); 	} 	return _viewDidLoadSignal; }  -(void)viewDidLoad { 	[super viewDidLoad]; 	// Рассылаем событие слушателям, передав в них наш UIView 	self.viewDidLoadSignal->notify(self.view); }  @end 
AppDelegate.mm

#import "AppDelegate.h" #import "ExampleViewController.h"  @implementation AppDelegate  - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)options { 	UIWindow window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 	self.viewController = [[ExampleViewController alloc] initWithNibName:@"ViewController" bundle:nil]; 	 	// Добавим сигналу слушателя, который по событию поменяет цвет переданного в него UIView 	self.viewController.viewDidLoadSignal->addObserver(^(id target, UIView *targetView) 	{ 		targetView.backgroundColor = [UIColor blackColor]; 	}); 	 	self.window.rootViewController = self.viewController; 	[self.window makeKeyAndVisible]; 	return YES; }  @end  

Обратите внимание, что расширение файлов с кодом, в котором используются сигналы, должно быть .mm а не .m, иначе он будет воспринят как обычный Objective-C код и не поймёт нашей плюсовой магии!

Заключение

Я искренне надеюсь, что в Objective-C (например, 2.5 или 3.0) появится что-то подобное на уровне стандарта, а пока это не произошло, предлагаю Вам использовать мою библиотеку (она крайне лёгкая в использовании, код так же понятен даже с небольшими знаниями в C++, коими я и обладаю:)):
TLSignals на GitHub

На днях она появится в Cocoapods, а пока это не произошло, Вы можете просто скопировать файл TLSignals.h к себе в проект и начать работать с ним.

Код частично покрыт тестами, чтение которых поможет Вам лучше понять, как оно работает:
TLSignalsTests.mm на GitHub

Буду благодарен за дополнения и улучшения, а так же тыкание носом меня в слабые моменты со стороны C++ и вообще, потому что все мы только учимся;)

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

Спасибо за внимание!

ссылка на оригинал статьи http://habrahabr.ru/post/162235/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *