Естественно, все это благообразие можно спокойно использовать для построения сложных приложений. Никакой реальной необходимости в изобретении собственных велосипедов, конечно же, нет. Однако мне, как человеку пришедшему в разработку Objective C приложений из мира .NET, показалось очень неприятным то, что notification center, который я планировал использовать для событий, разраывает стек приложения, записывая произошедшее событие в очередь в UI thread, а протоколы в классическом представлении не слишком удобны, посему для удобства я решил соорудить себе механизм, который был бы гораздо больше похож на то, чем мы привыкли обходиться в мире .NET. Так родился родилась идея реализации модели множественных подписантов через специальный класс, названный AWHandlersList.
Данная статья рассчитана на программистов, которые имеют определенный опыт в создании приложений на Objective C и уже писали подобные велосипеды, либо решали похожие задачи стандартными способами. Данный вариант не является silver bullet, но показал себя как удобный механизм, минимизирующий написание кода для обарботки множеств событий с разными интерфейсами и параметрами.
Идея класса довольно проста — он содержит в себе список подписантов, каждый элемент которого состоит из двух компонентов — target и selector.
Для чего создан этот велосипед? Мне показалось, что он удобнее, чем все представленные стандартные модели, для некоторой логики, связанной с передачей событий. Может быть, он кому-то поможет облегчить жизнь.
В .NET привычная модель событийной логики предлагает делегат с двумя параметрами — sender типа object и args типа, наследуемого от EventArgs. Чтобы не ломать себе мозг, сделаем то же самое. Для начала, определим пустой класс EventArgs, от которого будут наследоваться все аргументы событий.
@interface AWEventArgs : NSObject @end
Теперь определим класс, который будет содержать пару «целевой объект и вызываемый метод», добавив туда заодно некоторую отладочную информацию для того, чтобы в дальнейшем можно было легко отлаживать логику событий.
@interface AWEventHandler : NSObject { @private NSString *_description; } @property (nonatomic, assign) id target; @property (nonatomic, assign) SEL method; +(AWEventHandler *)handlerWithTarget:(id)target method:(SEL)method; @end @implementation AWEventHandler @synthesize method, target; -(id)initWithTarget:(id)t method:(SEL)m; { self = [super init]; if(self) { target = t; method = m; _description = [[NSString alloc] initWithFormat:@"EventHandler, Target=%@, Method=%@", NSStringFromClass([target class]), NSStringFromSelector(method)]; } return self; } -(NSString *)description { return _description; } -(void)dealloc { [_description release]; [super dealloc]; } +(AWEventHandler *)handlerWithTarget:(id)target method:(SEL)method { AWEventHandler *handler = [[[AWEventHandler alloc] initWithTarget:target method:method] autorelease]; return handler; } @end
Как видите, и target, и method по сути представляют собой слабые ссылки. Это вполне закономерно — слабые ссылки повсеместно используются в мире Objective C для того, чтобы избежть circular references и дать возможность автоматически освобождать объекты. К сожалению, это приводит к тому, что при небрежном кодировании повсеместно появляются «мертвые» указатели на объекты, которые роняют приложение, поэтому я чуть дальше покажу один красивый механизм, который позволяет предупреждать и устранять их появление.
Теперь, наконец, перейдем к нашему основному классу — списку подписантов. В коде есть нетривиальные моменты, но они решаются чтением документации, а если желания разбираться в вопросе нет — то его можно просто использовать, код полностью рабочий и вынут из «боевого» проекта.
@interface AWEventHandlersList : NSObject { NSMutableArray *_handlers; } @property (nonatomic, copy) NSString *name; -(void)addReceiver:(id)receiver delegate:(SEL)delegate; -(void)removeReceiver:(id)receiver delegate:(SEL)delegate; -(void)clearReceivers; -(void)invoke; -(void)invokeWithSender:(id)sender; -(void)invokeWithSender:(id)sender args:(AWEventArgs *)event; @property (nonatomic, retain) NSRunLoop *runLoop; @end
Вкратце поясню, зачем нужны поля данного класса.
Первое — это name. Я предпочитаю именовать события, чтобы можно было увидеть в логах, какое именно событие было вызвано. Обычно в качестве имени события я использую имя класса вкупе с именем вызываемого в нем для выбрасывания (raise) метода. Это удобная практика, так как позволяет не рыскать судорожно по стеку в поисках того, кто событие выбросил, а просто в консоли отладки посмотреть это значение.
Методы addReceiver
и removeRecevier
логичны — они принимают объект и селектор, которые в дальнейшем будут принимать вызовы.
Методы invoke
должны выбрасывать событие, передавая его для обработки в подписанные объекты. Они даются в трех вариантах — для того, чтобы не передавать пустые значения nil в том случае, если в каикх-то параметрах события нет нужды.
Метод clearReceivers
внутренний, его лучше определять в анонимной секции, так как вызывающий код не должен иметь возможности отписывать другие объекты от событий, но исторически сложилось так, что он вынесен в интерфейс. Это легко исправить, если вам кажется это неправильным.
Наконец, свойство runLoop
необходимо в том случае, если вы собираетесь делать так, чтобы некоторые события были привязаны к определенному потоку (thread). Например, это необходимо, если существует какой-то код в worker thread должен обновлять визуальную часть приложения, либо наоборот — из UI thread должен быть доступ к какому-либо worker thread, синхронизируемому через очередь сообщений, то есть если есть необходимость выбрасывать события и обрабатывать их в разных потоках.
Реализация класса идейно тривиальна, однако требует некоторого понимания того, как работают селекторы. Сложные моменты я проясню в комментариях в самом коде.
@implementation AWEventHandlersList @synthesize runLoop = _runLoop; @synthesize name = _name; -(id)init { self = [super init]; if(!self) return nil; _handlers = [[NSMutableArray alloc] init]; return self; } -(void)addReceiver:(id)receiver delegate:(SEL)delegate { /* Этот код можно убрать, если вы гарантируете, что каждый объект будет подписываться на событие только один раз, либо * вам необходимо множественное подписание. Я предпочитаю работать со страховкой */ [self removeReceiver:receiver delegate:delegate]; [_handlers addObject:[AWEventHandler handlerWithTarget:receiver method:delegate]]; } -(void)removeReceiver:(id)receiver delegate:(SEL)delegate { /* В идеале снятие копии со списка, сделанное для поддержки многопоточности, должно производитсья в критической секции * (NSLock), однако я опустил этот момент, как как у меня подписание на события всегда происходит в одном потоке, * а копия списка берется для того, чтобы в будущем достаточно было обернуть вызов в NSLock */ for(AWEventHandler *handler in [[_handlers copy] autorelease]) if(handler.method == delegate && handler.target == receiver) [_handlers removeObject:handler]; } -(void)clearReceivers { [_handlers removeAllObjects]; } -(void)invoke { [self invokeWithSender:nil args:nil]; } -(void)invokeWithSender:(id)sender { [self invokeWithSender:sender args:nil]; } -(void)invokeWithSender:(id)sender args:(AWEventArgs *)event { [self invokeWithSender:sender args:event runLoop:_runLoop]; } -(void)invokeWithSender:(id)sender args:(AWEventArgs *)event runLoop:(NSRunLoop *)runLoop { /* Вс случае, если к текущему потоку не привязан цикл выборки сообщений, метод вернет null и выполнеие будет * происходить по обычному сценарию */ if(!runLoop) runLoop = [NSRunLoop currentRunLoop]; NSUInteger order = 1; NSArray *handlersCopy = [NSArray arrayWithArray:_handlers]; for(AWEventHandler *handler in handlersCopy) if(runLoop == [NSRunLoop currentRunLoop]) [self internalInvoke:[NSArray arrayWithObjects:handler, sender == nil ? [NSNull null] : sender, event == nil ? [NSNull null] : event, nil]]; else [runLoop performSelector:@selector(internalInvoke:) target:self argument:[NSArray arrayWithObjects:handler, sender == nil ? [NSNull null] : sender, event == nil ? [NSNull null] : event, nil] order:order++ modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]]; } /* Передача объектов производится через массив для возможности работы с потоками через performSelector:target:argument:order:modes: */ -(void)internalInvoke:(NSArray *)data { AWEventHandler *handler = [data objectAtIndex:0]; id sender = [data objectAtIndex:1]; if(sender == [NSNull null]) sender = nil; id args = [data objectAtIndex:2]; if(args == [NSNull null]) args = nil; /* Данный класс используется для анализа сигнатуры метода и определения потребного числа параметров его вызова */ NSMethodSignature *mSig = [handler.target methodSignatureForSelector:handler.method]; if([mSig numberOfArguments] == 2) [handler.target performSelector:handler.method]; else if([mSig numberOfArguments] == 3) [handler.target performSelector:handler.method withObject:sender]; else if ([mSig numberOfArguments] == 4) [handler.target performSelector:handler.method withObject:sender withObject:args]; else @throw [NSException exceptionWithName:@"Invalid selector type" reason:@"This type of selector is not supported" userInfo:nil]; } -(void)dealloc { self.name = nil; [self clearReceivers]; [_handlers release]; [super dealloc]; } @end
Теперь определим пару вспомогательных макросов, которые дадут нам возможность встраивать логику работы с событиями в класс буквально двумя строчками.
#define DEFINE_EVENT(eventName) \ -(void)add ## eventName ## Handler:(id)receiver action:(SEL)action; \ -(void)remove ## eventName ## Handler:(id)receiver action:(SEL)action #define DEFINE_EVENT_IMPL(eventName, innerVariable) \ -(void)add ## eventName ## Handler:(id)receiver action:(SEL)action \ { \ [innerVariable addReceiver:receiver delegate:action]; \ } \ \ -(void)remove ## eventName ## Handler:(id)receiver action:(SEL)action \ { \ [innerVariable removeReceiver:receiver delegate:action] ; \ } \
Теперь для того, чтобы создать в классе событие, нужно определить внутреннюю списочную переменную:
AWEventHandlersList *_handlers;
Определить событие в интерфейсе
DEFINE_EVENT(Event);
И связать список с событием
DEFINE_EVENT_IMPL(Event, _handlers)
В классе автоматически добавляются два метода — addEventHandler:action:
и removeEventHandler:action:
, а вызвать событие можно через методы invoke
объекта _handlers
.
Конечно, не стоит забывать о том, что объект _handlers нужно инициализировать в конструкторе
_handlers = [AWEventHandlersList new];
И уничтожать в деструкторе объекта
[_handlers release];
Во второй части статьи я расскажу, к каким проблемам ведет использование этого подхода и как справляться с трудностями «мертвых» ссылок, которые возникают в любом мало-мальски объемном приложении в результате наших собственных ошибок.
ссылка на оригинал статьи http://habrahabr.ru/post/168179/
Добавить комментарий