美文网首页
runtime的理解(二)

runtime的理解(二)

作者: __Gavin__ | 来源:发表于2018-06-14 18:13 被阅读0次

    主要内容

    • 利用 runtime 交换方法
    • 利用 runtime 动态添加方法
    • 利用 runtime 动态添加属性
    • 利用 runtime 字典转模型

    交换方法

      交换自定义方法 ditImangeNamed: 和系统方法 imageNamed: 的implementation。可以在不大量修改项目代码的情况下,添加所需的功能。注:ditImangeNamed: 的方法实现里不要再掉用 imageNamed: 方法,否则会引起循环引用。

    @implementation UIImage (DITImage)
    
    + (nullable UIImage *)ditImangeNamed:(NSString *)name
    {
        UIImage *image = [UIImage ditImangeNamed:name];
        if (!image)
        {
            NSLog(@"资源图片不存在");
        }
        
        return image;
    }
    
    + (void)load
    {
        Method imageNamedMethod = class_getClassMethod([UIImage class], @selector(imageNamed:));
        Method ditImageNamedMethod = class_getClassMethod([UIImage class], @selector(ditImangeNamed:));
        method_exchangeImplementations(imageNamedMethod, ditImageNamedMethod);
    }
    
    @end
    

    动态添加方法

      即是 runtime的理解(一) 的消息转发,动态解析部分,提供新的方法实现来接收消息并处理。

    - (void)testDynamicMethod
    {
        [self performSelector:@selector(justTest:) withObject:@"testStr"];
    //    [DITRuntimeViewController performSelector:@selector(justClassTest:) withObject:@"classTestStr"];
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        if (sel == @selector(justTest:))
        {
            class_addMethod([self class], sel, (IMP)justTestMethod, "v@:@");
            return YES;
        }
        
        return [super resolveInstanceMethod:sel];
    }
    
    void justTestMethod(id self, SEL _cmd, id str) {
        NSLog(@"%@的%@方法动态实现了,参数为:%@", self, NSStringFromSelector(_cmd), str);
    }
    

    动态添加属性

    objc_setAssociatedObject()

      方法全称: objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy) 给要添加的属性添加关联,参数分别表示如下:

    • id object : 给哪个对象添加属性。
    • void * key : 属性名,作为被关联属性对象的标示。
    • id value : 被关联的属性对象。
    • objc_AssociationPolicy policy : 策略,属性以什么形式保存。有以下几种:
    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
        OBJC_ASSOCIATION_ASSIGN = 0,  // 指定一个弱引用相关联的对象
        OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关对象的强引用,非原子性
        OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  // 指定相关的对象被复制,非原子性
        OBJC_ASSOCIATION_RETAIN = 01401,  // 指定相关对象的强引用,原子性
        OBJC_ASSOCIATION_COPY = 01403     // 指定相关的对象被复制,原子性   
    };
    

    objc_getAssociatedObject()

      方法全称: objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key),用来通过 key 值获取关联对象,参数分别表示如下:

    • id object : 获取哪个对象里面的关联的属性。
    • void * id key : 属性名,被关联对象的标示。

    代码示例

      以给 UITextView 的分类添加 placeholder 属性为例,代码如下:

    /* UITextView+DITPlaceholder.h */
    @interface UITextView (DITPlaceholder)
    @property (nonatomic, copy) NSString *placeholder;
    @property (nonatomic, weak) UILabel *placeholderLabel;
    @end
    
    /* UITextView+DITPlaceholder.m */
    - (UILabel *)placeholderLabel
    {
        UILabel *tempLabel = objc_getAssociatedObject(self, @"placeholderLabel");
        
        if (!tempLabel)
        {
            tempLabel = [[UILabel alloc] init];
            tempLabel.font = self.font;
            tempLabel.textColor = [UIColor grayColor];
            tempLabel.textAlignment = NSTextAlignmentLeft;
            tempLabel.numberOfLines = 0;
            
            [self addSubview:tempLabel];
            
            objc_setAssociatedObject(self, @"placeholderLabel", tempLabel, OBJC_ASSOCIATION_RETAIN);
        }
        
        return tempLabel;
    }
    

    字典转模型

    class_copyIvarList()

      方法 class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) 参数释义:

    • __unsafe_unretained Class cls: 获取哪个类的成员属性列表。
    • unsigned int * outCount : 无符号int型指针,传入一个无符号整型变量的地址,传出来的值为成员属性总数。
    • (返回值:Ivar * ) : 返回的是一个Ivar类型的指针 。指针默认指向的是数组的第0个元素,指针+1会向高地址移动一个Ivar单位的字节,也就是指向第一个元素。Ivar表示成员属性。

    ivar_getName()

      方法 ivar_getName(Ivar _Nonnull v) 通过上个方法获得的成员属性 Ivar ,传入此方法,获得成员属性名的C语言字符串。

    代码示例

      带二级转换的简单的字典转模型。

    /* DITRuntimeViewController.m */
    #pragma mark - 字典转模型
    - (void)testModelWithDict
    {
        DITForwardingTest *forwardingTestModel = [DITForwardingTest modelWithKeyValues:@{
                                                @"testName": @"testName123",
                                                @"subModel": @{@"testSubName": @"testSubName123"}
                                                }];
        NSLog(@"%@", forwardingTestModel.mj_keyValues);
    }
    
    /* NSObject+DITKeyValues.m */
    + (instancetype)modelWithKeyValues:(NSDictionary *)dict
    {
        id obj = [[self alloc] init];
        
        unsigned int count = 0;
        Ivar *ivarList = class_copyIvarList(self, &count);
        
        for (int i = 0; i < count; i++)
        {
            Ivar ivar = ivarList[i];
            
            NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
            
            NSString *key = [propertyName substringFromIndex:1];
            
            NSString *value = dict[key];
            NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; // @"@\"NSString\""
            
            if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"])
            {
                // 二级字典转模型
                propertyType = [propertyType substringWithRange:NSMakeRange(2, propertyType.length-3)];
                
                Class modelClass = NSClassFromString(propertyType);
                value = [modelClass modelWithKeyValues:(NSDictionary *)value];
            }
            
            if (value)
            {
                [obj setValue:value forKey:key];
            }
        }
        
        return obj;
    }
    
    /* DITSubModel.h */
    @interface DITSubModel : NSObject
    @property (nonatomic, copy) NSString *testSubName;
    @end
    
    /* DITForwardingTest.h */
    @interface DITForwardingTest : NSObject
    @property (nonatomic, copy) NSString *testName;
    @property (nonatomic, strong) DITSubModel *subModel;
    @end
    

    KVO实现

      KVO 的实现用到了 runtime 的方法IMP替换,当观察对象 Person 时,KVO机制动态创建一个名为:NSKVONotifying_ Person 的新类,该类继承自对象 Person 的本类,且 KVO 为 NSKVONotifying_ Person 重写要观察属性的 setter 方法,setter 方法会负责在实现 setter 之前和之后,通知所有观察对象属性值的更改情况。
      KVO 的键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangeValueForKey: ,在存取数值的前后分别调用这 2 个方法:被观察属性发生改变之前, willChangeValueForKey: 被调用,通知系统该 keyPath 的属性值即将变更;当改变发生后, didChangeValueForKey: 被调用,通知系统该keyPath 的属性值已经变更;之后, observeValueForKey:ofObject:change:context:也会被调用。代码如下:

    - (void)setName:(NSString *)newName
     { 
          [self willChangeValueForKey:@"name"];    // KVO 在调用存取方法之前总调用 
          [super setValue:newName forKey:@"name"]; // 调用父类的存取方法 
          [self didChangeValueForKey:@"name"];     // KVO 在调用存取方法之后总调用
    }
    

    相关文章

      网友评论

          本文标题:runtime的理解(二)

          本文链接:https://www.haomeiwen.com/subject/iwsseftx.html