RunTime

作者: 小笨憨 | 来源:发表于2017-04-13 14:58 被阅读0次

    runtime:Objective-C是动态语言,它将很多静态语言在编译和链接时做的事放到了运行时,这个运行时系统就是runtime

    runtime其实就是一个库,它基本上是用C和汇编写的一套API,这个库使C语言有了面向对象的能力。

    静态语言:在编译的时候会决定调用哪个函数。

    动态语言(OC):在运行的时候根据函数的名称找到对应的函数来调用。

    isa:OC中,类和类的实例在本质上没有区别,都是对象,任何对象都有isa指针,它指向类或者元类。

    isa:是一个Class类型的指针。每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针,指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass)。根元类的isa指针指向本身,这样形成了一个封闭的内循环。

    SELSEL(选择器)是方法的selector的指针。方法的selector表示运行时方法的名字。OC在编译时,会依据每一个方法的名字、参数,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL

    IMPIMP是一个函数指针,指向方法最终实现的首地址。SEL就是为了查找方法的最终实现IMP

    Method:用于表示类定义中的方法,它是结构体中包含一个SELIMP,相当于在SELIMP之间做了一个映射。

    消息机制:任何方法的调用本质就是发送一个消息。编译器会将消息表达式[receiver message]转化为一个消息函数objc_msgSend(receiver,selector)

    Runtime的使用:获取属性列表,获取成员变量列表,获得方法列表,获取协议列表,方法交换(黑魔法),动态的添加方法,调用私有方法,为分类添加属性。

    一、什么是Runtime运行时

    runtime主要做了两件事情:

    1.封装:runtime把对象用C语言的结构体来表示,方法用C语言的函数来表示。这些结构体和函数被runtime封装后,我们就可以在程序运行的时候,对类/对象/方法进行操作。

    2.寻找方法的最终执行:当执行[receiver message]的时候,相当于想receiver发送一条messageruntime会根据receiver能否处理这条message,从而做出不同的反应。

    在OC中,类是用Class来表示的,而Class实际上是一个指向objc_class结构体的指针。
    struct objc_class{
    
       Class isa OBJC_ISA_AVAILABILITY; // isa指针
    #if !__OBJC2__
       
       Class super_class                        OBJC2_UNAVAILABLE; // 父类
       
       const char *name                         OBJC2_UNAVAILABLE; // 类名
       
       long version                             OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
    
       long info                                OBJC2_UNAVAILABLE; // 类信息
    
       long instance_size                       OBJC2_UNAVAILABLE; // 该类的实例变量大小
    
       struct objc_ivar_list *ivars             OBJC2_UNAVAILABLE; // 该类的成员变量链表
    
       struct objc_method_list **methodLists    OBJC2_UNAVAILABLE; // 方法定义的链表
        
       struct objc_cache *cache                 OBJC2_UNAVAILABLE; // 方法缓存
    
       struct objc_protocol_list *protocols     OBJC2_UNAVAILABLE; // 协议链表
    #endif
    } OBJC2_UNAVAILABLE;
    

    Cache用于缓存最近使用的方法。一个类只有一部分方法是常用的,每次调用一个方法之后,这个方法就被缓存到cache中,下次调用时runtime会先在cache中查找,如果cache中没有,才会去methodList中查找。有了cache,经常用到的方法的调用效率就提高了!

    你只要记住,runtime其实就是一个库,它是一套API,这个库使C语言有了面向对象的能力。我们可以运用runtime这个库里面的各种方法,在程序运行时的时候对类/实例对象/变量/属性/方法等进行各种操作。

    二、 什么是iso指针

    在解释isa之前,你需要知道,在Objective-C中,所有的类自身也是一个对象,我们可以向这个对象发送消息(调用类方法)。

    先来看一下runtime中实例对象的结构体objc_object

    struct objc_object {
    
      Class isa OBJC_ISA_AVAILABILITY;
    };
    

    从结构体中可以看到,这个结构体只包含一个指向其类的isa指针。

    isa指针的作用:当我们向一个对象发送消息时,runtime会根据这个对象的isa指针找到这个对象所属的类,在这个类的方法列表及父类的方法列表中,寻找与消息对应的selector指向的方法,找到后就运行这个方法。

    要彻底理解isa,还需要了解一下元类的概念。

    (meta-class)元类中存储着一个类的所有类方法。

    向一个对象发送消息时,runtime会在这个对象所属的类的方法列表中查找方法;
    向一个类发送消息时,会在这个类的meta-class(元类)的方法列表中查找。

    meta-class是一个类,也可以向它发送消息,那么它的isa又是指向什么呢?为了不让这个结构无限延伸下去,Objective-C的设计者让所有的meta-classisa指向基类(NSObject)meta-class,而基类的meta-classisa指针是指向自己(NSObject)

    三、 什么是SEL,IMP,Method

    SEL

    SEL又叫选择器,是方法的selector的指针。

    typedef struct objc_selector *SEL;
    

    方法的selector用于表示运行时方法的名字。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL

    两个类之间,无论它们是父子关系,还是没有关系,只要方法名相同,那么方法的SEL就是一样的,每一个方法都对应着一个SEL,所以在Objective-C同一个类中,不能存在两个同名的方法,即使参数类型不同也不行。

    SEL是一个指向方法的指针,是根据方法名hash化了的一个字符串,而对于字符串的比较仅仅需要比较他们的地址就可以了,所以速度上非常优秀,它的存在只是为了加快方法的查询速度。

    不同的类可以拥有相同的selector,不同类的实例对象执行相同的selector时,会在格子的方法列表中取根据selector寻找对应的IMPSEL就是为了查找方法的最终实现IMP

    IMP

    IMP实际上是一个函数指针,指向方法实现的首地址。代表了方法的最终实现。

    id (*IMP)(id,SEL,…)
    

    第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针),第二个参数是方法选择器(selector),省略号是方法的参数。

    每个方法对应唯一的SEL,通过SEL快速准确的获得对应的IMP,取得IMP后,就获得了执行这个方法代码了。

    Method

    Method是用于表示类的方法。

    typedef struct objc_method *Method;
    
    struct objc_method {
    
        SEL method_name             OBJC2_UNAVAILABLE; // 方法名
       
        char *method_types          OBJC2_UNAVAILABLE; 
    
        IMP method_imp          OBJC2_UNAVAILABLE; // 方法实现
    }
    

    Method结构体中包含一个SELIMP,实际上相当于在SELIMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。

    四、 什么是消息机制

    当执行了[receiver message]的时候,相当于向receiver发送一条messageruntime会根据receiver能否处理这条message,从而做出不同的反应。

    方法(消息机制)的调用流程

    消息直到运行时才绑定到方法的实现上。编译器会将消息表达式[receiver message]转化为一个消息函数,即objc_msgSend(receiver,selector)

    objc_msgSend(receiver,selector)
    // 如果消息中还有其它参数
    objc_msgSend(receiver,selector,arg1,arg2,…)
    

    objc_msgSend做了如下事情:

    1.通过对象的isa指针获取类的结构体。
    2.在结构体的方法表里查找方法的selector
    3.如果没有找到selector,则通过objc_msgSend结构体中指向父类的指针找到父类,并在父类的方法表里找到方法的selector
    4.依次会一直找到NSObject
    5.一旦找到selector,就会获取到方法实现IMP
    6.传入相应的参数来执行方法的具体实现。
    7.如果最终没有定位到selector,就会走消息转发流程。

    消息转发机制

    [receiver message]的方式调用方法,如果receiver无法响应message,编译器会报错。但如果是以performSelecror来调用,则需要等到运行时才能确定object是否能接收message消息。如果不能,则程序崩溃。

    当我们不能确定一个对象是否能接收某个消息时,会先调用respondsToSelector:来判断一下

        if ([self respondsToselector:@selector(doSomething)]) {
    
             [self performSelector:@selector(doSomething)];
        }
    

    如果不使用respondsToSelector:来判断,那么这就可以用到“消息转发”机制。

    当对象无法接收消息,就会启动消息转发机制,通过这一机制,告诉对象如何处理未知的消息。
    

    这样就就可以采取一些措施,让程序执行特定的逻辑,从而避免崩溃。措施分为三个步骤。

    1.动态方法解析

    对象接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)。

    在这个方法中,我们有机会为该未知消息新增一个“处理方法”。使用该“处理房啊”的前提是已经实现,只需要在运行时通过class_addMethod函数,动态的添加到类里面就可以了。代码如下。

        void functionForMethod1(id self,SEL _cmd) {
        
            NSLog(@“%@,%p”,self,_cmd);
        }
    
        + (BOOL)resolveInstanceMethod:(SEL)sel {
    
            NSString *selectorString = NSStringFromSelector(self);
            
            if ([selectorString isEqualToString:@“method1”]) {
    
                class_addMethod(self.class,@selector(method1),(IMP)functionForMethod1,”@:”);
            }
            
            return [super resolveInstanceMethod:sel];
        }
    

    2. 备用接收者

    如果在上一步无法处理消息,则runtime会继续调下面的方法。

    - (id)forwardingTargetForSelector:(SEL)aSelector
    

    如果这个方法返回一个对象,则这个对象会作为消息的新接收者。注意这个对象不能是self自身,否则就是出现无限循环。如果没有指定对象来处理aSelector,则应该return [super forwardingTargetForSelector:aSelector]

    但是我们只将消息转发到另一个能处理该消息的对象上,无法对消息进行处理,例如操作消息的参数和返回值。

        @interface RuntimeTest() {
    
            RuntimeMethod *_TestMethod;
        }
    
        @implementation RuntimeTest
    
        - (instancetype)init {
    
            self = [super init];
            
            if (self != nil) {
    
                _TestMethod = [[TestMethod alloc] init];
            }
            return self;
        }
    
        - (id)forwardingTargetForSelector:(SEL)aSelector {
    
            NSLog(@“forwardingTargetForSelector”);
    
            NSString *selectorString = NSStringFromSelector(aSelector);
            
            // 将消息转发给_TestMethod来处理
            if ([selectorString isEqualToString:@“method2”]) {
    
                return _TestMethod;
            }
            return [super forwardingTargetForSelector:aSelector];
        }
    

    3.完整消息转发

    如果在上一步还是不能处理未知消息,则唯一能做的就是启用完整的消息转发机制。此时会调用以下方法:

    - (void)forwardInvocation:(NSInvocation *)anInvocation {
    
        if ([RuntimeMethod instancesRespondToSelector:anInvocation.selector]) {
    
            [anInvocation invakeWithTarget:_TestMethod];
        }
    }
    

    这是最后一次机会将消息转发给其它对象。创建一个表示消息的NSInvocation对象,把与消息的有关全部细节封装在anInvocation中,包括selector,目标(target)和参数。在forwardInvocation方法中将消息转发给其它对象。

    forwardInvocation:方法的实现有两个任务:
    1.定位可以响应封装在anInvocation中的消息的对象。
    
    2.使用anInvocation作为参数,将消息发送到选中的对象。anInvocation将会保留调用结果,runtime会提取这一结果并发送到消息的原始发送者。
    

    在这个方法中我们可以实现一些更复杂的功能,我们可以对消息的内容进行修改。另外,若发现消息不应由本类处理,则应调用父类的同名方法,以便集成体系中的每个类都有机会处理。

    另外,必须重写下面的方法:

        - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    

    消息转发机制从这个方法中获取消息来创建NSInvocation对象。完整的示例如下:

        - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
            NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
            
            if (!signature) {
    
                if ([RuntimeMethod instanceRespondToSelector:aSelector]) {
                    
                    signature = [RuntimeMethod instanceMethodSignatureForSelector:aSelector];
                }
            }
            return signature;
        }
    
        - (void)forwardInvocation:(NSInvocation *)anInvocation {
    
            if ([RuntimeMethod instanceRespondToSelector:anInvocation.selector]) {
    
                [anInvocation invokeWithTarget:_TestMethod];
            }
        }
    

    NSObjectforwardInvocation方法只是调用了doesNotRecognizeSelector方法,它不会转发任何消息。如果不在以上所述的三个步骤中处理未知消息,则会引发异常。

    forwardInvocation就像一个未知消息的分发中心,将这些未知的消息转发给其它对象。或者也可以像一个运输站一样将所有未知消息都发送给同一个接收对象,取决于具体的实现。

    消息的转发机制可以用下图来帮助理解。

    五、 runtime的使用

    1. 获取属性列表
    2. 获取成员变量列表
    3. 获取方法列表
    4. 获取协议列表
    5. 方法交换(黑魔法)
    6. 添加方法
    7. 调用私有方法
    8. 为分类添加属性

    1.获取属性列表

    运用class_copyPropertyList方法来获得属性列表,遍历把属性加入数组中,最终返回此数组。其中[self dictionaryWithProperty:properties[i]]方法是用来拿到属性的描述,例如copyreadonlyNSString等信息。

     /** 属性列表 */
     - (NSArray *)propertiesInfo
     {
        return [[self class] propertiesInfo];
     }
     + (NSArray *)propertiesInfo
        {
            NSMutableArray *propertieArray = [NSMutableArray array];
            unsigned int propertyCount;
            objc_property_t *properties = class_copyPropertyList([self class],&propertyCount);
            
            for (int index = 0; index < propertyCount; index++)
            {
                [propertieArray addObject:({
            
                    NSDictionary *dictionary = [self dictionaryWithProperty:properties[index]];
    
                    dictionary;
                })];
            }
    
            free(properties);
    
            return propertieArray;
        }
    
    // 格式化之后的属性列表
        + (NSArray *)propertiesWithCodeFormat
        {
            NSMutableArray  *array = [NSMutableArray array];
    
            NSArray *properties = [[self class] propertiesInfo];
            
            for (NSDictionary *item in properties)
            {
                NSMutableString *format = ({
    
                    NSMutableString *formatString = [NSMutableString stringWithFormat:@“property ”];
                    // attribute
                    NSArray *attribute = [item objectForKey:@“attribute”];
                    attribute = [attribute sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
            
                        return [obj1 compare:obj2 options:NSNumericSearch];
                    }];
    
                    if (attribute && attribute.count > 0)
                    {
                        NSString *attributeStr = [NSString stringWithFormat:@“(%@)”,[attribute componentsJoinedByString:@“, ”]];
                        [formatString appendString:attributeStr];
                    }
    
                    // type
                    NSString *type = [item objectForKey:@“type”];
                    if (type) 
                    {
                        [formatString appendString:@“ ”];
                        [formatString appednString:type];
                    }
                
                    // name
                    NSString *name = [item objectForKey:@“name”];
                    if (name)
                    {   
                        [formatString appendString:@“ ”];
                        [formatString appendString:name];
                        [formatString appendString:@“;”];
                    } 
    
                    formatString;
                });
    
                [array addObject:format];
            }
            
            return array;
        }
    
    
        + (NSDictionary *)dictionaryWithProperty:(objc_property_t)property
        {
            NSMutableDictionary *result = [NSMutableDictionary dictionary];
    
            // name
            NSString *propertyName  = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
            [result setObject:propertyName forKey:@“name”];
    
            // attribute
            
            NSMutableDictionary *attributeDictionary = ({
    
                [NSMutableDictionary dictionary];
    
                unsigned int attributeCount;
                objc_property_attribute_t *attrs = property_copyAttributeList(property,&attributeCount);
    
                for (int index = 0; index < attributeCount; index++) 
                {
                    NSString *name = [NSString stringWithCString:attrs[index].name encoding:NSUTF8StringEncoding];
                    NSString *value = [NSString stringWithCString:attrs[index].value encoding:NSUTF8StringEncoding];
                }
    
                free(attrs);
    
                dictionary;
            });
    
            NSMutableArray *attributeArray  = [NSMutableArray array];
    
            // R
            if ([attributeDictionary objectForKey:@“R”]) 
            {
                [attributeArray addObject:@“readonly”];
            }
            // C
            if ([attributeDictionary objectForKey:@“C”])
            {
                [attributeArray addObject:@“copy”];
            }
            // &
            if ([attributeDictionary objectForKey:@“&”])
            {
                [attributeArray addObject:@“strong”];
            }
            // N
            if ([attributeDictionary objectForKey:@“N”])
            {
                [attributeArray addObject:@“nonatomic”];    
            } else 
            {
                [attributeArray addObject:@“atomic”];
            }
            // G<name>
            if ([attributeDictionary objectForKey:@“G”]) 
            {
                [attributeArray addObject:[NSString stringWithFormat:@“getter=%@”,[attributeDictionary objectForKey:@“G”]]];
            }
            // S<name>
            if ([attributeDictionary objectForKey:@“S”])
            {
                [attributeArray addObject:[NSString stringWithFormat:@“setter=%@”,[attributeDictionary objectForKey:@“G”]]];
            }
            // D
            if ([attributeDictionary objectForKey:@“D”])
            {
                [result setObject:[NSNumber numberWithBool:YES] forKey:@“isDynamic”];
            } else
            {
                [result setObject:[NSNumber numberWithBool:NO] forKey:@“isDynamic”];
            }   
            // W
            if ([attributeDictionary objectForKey:@“W”]) 
            {
                [attributeArray addObject:@“weak”];
            } 
            // P
            if ([attributeArray objectForKey:@“P”])
            {
                
            }
            // T
            if ([attributeDictionary objectForKey:@“T”])
            {
                NSDictionary *typeDic   = @{
                            @“c”:@“char”,
                            @“i”:@“int”,
                            @“s”:@“short”,
                            @“l”:@“long”,
                            @“q”:@“long long”,
                            @“C”:@“unsigned char”,
                            @“I”:@“unsigned int”,
                            @“S”:@“unsigned short”,
                            @“L”:@“unsigned long”,
                            @“Q”:@“unsigned long long”,
                            @“f”:@“float”,
                            @“d”:@“double”,
                            @“B”:@“BOOL”,
                            @“v”:@“void”,
                            @“*”:@“char *”,
                            @“@”:@“id”,
                            @“#”:@“Class”,
                            @“:”:@“SEL”,
                                };
                // TODO:An array
                NSString *key   = [attributeDictionary objectForKey:@“T”];
    
                id type_str = [typeDic objectForKey:key];
    
                if (type_str == nil) 
                {
                    if ([[key substringToIndex:1] isEqualToString:@“@”] && [key rangeOfString:@“?”].location == NSNotFound)
                    {
                        type_str = [[key substringWithRange:NSMakeRange(2,key.length - 3)] stringByAppendingString:@“*”] ;
                    } 
                    else if ([[key substringToIndex:1] isEqualToString:@“^”])
                    {
                        id str = [typeDic objectForKey:[key substringFromIndex:1]];
                        if (str) 
                        {
                            type_str = [NSString stringWithFormat:@“%@ *”,str];
                        }
                    }
                    else 
                    {
                        type_str    = @“unknow”;
                    }
                }
                
                [result setObject:type_str forKey:@“type”];
            }
            [result setObject:attributeArray forKey:@“attribute”];
            
            return result;
        }
    

    2.获取成员变量列表

    运用class_copyIvarList方法来获得变量列表,通过遍历把变量加入到数组中,最终返回此数组。其中[[self class] decodeType:ivar_getTypeEncoding(ivars[i])]方法是用来拿到变量的类型,例如char,int,unsigned long 等信息。

    /** 成员变量列表 */
    - (NSArray *)ivarInfo
    {
        return [[self class] invarInfo];
    }
    
    + (NSArray *)ivarInfo
    {
        unsigned int outCount;
        Ivar *ivars = class_copyIvarList([self class],&outCount);
        for (int index = 0; index < outCount; index++)
        {
            NSString *type  = [[self class] decodeType:ivar_getTypeEncoding(ivars[index])];
            NSString *name  = [NSString stringWithCString:ivar_getName(ivars[index]) encoding:NSUTF8StringEncoding];
            NSString *ivarDescription = [NSString stringWithFormat:@“%@ %@”,type,name];
            [result addObject:ivarDescription];
        }
        free(ivars);
        return result.count ? [result copy] : nil;
    }
    
    + (NSString *)decodeType:(const char *)cString
    {
        if (!strcmp(cString,@encode(char)))
        {
            return @“char”;
        }
        if (!strcmp(cString,@encode(int)))
        {
            return @“int”;
        }
        if (!strcmp(cString,@encode(short)))
        {
            return @“short”;
        }
        if (!strcmp(cString,@encode(long)))
        {
            return @“long”;
        }
        if (!strcmp(cString,@encode(long long)))
        {
            return @“long long”;
        }
        if (!strcmp(cString,@encode(unsigned char)))
        {
            return @“unsigned char”;
        }
        if (!strcmp(cString,@encode(unsigned int)))
        {
            return @“unsigned int”;
        }
        if (!strcmp(cString,@encode(unsigned short)))
        {
            return @“unsigned short”;
        }
        if (!strcmp(cString,@encode(unsigned long)))
        {
            return @“unsigned long”;
        }
        if (!strcmp(cString,@encode(unsigned long long)))
        {
            return @“unsigned long long”;
        }
        if (!strcmp(cString,@encode(float)))
        {
            return @“float”;
        }
        if (!strcmp(cString,@encode(double)))
        {
            return @“double”;
        }
        if (!strcmp(cString,@encode(bool)))
        {
            return @“bool”;
        }
        if (!strcmp(cString,@encode(_Bool)))
        {
            return @“_Bool”;
        }
        if (!strcmp(cString,@encode(void)))
        {
            return @“void”;
        }
        if (!strcmp(cString,@encode(char *)))
        {
            return @“char *”;
        }
        if (!strcmp(cString,@encode(id)))
        {
            return @“id”;
        }
        if (!strcmp(cString,@encode(Class)))
        {
            return @“class”;
        }
        if (!strcmp(cString,@encode(SEL)))
        {
            return @“SEL”;
        }
        if (!strcmp(cString,@encode(BOOL)))
        {
            return @“BOOL”;
        }
        
        NSString *result    = [NSString stringWithCString:cString encoding:NSUTF8StringEncoding];
        
        if ([[result substringToIndex:1] isEqualToString@“@”] && [result rangeOfString:@“?”].location == NSNotFound)
        {
            result = [[result substringWithRange:NSMakeRange(2,result.length - 3)] stringByAppendingString:@“*”];
        }
        else 
        {
            if ([[result substringToIndex:1] isEqualToString:@“^”])
            {
                result = [NSString stringWithFormat:@“%@ *”,[NSString decodeType:[result substringFromIndex:1] cStringUsingEncoding:NSUTF8StringEncoding]];
            }
        }
        retuen result;
    }
    

    3.获取方法列表

    通过runtimeclass_copyMethodList方法来获取方法列表,通过遍历把方法加入到数组中,最终返回此数组。

    // 对象方法列表
    - (NSArray *)instanceMethodList
    {
        u_int count;
        NSMutableArray  *methodList = [NSMutableArray array];
        Method *methods = class_copyMethodList([self class],&count);
        for (int index = 0; index < count; index++)
        {
            SEL name = method_getName(methods[index]);
            NSString *strName   = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
            [methodList addObject:strName];
        }
        free(methods);
        return methods;
    }
    
    + (NSArray *)instanceMethodList
    {
        u_int count;
        NSMutableArray  *methodList = [NSMutableArray array];
        Method *methods = class_copyMethodList([self class],&count);
        for (int index = 0; index < count; index++)
        {
            SEL name = method_getName(methods[index]);
            NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
        }
        free(methods);
    
        return methodList;
    }
    
    // 获取类方法列表
    + (NSArray *)classMethodList
    {
        unsigned int count = 0;
        Method *methodList  = class_copyMethodList(object_getClass(self),&count);
        NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
        for (unsigned int index = 0; index < count; index++)
        {
            Method method = methodList[index];
            SEL methodName = method_getName(method);
            [mutableList addObject:NSStringFromSelector(methodName)];
        }
        free(methodList);
        return [NSArray arrayWithArray:mutableList];
    }
    

    4.获取协议列表
    运用class_copyProtocolList方法来获得协议列表。

    //协议列表
    - (NSDictionary *)protocolList
    {
        retuen [[self class] protocolList];
    }
    
    + (NSDictionary *)protocolList
    {
        NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
    
        unsigned int count;
    
        Protocol * __unsigned_unretained *protocols = class_copyProtocolList([self class],&count);
        
        for (int index = 0; index < count; index ++)
        {
            Protocol *protocol = protocols[index];
        
            NSString *protocolName  = [NSString stringWithCString:protocol_getName(protocol)];
    
            NSMutableArray *superProtocolArray = ({
    
                NSMutableArray *array = [NSMutableArray array];
    
                unsigned int superProtocolCount;
    
                Protocol * __unsafe_unretained *superProtocols = protocol_copyProtocolList(protocol,&superProtocolCount);
    
                for (int ii = 0; ii < superProtocolCount; ii++)
                {
                    Protocol *superProtocol = superProtocols[ii];
    
                    NSString *superProtocolName = [NSString stringWithCString:protocol_getName(superProtocol) encoding:NSUTF8StringEncoding];
    
                    [array addObject:superProtocolName];
                }
                free(superProtocols);
        
                array;
            });
    
            [dictionary setObject:superProtocolArray forKey:protocolName];
        }
        free(protocols);
    
        return dictionary;
    }
    

    5.方法交换(黑魔法)

    runtime的重头戏,被称作黑魔法的方法交换Swizzling。交换方法是在method_exchangeImplementations里发生的。

    /** 交换实例方法 */   
    + (void)SwizzlingInstanceMethodWithOldMethod:(SEL)oldMethod newMethod:(SEL)newMethod
    {
        Class class = [self class];
        
        SEL originalSelector = oldMethod;
        SEL swizzledSelector = newMethod;
    
        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);  
        }
    }
    
    /** 交换类方法 */
    + (void)SwizzlingClassMethodWithOldMethod:(SEL)oldMethod newMethod:(SEL)newMethod
    {
        Class class = object_getClass((id)self);
        SEL originalSelector = oldMethod;
        SEL swizzledSelector = newMethod;
    
        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);
        }
    }
    

    使用Swizzling的过程中要注意两个问题:

    Swizzling要在+load方法中执行

    运行时会自动调用每个类的两个方法,+load+initialize

    +load会在main函数之前调用,并且一定会调用。

    +initialize是在第一次调用类方法或实例方法之前被调用,有可能一直不被调用

    一般使用Swizzling是为了影响全局,所以为了方法交换一定成功,Swizzling要放在+load中执行。

    新建工程创建类,A类和B类的load顺序是无序的 但是运行过一次后load顺序就固定了
    假如A类先load
    在A类的load方法中创建B类的实例 会使B类先调用initialize方法 后调用 load 方法
    正常情况下,会先调用load方法,后调用initialize方法

    Swizzling要在dispatch_once中执行

    Swizzling是为了影响全局,所以只让它执行一次就可以了,所以要放在dispatch_once中。

    6.添加方法

    /** 添加方法 */
    + (void)addMethodWithSEL:(SEL)methodSEL methodIMP:(SEL)methodIMP
    {
        Method method   = class_getInstanceMethod(self, methodIMP);
        IMP getMethodIMP = method_getImplementation(method);
        const char *types = method_getTypeEncoding(method);
        class_addMethod(self, methodSEL, getMethodIMP, types);
    }
    

    添加方法的运用

    接收到未知的消息时,首先会调用所属类的类方法
    +resolveInstanceMethod:(实例方法)+resolveClassMethod:(类方法)

    第一种情况是,根据已知的方法名动态的添加一个方法。

    第二种情况是,直接添加一个方法。

    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        // 1.如果方法名是addMethod,就添加一个MethodOne方法来执行
        if (sel == NSSelectorfromString(@“addMethod”))
        {
            class_addMethod(self, sel, (IMP)MethodOne, “v@:”);
            return YES;
        }
        
        // 2.如果找不到方法,就添加一个addMethod来执行
        [self addMethodWithSEL:sel methodIMP:@selector(addMethod)];
        return YES;
    }   
    

    7.调用私有方法

    由于消息机制,runtime可以通过objc_msgSend来帮我们调用一些私有方法

    TestRuntime *test   = [[TestRuntime alloc] init];
    objc_msgSend(test, @selector(privateMethod));
    

    使用objc_msgSend需要注意两个问题:

    1.需要导入头文件#import <objc/message.h>
    2.在Build Settings里设置Enable Strict Checking of objc_msgSend Calls 的值为NO

    8.为分类添加属性

    在分类中属性不会自动生成实例变量和存取方法,但是可以运用runtime的关联对象(Associated Object)来解决这个问题。

    @interface UIImage (swizzlingImage)
    
    // 用关联对象为分类添加属性
    @property (nonatomic, copy) NSString *categoryProperty;
    
    @end
    
    @implementation UIImage (swizzlingImage)
    
    // getter
    - (NSString *)categoryProperty
    {
        return objc_getAssociatedObject(self, _cmd);
    }
    // setter
    - (void)setCategoryProperty:(NSString *)categoryProperty
    {
        objc_setAssociatedObject(self, @selector(categoryProperty), categoryProperty, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    @end
    

    使用objc_getAssociatedObjectobjc_setAssociatedObject 来做到存取方法,使用关联对象模拟实例变量。下面是两个方法的原型:

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

    方法中的@selector(categoryProperty)就是参数key,使用@selector(categoryProperty)作为key传入,可以确保key的唯一性。

    OBJC_ASSOCIATION_COPY_NONATOMIC 是属性修饰符

    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy)
    {
        OBJC_ASSOCIATION_ASSIGN = 0,
        OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
        OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
        OBJC_ASSOCIATION_PETAIN = 01401,
        OBJC_ASSOCIATION_COPY   = 01403
    };
    

    相关文章

      网友评论

          本文标题:RunTime

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