Runtime

作者: 旺仔Milk | 来源:发表于2017-08-24 18:28 被阅读15次

    本文系摘抄,仅供学习,如有错误欢迎指正,谢谢

    一.认识一下runtime类

    image.png

    二.The Runtime

    1.Objective-C:

    是一门简单的语言,95%是C。只是在语言层面上加了些关键字和语法。真正让Objective-C如此强大的是它的运行时。它很小但却很强大。它的核心是消息分发。

    2.Messages

    执行一个方法,有些语言,编译器会执行一些额外的优化和错误检查,因为调用关系很直接也很明显。但对于消息分发来说,就不那么明显了。在发消息前不必知道某个对象是否能够处理消息。你把消息发给它,它可能会处理,也可能转给其他的 Object 来处理。一个消息不必对应一个方法,一个对象可能实现一个方法来处理多条消息。在 Objective-C 中,消息是通过objc_msgSend()这个 runtime 方法及相近的方法来实现的。这个方法需要一个target,selector,还有一些参数。理论上来说,编译器只是把消息分发变成objc_msgSend()来执行。比如下面这两行代码是等价的:

    [array insertObject:foo atIndex:5];
    // 等价于下面这个
    objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);
    

    3.Objects, Classes, MetaClasses

    大多数面向对象的语言里有 classes 和 objects 的概念。Objects通过Classes生成。但是在Objective-C中,classes本身也是objects(译者注:这点跟python很像),也可以处理消息,这也是为什么会有类方法和实例方法。具体来说,

    Objective-C 中的 Object 是一个结构体(struct),第一个成员是isa,指向自己的class。

    这是在objc/objc.h中定义的。
    typedef struct objc_object { Class isa;} *id;
    object的class保存了方法列表,还有指向父类的指针。但classes也是objects,也会有isa变量,那么它又指向哪儿呢?这里就引出了第三个类型:metaclasses。一个metaclass被指向class,class被指向object。它保存了所有实现的方法列表,以及父类的metaclass

    4.Methods, Selectors and IMPs*

    我们知道了运行时会发消息给对象。我们也知道一个对象的class保存了方法列表。那么这些消息是如何映射到方法的,这些方法又是如何被执行的呢?第一个问题的答案很简单。class的方法列表其实是一个字典,key 为 selectors ,IMPs 为value 。一个IMP是指向方法在内存中的实现。很重要的一点是,selector和IMP之间的关系是在运行时才决定的,而不是编译时。这样我们就可以在其中进行操作啦。IMP通常是指向方法的指针,。下面演示了Method和IMP

    - (id)doSomethingWithInt:(int)aInt{}
    // 第一个参数是self,类型为id,第二个参数是_cmd,类型为SEL,余下的是方法的参数。 这也是self和_cmd被定义的地方
    id doSomethingWithInt(id self, SEL _cmd, int aInt){}
    

    5运行时到底能做什么呢?

    所有的运行时方法都有特定的前缀。下面是一些有意思的方法:classclass开头的方法是用来修改和自省classes。方法如class_addIvar, class_addMethod, class_addPropertyclass_addProtocol允许重建classes。class_copyIvarList, class_copyMethodList,class_copyProtocolListclass_copyPropertyList能拿到一个class的所有内容。而class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementationclass_getProperty返回单个内容。

    也有一些通用的自省方法,如class_conformsToProtocol, class_respondsToSelector, class_getSuperclass。最后,你可以使用class_createInstance来创建一个object。
    ivar
    这些方法能让你得到名字,内存地址和Objective-C type encoding。
    method
    这些方法主要用来自省,比如method_getName, method_getImplementation, method_getReturnType等等。也有一些修改的方法,包括method_setImplementationmethod_exchangeImplementations,这些我们后面会讲到。
    objc
    一旦拿到了object,你就可以对它做一些自省和修改。你可以get/set ivar, 使用object_copyobject_disposecopyfree object的内存。最NB的不仅是拿到一个class,而是可以使用object_setClass来改变一个object的class。待会就能看到使用场景。
    property
    属性保存了很大一部分信息。除了拿到名字,你还可以使用property_getAttributes来发现property的更多信息,如返回值、是否为atomicgetter/setter名字、是否为dynamic、背后使用的ivar名字、是否为弱引用。
    protocol
    Protocols有点像classes,但是精简版的,运行时的方法是一样的。你可以获取method, property, protocol列表, 检查是否实现了其他的protocol。
    sel
    最后我们有一些方法可以处理 selectors,比如获取名字,注册一个selector等等
    Classes And Selectors From Strings
    通过一个字符串生成Classes和Selectors。

    // NSClassFromString 和 NSSelectorFromString:
    Class stringclass = NSClassFromString(@"NSString");
    

    这样做有以下优点:1.可以得知是否存在某个class,NSClassFromString 会返回nil,2根据不同的输入返回不同的class或method进行相应的操作,比如你在解析一些数据,每个数据项都有要解析的字符串以及自身的类型(String,Number,Array)。你可以在一个方法里搞定这些,也可以使用多个方法。
    Method Swizzling
    我们知道方法由两个部分组成。Selector相当于一个方法的id;IMP是方法的实现。这样分开的一个便利之处是selector和IMP之间的对应关系可以被改变。比如一个 IMP 可以有多个 selectors 指向它。而 Method Swizzling 可以交换两个方法的实现。或许你会问“什么情况下会需要这个呢?”。我们先来看下Objective-C中,两种扩展class的途径。首先是 subclassing。你可以重写某个方法,调用父类的实现,这也意味着你必须使用这个subclass的实例,但如果继承了某个Cocoa class,而Cocoa又返回了原先的class(比如 NSArray)。这种情况下,你会想添加一个方法到NSArray,也就是使用Category。99%的情况下这是OK的,但如果你重写了某个方法,就没有机会再调用原先的实现了。
    Method Swizzling 可以搞定这个问题。你可以重写某个方法而不用继承,同时还可以调用原先的实现。通常的做法是在category中添加一个方法(当然也可以是一个全新的class)。可以通过method_exchangeImplementations这个运行时方法来交换实现。
    来看一个demo,这个demo演示了如何重写addObject:方法来纪录每一个新添加的对象。

    #import <objc/runtime.h>
    @interface NSMutableArray (LoggingAddObject)
    - (void)logAddObject:(id)aObject;
    @end
    
    @implementation NSMutableArray (LoggingAddObject)
    + (void)load {
         Method addobject = class_getInstanceMethod(self, @selector(addObject:));
         Method logAddobject = class_getInstanceMethod(self, @selector(logAddObject:));
         method_exchangeImplementations(addObject, logAddObject);
      }
    - (void)logAddObject:(id)aobject {
         [self logAddObject:aObject];
         NSLog(@"Added object %@ to array %@", aObject, self);
      }
    @end
    

    我们把方法交换放到了load中,这个方法只会被调用一次,而且是运行时载入。如果指向临时用一下,可以放到别的地方。注意到一个很明显的递归调用logAddObject:。这也是Method Swizzling容易把我们搞混的地方,因为我们已经交换了方法的实现,所以其实调用的是addObject:

    动态继承、交换
    我们可以在运行时创建新的class,这个特性用得不多,但其实它还是很强大的。你能通过它创建新的子类,并添加新的方法。
    但这样的一个子类有什么用呢?别忘了Objective-C的一个关键点:object内部有一个叫做isa的变量指向它的class。这个变量可以被改变,而不需要重新创建。然后就可以添加新的ivar和方法了。可以通过以下命令来修改一个object的

    class.object_setClass(myObject, [MySubclass class]);
    

    这可以用在Key Value Observing。当你开始observing an object时,Cocoa会创建这个object的class的subclass,然后将这个object的isa指向新创建的subclass。点击这里查看更详细的解释。动态方法处理目前为止,我们讨论了方法交换,以及已有方法的处理。那么当你发送了一个object无法处理的消息时会发生什么呢?很明显,"it breaks"。大多数情况下确实如此,但Cocoa和runtime也提供了一些应对方法。
    首先是动态方法处理。通常来说,处理一个方法,运行时寻找匹配的selector然后执行之。有时,你只想在运行时才创建某个方法,比如有些信息只有在运行时才能得到。要实现这个效果,你需要重写+resolveInstanceMethod:和/或+resolveClassMethod:。如果确实增加了一个方法,记得返回YES。

    + (BOOL)resolveInstanceMethod:(SEL)aSelector { 
         if (aSelector ==@selector(myDynamicMethod)) { 
               class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:");
               return YES;
          } 
         return [super resolveInstanceMethod:aSelector];
    }
    

    消息转发
    如果 resolve method 返回NO,运行时就进入下一步骤:消息转发。有两种常见用例。

    1. 将消息转发到另一个可以处理该消息的object。
    2. 将多个消息转发到同一个方法。
      消息转发分两步。首先,运行时调用-forwardingTargetForSelector:,如果只是想把消息发送到另一个object,那么就使用这个方法,因为更高效。如果想要修改消息,那么就要使用-forwardInvocation:,运行时将消息打包成NSInvocation,然后返回给你处理。处理完之后,调用invokeWithTarget:
      Cocoa有几处地方用到了消息转发,主要的两个地方是代理(Proxies)和响应链(Responder Chain)。NSProxy是一个轻量级的class,它的作用就是转发消息到另一个object。如果想要惰性加载object的某个属性会很有用。NSUndoManager也有用到,不过是截取消息,之后再执行,而不是转发到其他的地方。
      响应链是关于Cocoa如何处理与发送事件与行为到对应的对象。比如说,使用Cmd+C执行了copy命令,会发送-copy:到响应链。首先是First Responder,通常是当前的UI。如果没有处理该消息,则转发到下一个-nextResponder。这么一直下去直到找到能够处理该消息的object,或者没有找到,报错。

    使用Block作为Method IMP
    iOS 4.3带来了很多新的runtime方法。除了对properties和protocols的加强,还带来一组新的以 imp 开头的方法。通常一个 IMP 是一个指向方法实现的指针,头两个参数为 object(self)和selector(_cmd)。iOS 4.0和Mac OS X 10.6 带来了block,imp_implementationWithBlock()能让我们使用block作为 IMP,下面这个代码片段展示了如何使用block来添加新的方法。

    IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) { 
            NSLog(@"Hello %@", string);
      });
    class_addMethod([MYclass class], @selector(sayHello:), myIMP, "v@:@");
    

    可以看到,Objective-C 表面看起来挺简单,但还是很灵活的,可以带来很多可能性。动态语言的优势在于在不扩展语言本身的情况下做很多很灵巧的事情。比如Key Value Observing,提供了优雅的API可以与已有的代码无缝结合,而不需要新增语言级别的特性。

    三.runtime 优秀博客

    Objective-C对象模型及应用
    objc/runtime 探索
    iOS开发教程之Objc Runtime笔记

    四.初识runtime

    1. objc的Types定义
    包括objc_method结构体方法Method;
    objc_ivar结构体实例变量Ivar;
    objc_category结构体类目Category;
    类中声明的objc_property结构体属性objc_property_t;
    class类objc_class结构体;

    /* Types */
    #if !OBJC_TYPES_DEFINED 
    //objc_types_defined(1)
    /// An opaque type that represents(代表) a method in a class definition(定义). 
    // 代表一个类定义中的一个方法
    typedef struct objc_method *Method; 
    -----------------
    以前方法的定义;
    struct objc_method { 
        SEL method_name;
        char *method_types; 
        IMP method_imp;
    }
    

    SEL selector的简写,俗称方法选择器,实质存储的是方法的名称
    IMP implement的简写,俗称方法实现,看源码得知它就是一个函数指针
    Method 对上述两者的一个包装结构.例如:
    获取类方法:
    Method class_getClassMethod(Class cls, SEL name)

    1. Ivar:实例变量,成员变量;
      --只能获取自己类的实例变量,因为封装性,其他类的实例变量不能获取typedef struct objc_ivar *Ivar;
      例:获取实例变量name;用属性声明的时候必须要@synthesize name才能有实例变量;
    @property(nonatomic,copy)NSString * name; 
    @synthesize name;
    // 或者:
    @interface MasterViewController : UITableViewController{ 
        NSString *name;
    }
    
    1. An opaque type that represents a category. //代表一个类目
    typedef struct objc_category *Category;
    
    1. 代表oc类中声明的属性; — 多个属性的列表; (包括获取私有属性);
    typedef struct objc_property *objc_property_t;
    
    1. objc_class:
      类对象的isa指向元类;类class在runtime中的表示:(在实际 中用Class 替代struct 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; //方法缓存,对象接到一个消息会根据isa指针查找消息对象,这时会在methodLists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率。 
        struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; //协议链表.
        #endif
    } 
    OBJC2_UNAVAILABLE;
    /* Use `Class` instead of `struct objc_class *` */
    #endif
    

    2.定义协议结构体struct objc_object Protocol;

    #ifdef __OBJC__
    @class Protocol;
    #else
    typedef struct objc_object Protocol;
    #endif
    

    3.定义一个方法method的结构体objc_method_description:方法名name,方法参数types;

    SEL name; /**< The name of the method  方法名 */
    char *types; /**< The types of the method arguments 方法参数 */
    

    4.定义一个声明属性结构体objc_property_attribute_t: 属性名name,属性值value;

    typedef struct { 
      const char *name; /**< The name of the attribute */特性名称
      const char *value; /**< The value of the attribute (usually empty) */特性值;
    } objc_property_attribute_t;
    

    特性相关编码
    属性的特性字符串 以 T@encode(type) 开头, 以 V实例变量名称 结尾,中间以特性编码填充,通过property_getAttributes即可查看特性编码具体含义

    R --- readonly
    C --- copy
    & --- retain
    N --- nonatomic
    G(name) --- getter=(name)
    S(name) --- setter=(name)
    D --- @dynamic
    W --- weak
    P --- 用于垃圾回收机制
    

    五.认识一下runtime的函数

    *1. 对象拷贝
    id object_copy(id obj, size_t size);[obj copy]-----arc中不能用;
    *2.释放给定对象的内存object_dispose(id obj)-释放对象,同[obj release]-----arc中不能使用;
    *3.获取对象obj的类---Class object_getClass(id obj); 同oc的[obj class];
    *4.设置id对象obj的类为cos:Class object_setClass(id obj, Class cos);
    *5.判断一个id对象是否是class类:BOOL object_isClass(id obj)——如果是类或者元类就返回YES,如果是对象就返回NO,因为对象不符合objc_class结构体中包含该有的成员变例如:

    ViewController *obj = [ViewController new]; 
    BOOL fi = object_isClass(obj); 
    NSLog(@"%@",@(fi));  //0,NO,因为obj是对象,不是类;
    

    6.获取对象obj的类名,const char *object_getClassName(id obj); obj:是一个对象;例如:

    ViewController *obj = [ViewController new]; 
    NSLog(@"%s",object_getClassName(obj));//ViewController
    

    7.不支持arc,不能用object_getIndexedIvars(id obj)

    8.获取一个对象的一个实例变量的值

    id object_getIvar(id obj, Ivar ivar)
    // 只能获取自己实例变量的值;
    // 跟Ivar class_getInstanceVariable(Class cls, const char *name)结合使用;
    // 跟oc的:[obj valueForKey:ivar],self.语法获取类似!
    

    9.
    void object_setIvar(id obj, Ivar ivar, id value)
    设置一个对象(obj)的一个实例变量(ivar)的
    Ivar class_getInstanceVariable(Class cls, const char *name)得到一个类中成员变量名字XX的成员变量Ivar
    Example:

    //获取一个类中名字为age的实例变量
    Ivar var = class_getInstanceVariable([self class], "age"); 
    //给这个实力变量赋值
    object_setIvar(self, var, @22);
    

    10.设置一个实例变量的实例Ivar:

    Ivar object_setInstanceVariable(id obj, const char *name, void *value)//arc不能使用
    

    11.获取一个实例变量的实例Ivar:

    Ivar object_getInstanceVariable(id obj, const char *name, void *outValue) // arc不能用
    

    12.根据class的name获取Class类

    Class objc_getClass(const char *name)
    // 等同于oc的
    NSClassFromString();
    //返回这个名字类的对象的元类; 如果没有注册过,将返回nil;
    Example:
    Class vc = objc_getClass("ViewController");
    Class vc1 = NSClassFromString(@"ViewController");
    

    13.根据class的name获取Class元类-
    Class objc_getMetaClass(const char *name);
    什么是元类呢?Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下

    typedef struct objc_class *Class;
    
    struct objc_class { 
        Class isa OBJC_ISA_AVAILABILITY;
        #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;
    

    isa:需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类)如下图:


    获取类定义的方法有三个:objc_lookUpClass, objc_getClassobjc_getRequiredClass。如果类在运行时未注册,则objc_lookUpClass会返回nil,而objc_getClass会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。而objc_getRequiredClass函数的操作与objc_getClass相同,只不过如果没有找到类,则会杀死进程。objc_getMetaClass函数:如果指定的类没有注册,则该函数会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。不过,每个类定义都必须有一个有效的元类定义,所以这个函数总是会返回一个元类定义,不管它是否有效。
    14.获取注册类的总数,列表buffer

    //buffer:缓存class的所有value的数组; 
    //bufferCount:缓存所有的类的个数
    int objc_getClassList(Class *buffer, int bufferCount);
    
    // 创建并返回一个指向所有已注册类的指针列表
    Class * objc_copyClassList ( unsigned int *outCount );
    Example:
    // 利用objc_getClassList输出项目所有类
    int numClasses; 
    Class *classes = NULL; 
    classes = NULL; 
    numClasses = objc_getClassList(NULL, 0); 
    NSLog(@"Number of classes: %d", numClasses);
     if (numClasses > 0 ) { classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses); numClasses = objc_getClassList(classes, numClasses); for (int i = 0; i < numClasses; i++) { NSLog(@"Class name: %s", class_getName(classes[i])); } free(classes);//malloc 分配内存 需要free释放 }
    

    15.获取类的类名----

    const char *class_getName(Class cls)
    
    Example:
    const char *className = class_getName(self.class);
    NSLog(@"%s",className);//转换为NSString字符串 
    NSString *strName = [NSString stringWithCString:className encoding:NSUTF8StringEncoding];
    

    16.判断这个class类是否是元类
    BOOL class_isMetaClass(Class cls);
    获取这个class类的父类
    Class class_getSuperclass(Class cls);
    设置class类cls的父类为新的newSuper类
    Class class_setSuperclass(Class cls, Class newSuper)
    @warning You should not use this function.//你不应该使用这个功能;
    获取这个类的实例变量大小
    size_t class_getInstanceSize(Class cls)
    不知道有什么卵用
    17.成员变量(ivar)- 获取类cls中指定name的实例成员变量
    Ivar class_getInstanceVariable(Class cls, const char *name)— 可以用这个来获取api的私有方法;
    成员变量(ivars)- 获取类中指定name的变量
    Ivar class_getClassVariable(Class cls, const char *name)

    Example:
    self.name = @"iOS";
    Ivar var = class_getInstanceVariable([self class], "name");
    id nameVar = object_getIvar(self, var); //同get; 
    NSLog(@"%@", nameVar);
    Ivar var = class_getClassVariable([self class], "name"); 
    object_setIvar(self, var, @"iOS"); 
    id nameVar = object_getIvar(self, var); 
    NSLog(@"%@", nameVar);
    

    18.获取类中所有实例成员变量IvarIvar
    Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
    这个比较常用class_copyPropertyList返回的仅仅是对象类的属性(@property声明的属性),而class_copyIvarList返回类的所有属性和变量(包括在@interface大括号中声明的变量)获取cls类指定名字的属性objc_property_t class_getProperty(Class cls, const char *name)

    Example:
    - (NSMutableArray *)getArrayValue:(NSArray *)array {
        NSMutableArray *valueArray = [NSMutableArray array]; //value数组 
        for (NSObject *object in array) { 
            unsigned int numberofIvars = 0;// 获取类成员变量列表,numberofIvars 为类成员数量 
            Ivar* ivars = class_copyIvarList([object class], &numberofIvars); 
            NSMutableArray *objectArray = [NSMutableArray array]; 
            for(const Ivar* p = ivars; p< ivars+numberofIvars;p++){ 
                Ivar const ivar = *p ; //获取变量名; 
                NSString* key = [NSString stringWithUTF8String:ivar_getName(ivar)]; 
                NSString *value = [object valueForKey:key]; 
                [objectArray addObject:value]; 
                } 
            [valueArray addObject:objectArray]; 
            } 
            free(ivars);//(需要free释放 否者内存泄露) 
        return valueArray;
    }
    //获取cls类指定名字的属性 
    objc_property_t properties = class_getProperty(objc_getClass("Class"), [@"name"UTF8String]); 
    const char *propertyName = property_getName(properties); 
    NSString *str = [NSString stringWithUTF8String:propertyName]; 
    NSLog(@"%@",str);
    

    19.获取实例方法(Method)
    获取类中的某个实例方法(减号方法):
    Method class_getInstanceMethod(Class cls, SEL name)
    获取类中的某个类方法(加号方法):
    Method class_getClassMethod(Class cls, SEL name)
    获取类中的SEL方法的实现:
    IMP class_getMethodImplementation(Class cls,SEL name)
    IMP方法实现这个参数可以被用在方法的实现替换函数class_replaceMethod;
    添加方法

    class_addMethod(Class cls, SEL name, IMP imp, const char *types)
    //等同oc的方法:
    - (IMP)methodForSelector:(SEL)aSelector;
    

    判断这个方法是否添加成功
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
    替换cls类中已有方法SEL-name的实现为imp,如果该方法不存在添加该方法
    IMP class_replaceMethod(Class cls, SEL name, IMP imp,const char *types)
    Example:
    利用runtime懒人实现iOS 防止按钮连续点击
    20.判断类中是否实现了某个方法的

    BOOL class_respondsToSelector(Class cls, SEL sel)
    // 与oc中NSObject.h的方法一样:
    - (BOOL)respondsToSelector:(SEL)aSelector;
    // 或
    - (BOOL)instancesRespondToSelector:(SEL)aSelector;
    // ***在使用代理时经常需要用到这个判断
    

    21.获取类的所有方法列表-

    Method *class_copyMethodList(Class cls, unsigned int *outCount)
    // 包括获取到了私有方法;
    // outCount是返回的一个值,包含返回array列表数组的长度个数,如果outCount是NULL,就不会返回length;
    Example:
    //例如:获取UILabel类的所有方法列表
    u_int count; 
    Method *methods = class_copyMethodList([UILabel class], &count); 
    for (int i =0; i<count; i++) {
        SEL name1 = method_getName(methods[i]);
        const char *selName= sel_getName(name1); 
        NSString *strName = [NSString stringWithCString:selName encoding:NSUTF8StringEncoding]; 
        NSLog(@"%@",strName); 
      // 这2句等同于NSStringFromSelector(name1);
    }
    free(methods);
    NSLog(@"%u",count);
    //171个方法;包括很多私有方法;
    

    22.判断类是否实现指定的协议

    - BOOL class_conformsToProtocol(Class cls, Protocol *protocol)
    //  与NSObject类中的方法类似, 但是不常用
    - (BOOL)conformsToProtocol:(Protocol*)aProtocol;
    
    // 返回类实现的协议列表
    Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount)
    
    Example:
    
    BOOL protocol = class_conformsToProtocol(self.class, @protocol(XXXDelegate));
    //oc中: 
    BOOL protocol = [self conformsToProtocol:@protocol(XXXDelegate)];
    //class_copyProtocolList 
    unsigned int outCount=0; 
    Protocol * __unsafe_unretained *protocols = class_copyProtocolList(objc_getClass("Class"), &outCount);
    for (int i =0; i<outCount; i++) { 
        Protocol *myProtocol = protocols[i]; 
        const char *protocolName = protocol_getName(myProtocol); 
        NSString *str = [NSString stringWithUTF8String:protocolName]; 
        NSLog(@"%@",str); 
    }
    

    23.给class类添加一个新的实例变量,再判断是否成功
    /*cls:动态创建的类; name:实例变量名; size:实例变量类型字节数; alignment:对齐方法,一般是0; */
    BOOL class_addIvar(Class cls, const char *name, size_t size,uint8_t alignment, const char *types)
    [注意]只能在动态创建类objc_allocateClassPair之后,注册类objc_registerClassPair之前添加实例变量给class动态添加一个属性:
    class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
    24.动态添加类.

    // 1* 创建一个新类和元类:*
    Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
    // superclass:父类;name:类名;extraBytes:变量字节数,一般是0;跟objc_registerClassPair是一对,搭配使用;
    // 2 *在应用中注册由objc_allocateClassPair创建的类; *
    void objc_registerClassPair(Class cls) //注册后,才能使用这个类;
    // 3 * 销毁一个类及其相关联的类 *
    void objc_disposeClassPair ( Class cls );
    //在运行中还存在或存在子类实例,就不能够调用这个
    Example:
    Class Test= objc_allocateClassPair([NSObject class], "Test", 0);
    //为类添加变量
    class_addIvar(Test, "_name", sizeof(NSString*), log2(sizeof(NSString*)), @encode(NSString*));
    //为类添加方法
    //IMP 是函数指针
    // typedef id (*IMP)(id, SEL, ...);
    IMP i = imp_implementationWithBlock(^(id this,id some){ 
        NSLog(@"%@",some); 
        return @111;
    });
    //注册方法名为 test: 的方法
    SEL s = sel_registerName("test:");
    class_addMethod(Test, s, i, "i@:");
    //结束类的定义
    objc_registerClassPair(Test);
    

    六 消息转发

    我们程序崩溃的时候看到这样的提示:
    unrecognized selector sent to instance
    表明你曾向某个对象发送了一条无法解读的消息。
    当一个对象收到无法解读的消息后会如何处理,也就是说对象无法响应选择子(方法),这时就要进入到消息转发机制的流程。
    1*.查找接收者所属的类,看其是否能动态添加方法,以处理这个“未知的方法”。
    (动态方法解析)
    +(BOOL) resolveInstanceMethod:(SEL)selector
    2 .运行期系统把消息转给其他接收者处理。
    (备援接收者)
    -(id)forwardingTargetForSelector:(SEL)selector
    3
    .经过上述两步后,如果还是没有办法处理选择子,就启动完成的消息转发。创建NSInvocation对象,把与尚未处理的那条消息有关的全部细节都封于其中。此对象包含选择子、目标target及参数。在触发NSInvocation对象时,消息派发系统会把消息指派给目标对象。
    -(void)forwardInvocation:(NSInvocation *)invocation

    7.Objective-C Reflection(Objective-C 反射机制)

    JSONModel中的实现:

    image.png

    对象属性的获取则主要在最后一个inspectProperties方法

    -(void)__inspectProperties{ 
        //JMLog(@"Inspect class: %@", [self class]);   
        NSMutableDictionary* propertyIndex = [NSMutableDictionary dictionary]; 
        //temp variables for the loops 
        Class class = [self class]; 
        NSScanner* scanner = nil; 
        NSString* propertyType = nil; 
        // inspect inherited properties up to the JSONModel class 
        while (class != [JSONModel class]) { 
            //JMLog(@"inspecting: %@", NSStringFromClass(class)); 
            unsigned int propertyCount; 
            objc_property_t *properties = class_copyPropertyList(class, &propertyCount); 
            //loop over the class properties 
            for (unsigned int i = 0; i < propertyCount; i++) { 
                JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init]; 
                //get property name 
                objc_property_t property = properties[i]; 
                const char *propertyName = property_getName(property); 
                p.name = [NSString stringWithUTF8String:propertyName]; 
                //JMLog(@"property: %@", p.name); 
                //get property attributes 
                const char *attrs = property_getAttributes(property); 
                NSString* propertyAttributes = [NSString stringWithUTF8String:attrs]; 
                if ([propertyAttributes hasPrefix:@"Tc,"]) { 
                    //mask BOOLs as structs so they can have custom convertors 
                    p.structName = @"BOOL"; 
                } 
                scanner = [NSScanner scannerWithString: propertyAttributes]; 
                //JMLog(@"attr: %@", [NSString stringWithCString:attrs encoding:NSUTF8StringEncoding]);
                [scanner scanUpToString:@"T" intoString: nil]; 
                [scanner scanString:@"T" intoString:nil];
                ···
    //finally store the property index in the static property index
    objc_setAssociatedObject(self.class, 
                             &kClassPropertiesKey, 
                             [propertyIndex copy], 
                             OBJC_ASSOCIATION_RETAIN // This is atomic );
    

    在这边可以看到基本步骤如下
    通过调用自身的class方法获取当前类的元数据信息通过runtime的 class_copyPropertyList 方法取得当前类的属性列表,以指针数组的形式返回遍历指针数组,通过property_getName获取属性名,property_getAttributes获取属性类型使用NSScanner来扫描属性类型字符串,将类似如下的形式"T@"NSNumber",&,N,V_id",处理成NSNumber,逐个属性循环处理将所有处理好的数据放入propertyIndex这个字典中通过objc_setAssociatedObject将这些数据关联到kClassPropertiesKey使用时在properties方法中这样取出属性数据:

    //returns a list of the model's properties
    -(NSArray*)__properties__{
        //fetch the associated object 
        NSDictionary* classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey); 
        if (classProperties) 
        return [classProperties allValues]; 
        //if here, the class needs to inspect itself 
        [self __setup__]; 
        //return the property list 
        class Properties = objc_getAssociatedObject(self.class, &kClassPropertiesKey); 
        return [classProperties allValues];
    }
    

    以上就是JSONModel中使用反射机制实现的类属性获取过程,相比常见的逐个取值赋值的方式,这种方法在代码上的确简洁优雅了很多

    相关文章

      网友评论

          本文标题:Runtime

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