2013-06-21 3 views
1

У меня есть пользовательский контроллер представления контейнеров, который управляет иерархией представления моего приложения. Я знаю, что каждый контроллер является своего рода дочерним элементом этого контроллера контейнера. Я подумал, что было бы неплохо иметь категорию на UIViewController, которая позволила бы мне получить доступ к контроллеру контейнера, независимо от того, где я нахожусь в иерархии.objc_setAssociatedObject в наборах категорий для всех подклассов

Это включает в себя рекурсивную прогулку по иерархии диспетчера, поэтому я подумал, что было бы неплохо попробовать и сделать это только один раз на контроллер. Таким образом, с objc_setAssociatedObject, я установил контейнер, как только нашел его, и установил флаг, чтобы я знал, нужно ли мне ходить по иерархии при последующих вызовах (я планировал сделать недействительным то, что когда контроллер просмотра когда-либо перемещался, но это, вероятно, , и я так далеко не добрался).

В любом случае, это работает в основном по большей части, за исключением того, что мой флаг для того, была ли иерархия пройдена, кажется, прикреплена к UIViewController, а не к определенным подклассам UIViewController.

Я swizzled + load, чтобы попытаться установить значения по умолчанию для моих связанных объектов безрезультатно.

Любые идеи? Как получить связанные объекты в категории для связи с подклассами класса, на котором определена категория?

Вот мой код, для хорошей оценки.

#import "UIViewController+LMPullMenuContainer.h" 
#import <objc/runtime.h> 

static char const * const CachedKey = "__LM__CachedBoolPullMenuAssociatedObjectKey"; 
static char const * const PullMenuKey = "__LM__PullMenuAssociatedObjectKey"; 

@implementation UIViewController (LMPullMenuContainer) 

+ (void)load { 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     SEL initSelector = @selector(initWithCoder:); 
     SEL pullViewInitSelector = @selector(init__LM__Swizzled__WithCoder:); 
     Method originalMethod = class_getInstanceMethod(self, initSelector); 
     Method newMethod = class_getInstanceMethod(self, pullViewInitSelector); 

     BOOL methodAdded = class_addMethod([self class], 
              initSelector, 
              method_getImplementation(newMethod), 
              method_getTypeEncoding(newMethod)); 

     if (methodAdded) { 
      class_replaceMethod([self class], 
           pullViewInitSelector, 
           method_getImplementation(originalMethod), 
           method_getTypeEncoding(originalMethod)); 
     } else { 
      method_exchangeImplementations(originalMethod, newMethod); 
     } 
    }); 
} 

- (instancetype)init__LM__Swizzled__WithCoder:(NSCoder *)coder { 
    self = [self init__LM__Swizzled__WithCoder:coder]; 
    if (self != nil) 
    { 
     objc_setAssociatedObject(self, CachedKey, [NSNumber numberWithBool:NO], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
     objc_setAssociatedObject(self, PullMenuKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
    } 
    return self; 
} 

- (LMPullMenuContainerViewController*)pullMenuContainerController { 
    BOOL isCached = [objc_getAssociatedObject(self, CachedKey) boolValue]; 
    if (isCached) { 
     return objc_getAssociatedObject(self, PullMenuKey); 
    } else { 
     return [self pullMenuParentOf:self]; 
    } 
} 

- (LMPullMenuContainerViewController *)pullMenuParentOf:(UIViewController *)controller { 
    if (controller.parentViewController) { 
     if ([controller.parentViewController isKindOfClass:[LMPullMenuContainerViewController class]]) { 
      objc_setAssociatedObject(self, CachedKey, [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
      objc_setAssociatedObject(self, PullMenuKey, controller.parentViewController, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
      return (LMPullMenuContainerViewController *)(controller.parentViewController); 
     } else { 
      return [self pullMenuParentOf:controller.parentViewController]; 
     } 
    } else { 
     objc_setAssociatedObject(self, CachedKey, [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
     objc_setAssociatedObject(self, PullMenuKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
     return nil; 
    } 
} 

На данный момент я смирился с настройкой свойства вручную, когда это необходимо.

+0

Что вы подразумеваете под флагом *, прикрепленным к UIViewController, а не к определенным подклассам UIViewController "*? Связанные объекты привязаны к экземпляру класса (sub), а не к классу. –

+0

Это то, что я думал и ожидал, однако, когда я проходил через него, я обнаружил, что 'isCached' возвращает' YES', прежде чем он будет установлен в определенных подклассах (но после того, как он был установлен в других подклассах). –

+0

Возможно, несвязанный, но не следует кэшировать результат также в 'return [self pullMenuParentOf: controller.parentViewController];' case? –

ответ

0

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