Object

iOS Runtime学习笔记 (二) - 实战应用

2018-03-23  本文已影响67人  brownfeng

iOS runtime实战应用

iOS runtime 进行添加属性,并支持KVO监听

iOS 中category和runtime的AssociatedObject是两大非常重要的工具:

  1. category可以给既有类直接添加方法
  2. associateObject可以给既有类添加属性(类似成员变量)

结合这两个工具, 那么通过category添加property方法.然后结合associateObject增加关联对象,完成属性存取.

@interface UIViewController (Extension)
@property (nonatomic, copy) NSString * categoryString;
@end

@implementation UIViewController (Extension)
-(NSString *)categoryString{
    return objc_getAssociatedObject(self, @selector(categoryString));
}


-(void)setCategoryString:(NSString *)categoryString{
    objc_setAssociatedObject(self, @selector(categoryString), categoryString, OBJC_ASSOCIATION_COPY);
}

@end

并且这种方法也支持KVO的监听:

-(void)test{
    self.categoryString = @"Runtime生成的属性";
    [self addObserver:self forKeyPath:@"categoryString" options:NSKeyValueObservingOptionNew context:nil];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"<接收到通知: object:%@ keyPath:%@ change:%@>", object, keyPath,change);

}

iOS 方法交换Method Swizzling

这里是哟个呢 method swizzling劫持 UIViewController 的viewWillAppear:方法, 周全起见,有两种情况要考虑一下(需要明确一下,它的目的是为了使用一个重写的方法替换掉原来的方法。但重写的方法可能是在父类中重写的,也可能是在子类中重写的):

  1. 复写的方法(overridden)并没有在目标类中实现(notimplemented),而是在其父类中实现了。
  2. 这个方法已经存在于目标类中(does existing the class itself)。这两种情况要区别对待。

