runtime的理解(二)

2018-06-14  本文已影响0人  __Gavin__

主要内容

交换方法

  交换自定义方法 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) 给要添加的属性添加关联,参数分别表示如下:

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 值获取关联对象,参数分别表示如下:

代码示例

  以给 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) 参数释义:

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 在调用存取方法之后总调用
}
上一篇下一篇

猜你喜欢

热点阅读