Runtime总结

2019-01-07  本文已影响5人  CowboyBebop

参考: Objc Runtime 总结
runtime

一, runtime 关联属性

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

object:与谁关联,通常是传self
key:唯一键,在获取值时通过该键获取,通常是使用static const void *来声明
value:关联所设置的值
policy:内存管理策略,比如使用copy

id objc_getAssociatedObject(id object, const void *key)

object:与谁关联,通常是传self,在设置关联时所指定的与哪个对象关联的那个对象
key:唯一键,在设置关联时所指定的键

objc_removeAssociatedObjects

二, runtime在模型与字典互转当中的应用

for 循环字典,根据key 生成set 方法,类似 setName: , 然后调用 ((void (*)(id, SEL, id))objc_msgSend)(self, setter, value) 方法,调用set方式,就可以给模型的属性赋值了。

//开头大写,其余小写
NSString *propertySetter = key.capitalizedString;
//拼接set方法
propertySetter = [NSString stringWithFormat:@"set%@:", propertySetter];
// 生成setter方法
SEL setter = NSSelectorFromString(propertySetter);
if ([self respondsToSelector:setter]) {
    if (setter != nil) {
     //利用运行时调用set 方法
      ((void (*)(id, SEL, id))objc_msgSend)(self, setter, value);
     }
}

还有种方式,通过KVC 方式也可以实现,具体步骤 :根据 class_copyIvarList方法获取该类的成员变量列表, 然后for 循环 根据 Ivar 作为key 去字典中取出value ,最后根据setValue:forKey 去设置。

unsigned int count;
// 获取类中的所有成员属性
Ivar *ivarList = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
      // 根据角标,从数组取出对应的成员属性
      Ivar ivar = ivarList[i];
      // 获取成员属性名
      NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
      // 处理成员属性名->字典中的key
      // 从第一个角标开始截取,去掉下划线
      NSString *key = [name substringFromIndex:1];
      // 根据成员属性名去字典中查找对应的value
      id value = dict[key];
      if (value) { // 有值,才需要给模型的属性赋值
          // 利用KVC给模型中的属性赋值
          [objc setValue:value forKey:key];
      }
}

第一种思路:先获取属性列表,然后根据单个属性生成getter 方法,然后运用 objc_msgSend q去调用getter 方法,获取到属性的值,添加到字典中去。

    unsigned int outCount = 0;
    //获取属性列表
    objc_property_t* properties = class_copyPropertyList([self class], &outCount);
    if (outCount != 0) {
        NSMutableDictionary * dict = [[NSMutableDictionary alloc] initWithCapacity:outCount];
        for (unsigned int i = 0; i < outCount; ++i) {
            //获取单个属性
            objc_property_t property = properties[i];
            //获取属性名称
            const void * propertyName = property_getName(property);
            //获取getter 方法
            SEL getter = sel_registerName(propertyName);
            if ([self respondsToSelector:getter]) {
                //调用getter 方法,获取value
                id value = ((id (*) (id,SEL)) objc_msgSend)(self,getter);
                if (value) {
                    NSString * key = [NSString stringWithUTF8String:propertyName];
                    [dict setObject:value forKey:key];
                }
            }
        }
        return dict;
    }

第二种思路:生成getter 方法的方式差不多,调用getter 方法不再是通过objc_msgSend,而是通过 NSInvocation ,NSInvocation 可以指定某个 类去掉用某个方法,具体代码如下。

    unsigned int outCount = 0;
    //获取属性列表
    objc_property_t* properties = class_copyPropertyList([self class], &outCount);
    if (outCount != 0) {
        NSMutableDictionary * dict = [[NSMutableDictionary alloc] initWithCapacity:outCount];
        for (unsigned int i = 0; i < outCount; ++i) {
            //获取单个属性
            objc_property_t property = properties[i];
            //获取属性名称
            const void * propertyName = property_getName(property);
            NSString * key = [NSString stringWithUTF8String:propertyName];
            //获取getter 方法
            SEL getter = NSSelectorFromString(key);
            if ([self respondsToSelector:getter]) {
                //获取方法签名
                NSMethodSignature * signature= [NSMethodSignature methodSignatureForSelector:getter];
                // 根据方法签名获取NSInvocation对象
                NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:signature];
                // 设置target
                [invocation setTarget:self];
                // 设置selector
                [invocation setSelector:getter];
                //调用
                [invocation invoke];
                __unsafe_unretained NSObject * propertyValue = nil;
                //返回值
                [invocation getReturnValue:&propertyValue];
                if (propertyValue != nil) {
                    [dict setObject:propertyValue forKey:key];
                }
            }
        }
        return dict;
    }

三,消息转发机制

#import <Foundation/Foundation.h>
int main(){
    @autoreleasepool {
        NSString* site = [[NSString alloc] initWithUTF8String:"starming"];
    }
    return 0;
}

上面的main.m 的文件,我们可以用Clang 编译的知识,通过命令 clang -rewrite-objc main.m ,就能得到main.cpp 文件,打开后就会发现转换后的C 代码,其中一个重要的函数就是 objc_msgSend。

int main(){
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSString* site = ((NSString * _Nullable (*)(id, SEL, const char * _Nonnull))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("initWithUTF8String:"), (const char *)"starming");

    }
    return 0;
}

