UIAppearance proxy for custom objects

前端 未结 4 540
小鲜肉
小鲜肉 2020-12-24 14:48

I have a custom object, it inherits from NSObject. This object does \"some things\", one of it is creating an UIView with some UIKit objects (UILabel, UIButtons ecc ecc...)

相关标签:
4条回答
  • 2020-12-24 14:59

    Nice implementation, I slightly modified the code and created the class as a subclass of NSProxy. Using it in a project I found a memory leak:

    For example: using the proxy to set global settings/appearance, each instance of that class will never reach refCount 0, so dealloc will never be called.

    Leak code:

    -(void)forwardInvocation:(NSInvocation *)anInvocation;
    {
        [...]
    
        // !! This will retain also the target
    
        [anInvocation retainArguments];
    
        [...]
    }
    

    Fix:

    -(void)forwardInvocation:(NSInvocation *)anInvocation
    {
         [anInvocation setTarget:nil];
         [anInvocation retainArguments];
    
         // add the invocation to the array
         [self.invocations addObject:anInvocation];
    }
    
    -(void)startForwarding:(id)sender
    {
         for (NSInvocation *invocation in self.invocations) {
    
             // Create a new copy of the stored invocation,
             // otherwise setting the new target, this will never be released
             // because the invocation in the array is still alive after the call
    
             NSInvocation *targetInvocation = [invocation copy];
             [targetInvocation setTarget:sender];
             [targetInvocation invoke];
             targetInvocation = nil;
         }
    }
    

    Copy category for NSInvocation

    -(id)copy
    {
         NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignature]];
         NSUInteger numberOfArguments = [[self methodSignature] numberOfArguments];
    
         [invocation setTarget:self.target];
         [invocation setSelector:self.selector];
    
         if (numberOfArguments > 2) {
             for (int i = 0; i < (numberOfArguments - 2); i++) {
                 char buffer[sizeof(intmax_t)];
                 [self getArgument:(void *)&buffer atIndex:i + 2];
                 [invocation setArgument:(void *)&buffer atIndex:i + 2];
             }
         }
    
         return invocation;
    }
    
    0 讨论(0)
  • 2020-12-24 15:02

    After some reserach I "give up" about using a standard Apple object. It doesn't exists, for now. I've created my own proxy, it's quite simple (works only with "appearance:" by now).

    Let's explain it. I want to set the appearance of "textColor" on a NSObject subclass, let's call it "FLObject". Make FLObject conforms to UIAppearance protocol and override the appearance method. In this method, you should return a proxy class (the one I created):

    + (id)appearance
    {
        return [FLAppearance appearanceForClass:[self class]];
    }
    

    How it works? FLAppearance creates a single instance of itself for each class passed by the appearanceForClass: method. If you call it two times for the same class, the same instance is returned.

    Then, you can do something like this:

    [[FLObject appearance] setTextColor:[UIColor redColor]]; 
    

    FLAppearance overrides the forwardInvocation: method, so it accepts all methods sent. Then, it puts all invocations in an array. When FLObject is initialized, a simple call to

    [(FLAppearance *)[FLAppearance appearanceForClass:[self class]] startForwarding:self];
    

    will start to send invocations and set the appearance. Sure, this needs some tuning and error checking, but I think it's a good start.

    @interface FLAppearance ()
    
    @property (strong, nonatomic) Class mainClass;
    @property (strong, nonatomic) NSMutableArray *invocations;
    
    @end
    
    static NSMutableDictionary *dictionaryOfClasses = nil;
    
    @implementation FLAppearance
    
    // this method return the same object instance for each different class
    + (id) appearanceForClass:(Class)thisClass
    {
        // create the dictionary if not exists
        // use a dispatch to avoid problems in case of concurrent calls
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            if (!dictionaryOfClasses)
                dictionaryOfClasses = [[NSMutableDictionary alloc]init];
        });
    
    
    
        if (![dictionaryOfClasses objectForKey:NSStringFromClass(thisClass)])
        {
            id thisAppearance = [[self alloc]initWithClass:thisClass];
            [dictionaryOfClasses setObject:thisAppearance forKey:NSStringFromClass(thisClass)];
            return thisAppearance;
        }
        else
            return [dictionaryOfClasses objectForKey:NSStringFromClass(thisClass)];
    }
    
    - (id)initWithClass:(Class)thisClass
    {
        self = [self initPrivate];
        if (self) {
            self.mainClass = thisClass;
            self.invocations = [NSMutableArray array];
        }
        return self;
    }
    
    - (id)init
    {
        [NSException exceptionWithName:@"InvalidOperation" reason:@"Cannot invoke init. Use appearanceForClass: method" userInfo:nil];
        return nil;
    }
    
    - (id)initPrivate
    {
        if (self = [super init]) {
    
        }
        return self;
    }
    
    -(void)forwardInvocation:(NSInvocation *)anInvocation;
    {
        // tell the invocation to retain arguments
        [anInvocation retainArguments];
    
        // add the invocation to the array
        [self.invocations addObject:anInvocation];
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        return [self.mainClass instanceMethodSignatureForSelector:aSelector];
    }
    
    -(void)startForwarding:(id)sender
    {
        for (NSInvocation *invocation in self.invocations) {
            [invocation setTarget:sender];
            [invocation invoke];
        }
    }
    
    0 讨论(0)
  • 2020-12-24 15:20

    For the purposes of my own project, I collect everything together and released custom UIApperance proxy as open source project MZApperance

    0 讨论(0)
  • 2020-12-24 15:25

    Check out http://logicalthought.co/blog/2012/10/8/uiappearance-and-custom-views

    Basically you just need to tag your properties with UI_APPEARANCE_SELECTOR and everything works as long as your class is a subclass of UIView which will handle the actual vending of the private _UIAppearance class.


    Edit:

    You're probably better off just rolling your own solution using a singleton and some class methods rather than attempting to do something scary with the runtime. It doesn't look like UIAppearance supports your use case.

    On the other hand, you could stick each object you vend in a private UIView subclass and then vend instances of that subclass instead. Then you can forward appearance messages sent to your NSObject to the instances you vend and use appearanceWhenContainedIn:<your private subclass>. That could get messy though and could be confusing for consumers of your class.

    0 讨论(0)
提交回复
热议问题