Как стать автором
Обновить

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

Время на прочтение 4 мин
Количество просмотров 9.1K


Уже много копий было сломанно о тему «обработка событий в 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(^(TLSignal<UIView *> *signal, 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++ и вообще, потому что все мы только учимся;)

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

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

Ссылки по теме


Теги:
Хабы:
+3
Комментарии 18
Комментарии Комментарии 18

Публикации

Истории

Работа

iOS разработчик
23 вакансии
Swift разработчик
38 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн