[Хабрахабр] NSProxy, как способ срезать на поворотах.pdf

(217 KB) Pobierz
NSProxy,
как способ срезать �½а поворотах
/
Хабрахабр
30/8/14 19:47
сегод�½я в
14:36
NSProxy,
как способ срезать �½а поворотах
Xcode*, Objective C*,
Разработка под
iOS*
tutorial
Как м�½огие читали в к�½игах, в языке
Objective-C
из�½ачаль�½о есть два кор�½евых класса
— NSObject
и
NSProxy.
И если �½а первом ос�½ова�½о
практически все и с �½им �½евозмож�½о �½е столк�½уться, то вторым пользуются з�½ачитель�½о реже. В этой �½ебольшой статье я опишу те
приме�½е�½ия этого класса, которые приходилось использовать м�½е.
В качестве первого примера, �½апом�½ю о такой штуке, как
UIAppearance —
�½асколько м�½е извест�½о
еди�½стве�½�½ое использова�½ие
NSProxy
в
базовых
iOS
фреймворках. Его задача заключается в предваритель�½ом ко�½фигурирова�½ии группы
UIKit
объектов в од�½ом месте. Фактически,
вы описываете �½екоторый �½абор действий, который будет приме�½яться к каждому создаваемому объекту
(таких
как зада�½ие цвета, шрифта и
других), удовлетворяющему �½екоторым условиям
(сейчас
таких условий два: класс объекта и класс объекта, содержащего �½аш объект как
subview).
Есть замечатель�½ая
статья,
посвяще�½�½ая использова�½ию, возмож�½остям и побоч�½ым эффектам такого и�½струме�½та, поэтому более
�½е будем �½а этом оста�½авливаться.
Если чест�½о, это �½е густо. Для такого гордого статуса, как
«оди�½
из двух кор�½евых классов», слишком мало примеров ко�½структив�½ого
использова�½ия. По своему лич�½ому опыту и опыту моих з�½акомых разработчиков
это достаточ�½о силь�½о затруд�½яет �½ачаль�½ое по�½има�½ие
этой сущ�½ости и, тем самым, как бы отговаривает �½ас от ее использова�½ия. Но и�½терес�½о же! И со време�½ем стали появляться задачи, для
которых
NSProxy
является потрясающим по удобству и�½струме�½том.
Декорирова�½ие объектов
В реализации паттер�½а
«декоратор»
есть оди�½ достаточ�½о �½еудоб�½ый аспект
и�½терфейс, собстве�½�½о, декоратора. Если у �½ас имеется
�½екоторая иерархия объектов, �½апример такая
@interface SomeClass : NSObject <SomeClassInterface>
@end
@interface ChildClass : SomeClass
-(void) additionalMethod;
@end
@interface DecoratorClass : NSObject <SomeClassInterface>
...
ChildClass *instance = [ChildClass new];
id
decoratedInstance = [DecoratorClass decoratedInstanceOf:instance]
То по по�½ят�½ым причи�½ам
decoratedInstance
уже �½е сможет выпол�½ить
additionalMethod.
И �½ам остается либо писать категории, либо
в�½едрять в кор�½евой класс какие-то хуки, либо за�½иматься еще каким-то подоб�½ым �½епотребством. Теперь посмотрим, как это мож�½о
решить, используя
NSProxy.
@interface SomeClass : NSObject;
-(int) getNumber;
-(NSString*) getString;
@end
@interface SimpleDecorator : NSProxy
@property (nonatomic,
strong)
SomeClass *instance;
+(instancetype) decoratedInstanceOf:(SomeClass*)instance;
@end
@implementation SimpleDecorator
-(instancetype) initWithObject:(SomeClass*)object
{
/*
мале�½ькое �½апоми�½а�½ие
- NSProxy
�½е имеет встрое�½�½ого и�½ициализатора, как
NSObject.
Поэтому вызов
[super init]
�½е �½уже�½*/
_instance = object;
return self;
}
+(instancetype) decoratedInstanceOf:(SomeClass*)instance
{
return
[[self alloc] initWithObject:instance];
http://habrahabr.ru/post/235041/
Página 1 de 6
NSProxy,
как способ срезать �½а поворотах
/
Хабрахабр
30/8/14 19:47
}
/*
ос�½ов�½ые методы
NSProxy -
о�½и отвечают за то, что мы делаем с теми методами, которые мы �½е можем обработать самостоятель�½о. В част
�½ости, сюда пойдут все вызовы, которые мы �½е декорировали
-
и самого класса
SomeClass
и его подклассов
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
return
[self.instance methodSignatureForSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
/*
этой строчкой мы, фактически, говорим, что если мы �½е можем обработать какой-то метод, то пусть его обработает, собстве�½�½о �½аш
объект В да�½�½ом случае так произойдет, �½апример, при вызове метода
getString */
[invocation invokeWithTarget:self.instance];
}
/*
собстве�½�½о декорируемый метод. Так как мы можем его обработать в�½утри и�½ста�½са
NSProxy -
предыдущие два метода вызываться �½е будут
*/
- (int) getNumber
{
return
[self.instance getNumber] +
1;
}
@end
И, собстве�½�½о, пример использова�½ия такой ко�½струкции:
SomeClass *object = [SomeClass new];
object = (SomeClass*)[SimpleDecorator decoratedInstanceOf:object];
NSLog(@"%d", [object getNumber]);
NSLog(@"%@", [object getString]);
При таком подходе �½а месте
SomeClass
может быть любой из его �½аслед�½ик и получе�½�½ый декорирова�½�½ый объект будет коррект�½о
откликаться �½а все отправле�½�½ые ему сообще�½ия.
Отложе�½�½ая �½астройка
По сути, мы сейчас будем решать задачу, похожу �½а ту, которую решает
UIAppearance.
При работе �½ад проектом, построе�½�½ым �½а ос�½ове
архитектуры, предлагаемой библиотектой-паттер�½ом
PureMVC (
en.wikipedia.org/wiki/PureMVC
)
пришлось �½еод�½ократ�½о сталкиваться с
ситуацией, когда в какой-то точке кода мы и�½ициируем цепочку кома�½д-действий, результатом которых будет �½екоторая ко�½текст�½о-
зависимая сущ�½ость
�½апример, всплывающее ок�½о, закрытие которого запускает следующие действия, зависящие от ко�½текста, в котором
было вызва�½о ок�½о. Ра�½ьше для этого использовался оди�½ из двух вариа�½тов:
Протяжка. Через всю цепочку действий мы передаем допол�½итель�½ые, как-то структурирова�½�½ые да�½�½ые, которые обрабатываем �½а месте
созда�½ия ок�½а. Доволь�½о �½аклад�½ый и �½е оче�½ь красивый способ, особе�½�½о если �½екоторые действия предполагают ветвле�½ие.
— «Я
все з�½аю». Так как
PureMVC
делает ос�½ов�½ые объекты, фактически, си�½глто�½ами и мож�½о в любой моме�½т достучаться до любого
мож�½о попытаться получить весь �½еобходимый ко�½текст в кома�½де созда�½ия всплывающего ок�½а, что чревато з�½ачитель�½ым увеличе�½ием
связ�½ости кода.
С помощью
NSProxy
мож�½о добиться �½ем�½ого и�½ого поведе�½ия: Выставить зависимые от ко�½текста свойства в объект ДО того, как о�½ был
созда�½
в том месте, где мы з�½аем эти условия. Звучит, ко�½еч�½о, �½есколько абсурд�½о, �½о как-то так о�½о и работает. Мы обращаемся с
имеющимся у �½ас объектом
NSProxy,
как с целевым объектом, которого еще �½ет.
NSProxy
хра�½ит в�½утри себя действия, которые мы
предпри�½яли и когда и�½ста�½цируется объект
приме�½яет их к �½ему.
Теперь �½ем�½ого кода.
С�½ачала класс, который мы хотим использовать для отложе�½�½ой �½астройки:
/*
Обыч�½ое всплывающее ок�½о, которое показывает сообще�½ие.
*/
@interface Popup : NSObject;
/*
Ключ �½еобходим для того, чтобы мож�½о было разделять, какие отложе�½�½ые сообще�½ия кто обрабатывает
*/
@property (nonatomic,
strong)
NSString* key;
@property (nonatomic,
strong)
NSString* message;
/*
мы �½е будем �½апрямую обращаться к прокси, обращаясь к �½ему только в ко�½тексте создаваемых объектов, как и в случае
UIAppearance */
+(DelayedActionsProxy*) delayedInitializerForKey:(NSString*)key;
/*
Показать всплывающее окошко. Собстве�½�½о это место и является точкой, когда будут приме�½е�½ы отложе�½�½ые �½астройки
*/
-(void) showPopup
@end
http://habrahabr.ru/post/235041/
Página 2 de 6
NSProxy,
как способ срезать �½а поворотах
/
Хабрахабр
30/8/14 19:47
@implementation Popup
-(void) showPopup
{
[DelayedActionsProxy invokeDelayedInvocationsWithTarget:self];
/*...*/
}
+(DelayedActionsProxy*) delayedInitializerForKey:(NSString*)key
{
return
[DelayedActionsProxy sharedProxyForKey:key fromClass:[self class]];
}
@end
А теперь, собстве�½�½о, прокси
@interface DelayedActionsProxy : NSProxy
+(void) invokeDelayedInvocationsWithTarget:(Popup*) target;
+(instancetype) sharedProxyForKey:(NSString*)key fromClass:(Class)objectClass;
@end
@interface DelayedActionsProxy()
/*
ключ �½ас и�½тересует для разделе�½ия поступающих в прокси отложе�½�½ых вызовов по раз�½ым объектам, а класс объекта
-
для коррект�½ого п
острое�½ия сиг�½атуры вызова
*/
@property (nonatomic,
strong)
NSString *currentKey;
@property (nonatomic,
assign)
Class currentClass;
@property (nonatomic,
strong)
NSMutableDictionary *delayedInvocations;
@end
@implementation DelayedActionsProxy
-(instancetype) init
{
self.delayedInvocations
= [NSMutableDictionary new];
return self;
}
static
DelayedActionsProxy *proxy =
nil;
+(instancetype) sharedProxyForKey:(NSString*)key fromClass:(Class)objectClass
{
static
dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
proxy = [[self alloc] init];
});
proxy.currentKey = key;
proxy.currentClass = objectClass;
return
proxy;
}
/*
Предполагается использова�½ие класса в виде
[[Popup delayedInitializerForKey:@"key"] setText:@"someText"],
то есть к моме�½ту вызова
-
еще �½е существует вызываемого объекта, и уже запол�½ятся поля
currentKey
и
currentClass */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
/*
Так как
currentClass
представляет собой класс, а �½е объект, мы долж�½ы воспользоваться методом
instanceMethodSignature
вместо
m
ethodSignature */
return
[self.currentClass instanceMethodSignatureForSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
if
(!self.delayedInvocations[self.currentKey])
{
self.delayedInvocations[self.currentKey]
= [NSMutableArray new];
}
http://habrahabr.ru/post/235041/
Página 3 de 6
NSProxy,
как способ срезать �½а поворотах
/
Хабрахабр
30/8/14 19:47
/*
мы �½е форвардим получаемые сообще�½ия, а аккурат�½е�½ько их складываем
*/
[self.delayedInvocations[self.currentKey] addObject:invocation];
}
/*
чтобы вызвать их по требова�½ию
*/
+(void) invokeDelayedInvocationsWithTarget:(Popup*) target
{
for
(NSInvocation *invocation in proxy.delayedInvocations[proxy.currentKey])
{
[invocation invokeWithTarget:target];
}
[proxy.delayedInvocations removeObjectForKey:proxy.currentKey];
}
@end
И пример использова�½ия
[[Popup delayedInitializerForKey:@"key"] setText:@"someText"];
Облегче�½ие работы с
UI-объектами
В м�½огопоточ�½ых приложе�½иях �½ередко, из-за �½ев�½иматель�½ости и �½едостаточ�½ого пла�½ирова�½ия �½а �½ачаль�½ом этапе, мож�½о получить код,
который выпол�½яется в другом потоке, �½о которому страсть как �½уж�½о модифицировать
UI (или
БД, или еще что-�½ибудь весьма
чувствитель�½ое к м�½огопоточ�½ому доступу). Код �½ачи�½ает обрастать м�½огочисле�½�½ыми
performSelectorOnMainThread, dispatch_async,
или
того хуже
обертками �½ад
NSInvocation,
поскольку
performSelectorOnMainThread
�½е дает использовать больше од�½ого параметра. Почему
бы �½е обзавестись еди�½ой оберткой для этого?
Пусть у �½ас есть какой-то объект
(�½апример
объект в игре �½а
Cocos2D)
@interface Entity : NSObject;
@property (nonatomic,
strong)
CCNode* node;
@end
@implementation Entity
-(void)setRepresentation:(CCNode *)node
{
/*
какое-то количество проверок коррект�½ости выставле�½ия
...
*/
_node = (CCNode*)[MainThreadProxy node];
}
@end
И, собстве�½�½о прокси
@interface MainThreadProxy : NSProxy
+(instancetype) proxyWithObject:(id)object;
/*
это стоит вы�½ести в отдель�½ый метод для того, чтобы мож�½о было делать цепочку действий с од�½им и тем же объектом, без постоя�½�½ого
форварди�½га
*/
-(void)performBlock:(void (^)(id object))block;
@end
@interface MainThreadProxy()
@property (nonatomic,
strong) id
object;
@end
@implementation MainThreadProxy
-(instancetype) initWithObject:(id)object
{
self.object
= object;
return self;
}
http://habrahabr.ru/post/235041/
Página 4 de 6
NSProxy,
как способ срезать �½а поворотах
/
Хабрахабр
30/8/14 19:47
+(instancetype) proxyWithObject:(id)object
{
return
[[self alloc] initWithObject:object];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
return
[self.object methodSignatureForSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
if
([NSThread isMainThread])
{
[invocation invokeWithTarget:self.object];
}
else
{
[invocation performSelectorOnMainThread:@selector(invokeWithTarget:) withObject:self.object waitUntilDone:YES];
}
}
-(void)performBlock:(void (^)(id object))block
{
if
([NSThread isMainThread])
{
block(self.object);
}
else
{
dispatch_sync(dispatch_get_main_queue(), ^{block(self.object);});
}
}
@end
Пример использова�½ия соверше�½�½о обыче�½,
[entity.node render];
Но у �½ас есть гара�½тия, что это может быть вызва�½о соверше�½�½о коррект�½о из любого потока.
Заверше�½ие
В качестве идей для чего мож�½о приме�½ять прокси, мож�½о еще выделить такие вещи как
Маскирова�½ие удале�½�½ого объекта
работать с объектом, представляющим из себя репрезе�½тацию какого-то сервиса с
�½ефиксирова�½�½ым време�½ем ответа
(�½апример
БД �½а сайте, объект �½а клие�½те, с которым ты соеди�½е�½ по
BlueTooth),
как обыч�½ый объект.
Просто медле�½�½ый.
Обертка в Прокси таймера, для того, чтобы стал уместе�½ си�½таксис �½авроде
[[object makeCallWithTimeInterval:1.0f andRepeatCount:2] someMethod];
и другие.
Следует учитывать, что это, разумеется, �½е па�½ацея. Следует быть оче�½ь аккурат�½ым при работе с геттерами, �½апример в приведе�½�½ом выше
примере строчка
[entity.node.effect update];
поведет себя �½е оче�½ь коррект�½о. Это, ко�½еч�½о, мож�½о исправить, �½о у этих исправле�½ий тоже есть своя це�½а. Рекоме�½дую �½е забывать об
этом, когда работаете с
NSProxy.
Post Scriptum
Вообще, о
NSProxy
мож�½о почитать еще �½апример
тут,
или
тут.
NSProxy
глубоко используется в таком и�½струме�½те как
OCMock.
Так или
и�½аче это достаточ�½о гибкий и удоб�½ый и�½струме�½т, пригод�½ый для �½екоторого класса задач
и с �½им, как ми�½имум, имеет смыл
оз�½акомиться.
туториал,
objective-c, ios sdk, nsproxy
37,5
657
10
i_user
http://habrahabr.ru/post/235041/
Página 5 de 6
Zgłoś jeśli naruszono regulamin