对于第一种情况,应当先在目标类增加一个新的实现方法(override),然后将复写的方法替换为原先(的实现(original one)。 对于第二情况(在目标类重写的方法)。这时可以通过method_exchangeImplementations来完成交换.

+(void)load{
    NSString *className = NSStringFromClass(self.class);
    NSLog(@"classname %@", className);
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //要特别注意你替换的方法到底是哪个性质的方法
        // When swizzling a Instance method, use the following:
        //        Class class = [self class];

        // When swizzling a class method, use the following:
        Class class = object_getClass((id)self);

        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod =
        class_addMethod(class,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

#pragma mark - Method Swizzling

/*
我们通过method swizzling修改了UIViewController的@selector(viewWillAppear:)对应的函数指针,使其实现指向了我们自定义的xxx_viewWillAppear的实现。这样,当UIViewController及其子类的对象调用viewWillAppear时,都会打印一条日志信息。
 */
- (void)xxx_viewWillAppear:(BOOL)animated {
    // 由于 系统在调用viewWillAppear时候 会调用到这里. 然后.调用这个以后. 继续调用xxx_viewWillAppear方法, 实际是调用系统的 viewWillAppear方法(因为两个交换过)
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}

使用Runtime进行 json/dict -> model

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString *city;
@property (nonatomic, copy) NSString *job;

- (instancetype)initWithNSDictionary:(NSDictionary *)dict;
@end

@implementation Person

-(instancetype)initWithNSDictionary:(NSDictionary *)dict{
    self = [super init];
    if (self) {
        [self processDict:dict];
    }
    return self;
}

-(void)processDict:(NSDictionary *)dict{
    NSMutableArray *keys = [[NSMutableArray alloc] init];
    unsigned int count = 0;
    objc_property_t *props = class_copyPropertyList([self class], &count);
    for (int i = 0; i < count; i++) {
        objc_property_t prop = props[i];
        const char *propCStr = property_getName(prop);
        NSString *propName = [NSString stringWithCString:propCStr encoding:NSUTF8StringEncoding];
        [keys addObject:propName];
    }
    free(props);
    for (NSString *key in keys) {
        if ([dict valueForKey:key]) {
            [self setValue:[dict valueForKey:key] forKey:key];
        }
    }
}
@end

使用runtime进行简单的json -> model, 分成以下步骤:

  1. json string -> NSDictionary/NSArray<Dict>
  2. NSDictionary -> model property

主要在第二步中, 使用runtime遍历model中的所有property, 然后根据Dict中的key去设置model中的property对应的值.

还有一个比较常用的是 model -> dict, 在实际中需要自己根据实际项目情况进行调整. 这里只是最简单的model -> dict的方法,很多情况没有考虑到.

-(NSDictionary *)toDictionary{
    NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
    Class cls = [self class];
    while (cls != [NSObject class]) {
        unsigned int count = 0;
        objc_property_t *props = class_copyPropertyList(cls, &count);
        for (int i = 0; i < count; i++) {
            objc_property_t prop = props[i];
            NSString *propName = [NSString stringWithUTF8String:property_getName(prop)];
            if ([propName length] == 0) {
                continue;
            }
            id value = [self valueForKey:propName];
            if (value) {
                [parameters setValue:value forKey:propName];
                if (![NSJSONSerialization isValidJSONObject:parameters]) {
                    [parameters removeObjectForKey:propName];
                }
            }
        }
        if (props) {
            free(props);
        }
        cls = class_getSuperclass(cls);
    }
    return [parameters copy];
}

runtime进行model <-> json/dict 有许多非常棒的框架进行了很多优化, 例如 MJExtension, YYModel. 可以参考一下他们的源码. 当然这种比较简单的json/dict <-> model方式, 我们可以自己写.

iOS Runtime 对对象进行序列化与反序列化

序列化和反序列化的目的是将对象存储到文件的方法.

需要实现上面的功能, 需要具有序列化能力的类必须实现NSCoding协议的两个函数:

-(void) encodeWithCoder:(NSCoder *)encoder;

-(id) initWithCoder:(NSCoder *)decoder;

下面有一个实例:

-(instancetype)initWithCoder:(NSCoder *)aDecoder{
    NSLog(@"%s",__func__);
    Class cls = [self class];
    while (cls != [NSObject class]) {
        /*判断是自身类还是父类*/
        BOOL bIsSelfClass = (cls == [self class]);
        unsigned int iVarCount = 0;
        unsigned int propVarCount = 0;
        unsigned int sharedVarCount = 0;
        Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL;/*变量列表,含属性以及私有变量*/
        objc_property_t *propList = bIsSelfClass ? NULL : class_copyPropertyList(cls, &propVarCount);/*属性列表*/
        sharedVarCount = bIsSelfClass ? iVarCount : propVarCount;

        for (int i = 0; i < sharedVarCount; i++) {
            const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i));
            NSString *key = [NSString stringWithUTF8String:varName];
            id varValue = [aDecoder decodeObjectForKey:key];
            NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"];
            if (varValue && [filters containsObject:key] == NO) {
                [self setValue:varValue forKey:key];
            }
        }
        free(ivarList);
        free(propList);
        cls = class_getSuperclass(cls);
    }
    return self;
}

/*
 这里需要特别注意的是:
 编解码的范围不能仅仅是自身类的变量,还应当把除NSObject类外的所有层级父类的属性变量也进行编解码!

 由此可见,这几乎是个纯体力活。而runtime在遍历变量这件事情上能为我们提供什么帮助呢?

 我们可以通过runtime在运行时获取自身类的所有变量进行编解码;然后对父类进行递归,获取除NSObject外每个层级父类的属性(非私有变量),进行编解码。
 */
- (void)encodeWithCoder:(NSCoder *)coder
{
    NSLog(@"%s",__func__);
    Class cls = [self class];
    while (cls != [NSObject class]) {
        /*判断是自身类还是父类*/
        BOOL bIsSelfClass = (cls == [self class]);
        unsigned int iVarCount = 0;
        unsigned int propVarCount = 0;
        unsigned int sharedVarCount = 0;
        Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL;/*变量列表,含属性以及私有变量*/
        objc_property_t *propList = bIsSelfClass ? NULL : class_copyPropertyList(cls, &propVarCount);/*属性列表*/
        sharedVarCount = bIsSelfClass ? iVarCount : propVarCount;

        for (int i = 0; i < sharedVarCount; i++) {
            const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i));
            NSString *key = [NSString stringWithUTF8String:varName];
            /*valueForKey只能获取本类所有变量以及所有层级父类的属性,不包含任何父类的私有变量(会崩溃)*/
            id varValue = [self valueForKey:key];
            NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"];
            if (varValue && [filters containsObject:key] == NO) {
                [coder encodeObject:varValue forKey:key];
            }
        }
        free(ivarList);
        free(propList);
        cls = class_getSuperclass(cls);
    }
}

相关Demo: https://github.com/brownfeng/RuntimeDemo.git

参考资料

http://www.cocoachina.com/ios/20170424/19102.html
https://www.jianshu.com/p/07b6c4a40a90
https://www.jianshu.com/p/fed1dcb1ac9f
https://www.jianshu.com/p/0497afdad36d

上一篇下一篇

猜你喜欢

热点阅读