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

в 18:49, , рубрики: c++0x, objective-c, Observer, метки: , ,

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++ и вообще, потому что все мы только учимся;)

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

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

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

Автор: bsideup

Источник



https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js