我们先来了解下objc_msgSend 的调用的检测过程:

  • 1, 检测这个selector 是否要忽略。
  • 2, 检测这个target 是不是nil 对象,nil 对象执行任何一个方法不会crash 是因为会被忽略掉。
  • 3, 查找这个类的IMP,也就是方法实现。先从方法缓存列表cache中查找,若找到则跳到对应的函数去执行;若找不到,则查找方法分发表。如果分发表找不到就到父类的分发表去找,直到找到或者查找到NSObject根类为止。
  • 4, 前三步都找不到,则开始进入动态方法解析了

其流程是这样的:
第一步:+ (BOOL)resolveInstanceMethod:(SEL)sel实现方法,指定是否动态添加方法。若返回NO,则进入下一步,若返回YES,则通过class_addMethod 函数动态地添加方法,消息得到处理,此流程完毕。
第二步:在第一步返回的是NO时,就会进入
- (id)forwardingTargetForSelector:(SEL)aSelector 方法,这是运行时给我们的第二次机会,用于指定哪个对象响应这个selector。不能指定为self。若返回nil,表示没有响应者,则会进入第三步。若返回某个对象,则会调用该对象的方法。
第三步:若第二步返回的是nil,则我们首先要通过- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector指定方法签名,若返回nil,则表示不处理。若返回方法签名,则会进入下一步。
第四步:当第三步返回方法方法签名后,就会调用- (void)forwardInvocation:(NSInvocation *)anInvocation方法,我们可以通过anInvocation对象做很多处理,比如修改实现方法,修改响应对象等
第五步:若没有实现- (void)forwardInvocation:(NSInvocation *)anInvocation方法,那么会进入- (void)doesNotRecognizeSelector:(SEL)aSelector方法。若我们没有实现这个方法,那么就会crash,然后提示找不到响应的方法。到此,动态解析的流程就结束了。

//第一步:在调用某个对象的某个方法找不到的时候,会先调用这个方法,允许我动态添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
        class_addMethod(self.class, sel, (IMP)(eat), "v:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
void eat(id self, SEL cmd){
    NSLog(@"%@ is eat",self);
}
@interface ZDPig ()
{
    ZDDog * _dog;
}

@end

@implementation ZDPig

- (instancetype)init
{
    self = [super init];
    if (self) {
        _dog = [ZDDog new];
    }
    return self;
}

//第一步,我们不动态添加方法,返回NO
+(BOOL)resolveInstanceMethod:(SEL)sel
{
    return NO;
}
//第二步,重定向接受者
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(eat)) {
        //要确定 _dog  要有这个方法,不然也会crash 的
        return _dog;
    }
    return [super forwardingTargetForSelector:aSelector];
}
// 第一步,我们不动态添加方法,返回NO
+(BOOL)resolveInstanceMethod:(SEL)sel
{
    return NO;
}
// 第二步,备选提供响应aSelector的对象,我们不备选,因此设置为nil,就会进入第三步
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return nil;
}
//必须重写这个方法,消息转发使用这个方法获得的信息创建NSInvocation对象。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if ([ZDDog instancesRespondToSelector:aSelector]) {
        return [ZDDog instanceMethodSignatureForSelector:aSelector];
    }
    return nil;
}
//这一步是最后机会将消息转发给其它对象,对象会将未处理的消息相关的selector,target和参数都封装在anInvocation中。forwardInvocation:像未知消息分发中心,将未知消息转发给其它对象。注意的是forwardInvocation:方法只有在消息接收对象无法正常响应消息时才被调用。
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    [anInvocation invokeWithTarget:[ZDDog new]];
}

四,Method Swizzling

Method 的相关函数

// 调用指定方法的实现,返回的是方法实现时的返回,参数receiver不能为空,这个比method_getImplementation和method_getName快
id method_invoke ( id receiver, Method m, ... );
// 调用返回一个数据结构的方法的实现
void method_invoke_stret ( id receiver, Method m, ... );
// 获取方法名,希望获得方法明的C字符串,使用sel_getName(method_getName(method))
SEL method_getName ( Method m );
// 返回方法的实现
IMP method_getImplementation ( Method m );
// 获取描述方法参数和返回值类型的字符串
const char * method_getTypeEncoding ( Method m );
// 获取方法的返回值类型的字符串
char * method_copyReturnType ( Method m );
// 获取方法的指定位置参数的类型字符串
char * method_copyArgumentType ( Method m, unsigned int index );
// 通过引用返回方法的返回值类型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );
// 返回方法的参数的个数
unsigned int method_getNumberOfArguments ( Method m );
// 通过引用返回方法指定位置参数的类型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
// 返回指定方法的方法描述结构体
struct objc_method_description * method_getDescription ( Method m );
// 设置方法的实现
IMP method_setImplementation ( Method m, IMP imp );
// 交换两个方法的实现
void method_exchangeImplementations ( Method m1, Method m2 );

关于 Method Swizzling 已经有很多文章了,这里就不多说了,推荐一个第三库 RSSwizzle介绍RSSwizzle 的资料

上一篇下一篇

猜你喜欢

热点阅读