美文网首页iOS-Runtime机制
Runtime笔记一:类与对象

Runtime笔记一:类与对象

作者: 蔚尼 | 来源:发表于2018-05-15 17:16 被阅读22次

    Objective-C是一门动态语言,把静态语言在编译和链接时期做的事情放到运行的时候来处理。这样我们写代码时可以把消息转发给我们想要的对象,或者交换一个方法的实现等。
    但这种特性需要一个运行时系统来执行编译的代码,让所有工作可以正常运行。这个运行时系统就是Objc Runtime,它其实是一个Runtime库,使用C和汇编写的库。

    Runtime库做的事情:

    • 封装:runtime里面,对象用C语言的结构体表示,方法用C语言的函数的表示,另外可以加上一些额外的特性。这样在运行的时候,创建,检查,修改类、对象和他们的方法。
    • 找到最终执行方法的代码:当程序执行[object doSomething]时,会给消息接收方发送消息,runtime会根据接收消息者能否执行方法做出不同的反应。

    一.runtime里面的类

    1、类

    OC中类是Class类型,它是一个指向objc_class结构体的指针。

    typedef struct objc_class *Class;
    

    objc/runtime.h中objc_class结构体的定义如下(如前面写的,类用结构体表示了):

    // An opaque type that represents an Objective-C declared property.
    typedef struct objc_property *objc_property_t;
    
    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class _Nullable super_class                              OBJC2_UNAVAILABLE;
        const char * _Nonnull name                               OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    

    2、objc_object(类的实例的结构体)与id

    objc_object是一个类的实例的结构体,定义如下:

    id是objc_object结构体类型的指针。

    /// Represents an instance of a class.(声明了一个实例类)
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    
    typedef struct objc_object *id;
    
    

    isa指针注意:

    1和2里面需要注意的字段:
    1.isa指针
    - 发送消息时(执行方法时),isa指向谁,就在谁里面查找要执行的方法;
    - 发送消息给一个对象时,isa指针指向这个对象所属的类,会在所属类和父类里面查找要执行的方法;
    - 发送消息给一个类时,isa指针指向这个类的元类,元类里面包含了所有的类方法。就会在元类(meta-class)的方法列表里面查找要执行的方法。
    eg:[NSArray array];就是在给NSArray发送消息,在NSArray的元类里面查找要执行的方法。
    - 发送消息给一个元类(meta-class),所有元类的isa指针都指向NSObject(基类)的元类,而NSObject的isa指针指向自己。形成了一个闭环。

    2.super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。

    3.cache:用于缓存最近使用的方法。
    发送一个消息时,isa指针都会去寻找可以执行该消息的对象。
    在每次调用过一个方法之后,这个方法会被缓存到catch之中,下次调用同样方法的时候runtime优先去catch里面查找,如果catch里面没有才去methodlists中查找方法。这样对于经常调用的方法就提高了效率。
    eg:

    NSArray *array = [[NSArray alloc] init];调用流程:
    1、执行[NSArray alloc] 方法,NSArray里面没有alloc方法,就去父类NSObject里面查找,找到后分配内存空间,并把alloc方法放到缓存里面,‘isa’指针指向NSArray类;
    2、执行init方法,NSArray响应该方法,就加入‘catche’,否则就去父类里面查找。
    3、以后如果在调用 [[NSArray alloc] init]方法,则直接从catche里面取出相应的方法。
    
    1. version:版本;在对象的序列化中,可以识别出不同类定义版本中实例变量布局的改变。

    3、元类(meta-class)

    • 每个类都有一个元类,这个元类里面放着所有的类方法。
    • 元类(meta-class)是一个类对象的类。
    • 当我们向一个对象发送消息时,runtime会在对象的所属类的方法列表里面寻找。
    • 当我们向一个类发送消息时(调用类方法时),就会在这个类的元类的方法列表中查找。
      eg:
    //我们在给NSArray发送消息。把NSArray当做一个对象(类对象),在给NSArray对象发送array消息。就会到NSArray的元类里面查找array方法。
    NSArray *array = [NSArray array];
    

    二、类和对象的操作函数

    类的操作方法大部分是以class_为前缀的;
    对象的操作方法大部分是以objc_object_为前缀;

    1、类相关操作函数

    runtime对类提供的相关操作方法主要是针对objc_class中各个结构体提供的。
    在操作之前,我们创建MyClass类,用MyClass进行操作:

    .h文件:
    #import <Foundation/Foundation.h>
    
    @interface MyClass : NSObject<NSCopying>
    
    @property(nonatomic,strong) NSArray * array;
    @property(nonatomic,copy) NSString * string;
    
    -(void)method1;
    -(void)method2;
    +(void)classMethod1;
    
    @end
    
    .m文件:
    #import "MyClass.h"
    
    @interface MyClass (){
        NSInteger  instant1;
        NSString * instant2;
    }
    
    @property(nonatomic,assign) NSUInteger integer;
    -(void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;
    @end
    
    @implementation MyClass
    
    +(void)classMethod1{
        
    }
    -(void)method1{
        NSLog(@"method1");
    }
    -(void)method2{
        NSLog(@"method2");
    }
    -(void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2{
        NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);
    }
    
    @end
    

    viewcontroller里面:

    #import "ViewController.h"
    #import "MyClass.h"
    #import <objc/runtime.h>
    
        MyClass * myClass = [[MyClass alloc] init];
    
    

    1-1、获取类的类名

     const char * className =  class_getName(myClass.class);
     const char * nilName =  class_getName(nil);
     NSLog(@"className:%@,nilName:%@",[NSString stringWithUTF8String:className],[NSString stringWithUTF8String:nilName]);
    //打印:className:MyClass,nilName:nil
    

    结论:
    const char * class_getName ( Class cls )函数
    如果传入的cls为Nil,则返回一个字字符串"nil"

    1-2、获取类的父类

     Class superClass = class_getSuperclass(myClass.class);
     Class nilClass = class_getSuperclass(nil);
     Class noSuperClass = class_getSuperclass([[NSObject alloc] init].class);//测试没有父类的情况
     NSLog(@"superClass:%@,nilClass:%@,noSuperClass:%@",superClass,nilClass,noSuperClass);
    //打印:superClass:NSObject,nilClass:(null),noSuperClass:(null)
    

    结论:

    Class class_getSuperclass ( Class cls );
    cls为Nil或者cls为根类时,返回Nil

    1-3、获取类的元类

    Class metaClass = objc_getMetaClass(class_getName(myClass.class));
    

    1-4、判断一个类是不是元类

    //获取一个元类
    Class metaClass = objc_getMetaClass(class_getName(myClass.class));
    
    BOOL isMeta = class_isMetaClass(myClass.class);
    BOOL isMeta1 = class_isMetaClass(metaClass);
    NSLog(@"isMeta:%d,isMeta1:%d",isMeta,isMeta1);
    //打印:isMeta:0,isMeta1:
    

    1-5、获取实例变量大小

    size_t classSize = class_getInstanceSize(myClass.class);
    NSLog(@"classSize:%zu",classSize);
    //打印:classSize:48
    

    2、成员变量ivars及属性

    • 在objc_class中,所有的成员变量、属性的信息都放在链表ivars中。
    • ivars是一个数组,数组中每个元素是指向objc_ivar结构体(变量信息)的指针。
    • 这个数组不包含在父类中声明的变量。

    2-1、 成员变量

    2-1-1、获取整个成员变量列表
      Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
    
    • outcount指针返回数组的大小。
    • 注意,必须使用free()来释放这个数组。
       unsigned int count = 0;
        Ivar * ivars = class_copyIvarList(myClass.class, &count);
        for (int i = 0; i <count; i++) {
            
            Ivar ivar = ivars[i];
            const char * ivarName = ivar_getName(ivar);//获取ivar对应的字段名称
            NSLog(@"ivarName:%@",[NSString stringWithUTF8String:ivarName]);
            /*
             .h:
             property(nonatomic,strong) NSArray * array;
             @property(nonatomic,copy) NSString * string;
             
             .m:
             {
             NSInteger  instant1;
             NSString * instant2;
             }
             @property(nonatomic,assign) NSUInteger integer;
             
             打印:(获取到了所有成员变量)
             ivarName:instant1
             ivarName:instant2
             ivarName:_array
             ivarName:_string
             ivarName:_integer
             
             */
        }
        free(ivars);
        NSLog(@"count:%d",count);//打印:count:5
    
    2-1-2、 获取指定名称实例成员变量的信息
    Ivar class_getInstanceVariable ( Class cls, const char *name );
    
        //获取类中指定名称实例成员变量的信息
        //是这么定义的:@property(nonatomic,strong) NSArray * array;
    
        const char * instantName = "_array";//!!!!!添加了_
        Ivar  ivar = class_getInstanceVariable(myClass.class, instantName);
        NSLog(@"ivarName:%s",ivar_getName(ivar));//打印:ivarName:_array
        
        //是这么定义的:NSInteger  instant1;
        Ivar  ivar1 = class_getInstanceVariable(myClass.class, "instant1");
        NSLog(@"ivarName1:%s",ivar_getName(ivar1));//打印:ivarName1:instant1
        
    

    2-2、 属性操作

    2-2-1 、根据属性名称获取对应的属性
    objc_property_t _Nullable
    class_getProperty(Class _Nullable cls, const char * _Nonnull name)
    
       //是这么定义的:@property(nonatomic,strong) NSArray * array;
        objc_property_t property1 = class_getProperty(myClass.class, "array");
        if (property1) {     
              NSLog(@"property1Name:%s",property_getName(property1));
              //打印:property1Name:array
        }
    
        //是这么定义的NSInteger  instant1;
        objc_property_t property2 = class_getProperty(myClass.class, "instant1");
        if (property2) {//未进入if里面
            NSLog(@"property2Name:%s",property_getName(property1));
        }
    
    • 注意查看2-1-2、2-2-1,获取@property修饰的成员变量,使用class_getInstanceVariable获取时,要加上“_”;使用class_getProperty获取属性时不需要。
    • 属性即@property修饰定义的。@property修饰的才能使用class_getProperty获取到。
    • class_getProperty获取到的是objc_property_t类型的,class_getInstanceVariable获取到的是Ivar类型的。
    2-2-2、 获取属性列表
     unsigned int propertyCount = 0;
        objc_property_t * proprties = class_copyPropertyList(myClass.class, &propertyCount);
        for (int i = 0 ; i < propertyCount; i++) {
            
            objc_property_t property = proprties[i];
            NSLog(@"i:%d,propertyName:%s",i,property_getName(property));
            /*
             打印:
             i:0,propertyName:integer
             i:1,propertyName:array
             i:2,propertyName:string
             */
        }
        NSLog(@"propertyCount:%d",propertyCount);//打印:propertyCount:3
    

    3、方法(methodLists)

    3-1 、获取所有方法

    • 返回包含所有实例方法的数组;
    • 该列表不包含父类实现的方法。
    • outCount参数返回方法的个数。
    • 注意:在获取到列表后,我们需要使用free()方法来释放它。
        unsigned int methodCount = 0;
        Method * methods = class_copyMethodList(myClass.class, &methodCount);
        for (int i = 0; i < methodCount; i++) {
            
            Method method = methods[i];
            NSLog(@"method:%s",method_getName(method));
            /*
             打印:
             method:method1
             method:method2
             method:method3WithArg1:arg2:
             method:integer
             method:setInteger:
             method:setArray:
             method:.cxx_destruct//ARC下对象实例变量的释放过程在.cxx_destruct内完成
             method:setString:
             method:array
             method:string
             
             */
       
        }
        NSLog(@"methodCount:%d",methodCount);//打印: methodCount:10
        free(methods);
    

    结论:
    方法总数 =自定义的方法(不包含类方法)+(数据类型的get方法+数据类型set方法)*有几种数据类型+cxx_destruct
    当前的MyClass类:
    自定义方法 3 + 2 * 3(数据类型有NSArray、NSString、NSUInteger)+1(cxx_destruct方法) = 10个

    3-2 、获取所有类方法

    一个类的类方法是定义在元类里面。
    返回对象的类:Class object_getClass ( id obj );
    如果要获取类方法,则可以使用class_copyMethodList(object_getClass(cls), &count)

        unsigned int classMethodCount = 0;
        Method * classMethods = class_copyMethodList(object_getClass(myClass.class), &classMethodCount);
        for (int i = 0; i < classMethodCount; i++) {
            
            Method  method = classMethods[i];
            NSLog(@"classMethods:%s",method_getName(method));
            /*
             打印:classMethods:classMethod1
             */
        }
        NSLog(@"classMethodCount:%d",classMethodCount);//classMethodCount:1
        free(classMethods);
    

    3-3、 获取类方法

        Method classMethod = class_getClassMethod(myClass.class, @selector(classMethod1));
    

    3-4、 获取实例方法

        Method instantMethod = class_getInstanceMethod(myClass.class, @selector(method1));
    

    与class_copyMethodList不同的是:
    class_getInstanceMethod、class_getClassMethod函数,这两个函数都会去搜索父类的实现。

    3-5、 获取方法的具体实现

     IMP methodImp2 = class_getMethodImplementation_stret(myClass.class, @selector(method2));
     methodImp2();//调用method2成功
    

    class_getMethodImplementation函数,该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分。

    3-6、 添加方法

      //viewcontroller中添加方法:
        -(void)methodForMyClass{
            NSLog(@"methodForMyClass");
        }
      //添加方法
        IMP methodImp = class_getMethodImplementation(self.class, @selector(methodForMyClass));
        class_addMethod(myClass.class, @selector(methodForMyClass), methodImp, "v@:");
        [myClass performSelector:@selector(methodForMyClass) withObject:nil afterDelay:0];//成功执行methodForMyClass方法
    

    3-7、 替代方法的实现:

    • 如果类中不存在name指定的方法,则类似于class_addMethod函数一样会添加方法;
    • 如果类中已经存在name指定的方法,则类似于method_setImplementation一样替代原方法的实现
      //viewcontroller中添加方法:
      -(void)addMethod{
        
          NSLog(@"addMethod");
      }
      
        //替换:(addMethod替换method1里面的实现)
        IMP methodImp3 = class_getMethodImplementation_stret(self.class, @selector(addMethod));
        IMP replaceMethod = class_replaceMethod(myClass.class, @selector(method1), methodImp3, "v@:");//用addMethod替代method1的实现
        [myClass method1];//打印:addMethod
    

    3-8、 类实例是否响应指定的selector:

       BOOL isRespond = class_respondsToSelector(myClass.class, @selector(method1));
       NSLog(@"isRespond:%d",isRespond);
    

    4、协议

    4-1、获取类实现的协议列表

        unsigned int protocolCount = 0;
        Protocol * __unsafe_unretained _Nonnull * protocols = class_copyProtocolList(myClass.class, &protocolCount);
        for (int i = 0; i < protocolCount; i++) {
            
            Protocol * protocol = protocols[i];
            NSLog(@"protocolName:%s",protocol_getName(protocol));
            /*
             打印:protocolName:NSCopying
             */
        }
    

    三、动态创建类和对象

    1、动态创建类

    • 为了创建一个新类,我们需要调用objc_allocateClassPair。
    • 然后使用诸如class_addMethod,class_addIvar等函数来为新创建的类添加方法、实例变量和属性等。
    • 完成这些后需要调用objc_registerClassPair函数来注册类,之后这个新类就可以在程序中使用了。

    注:实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上。

    1-1、动态创建类、元类

    objc_allocateClassPair函数:
    如果我们要创建一个根类,则superclass指定为Nil。
    extraBytes通常指定为0,该参数是分配给类和元类对象尾部的索引ivars的字节数。

        Class newClass = objc_allocateClassPair(NSObject.class, "Student", 0);
    

    1-2、为类添加成员变量

    • 已经有的变量再添加会失败
    • 这个方法只能在objc_allocateClassPair之前,objc_registerClassPair之后调用
    • 不能给一个已经存在的类添加
    • 不能给元类添加
    • 员变量的按字节最小对齐量是1<<alignment
    • 如果变量的类型是指针类型,则传递log2(sizeof(pointer_type))
    BOOL addIVar = class_addIvar(newClass, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "i");  
    

    1-3、为类添加属性

        objc_property_attribute_t type = {"T","@\"NSString\""};
        objc_property_attribute_t ownership = {"C",""};
        objc_property_attribute_t backingivar = {"V","_ivar1"};
        
        const objc_property_attribute_t  properties[] = {type,ownership,backingivar};
        class_addProperty(newClass, "propery", properties, 3);
    
    //添加方法
        IMP method1Imp = class_getMethodImplementation(self.class, @selector(newClassMethod1));
        class_addMethod(newClass, @selector(newClassMethod1), method1Imp, "v@:");
    

    1-4、注册类

        objc_registerClassPair(newClass);
      
       //调用
        id instantce = [[newClass alloc] init];
        [instantce performSelector:@selector(newClassMethod1) withObject:nil afterDelay:0];//执行newClassMethod1成功
    

    1-4、销毁

    objc_disposeClassPair用于销毁一个函数,如果程序中还存在类或其子类的示例,则不能针对该类调用销毁方法。

    objc_disposeClassPair(newClass);
    

    2、动态创建对象/动态创建类的实例

    2-1、 创建类实例:

    • id class_createInstance ( Class cls, size_t extraBytes );
      extraBytes参数表示分配的额外字节数。
      效果与+alloc方法类似

    • 使用class_createInstance函数获取的是NSString实例,而不是类簇中的默认占位符类__NSCFConstantString

    id theObject = class_createInstance(NSString.class, sizeof(unsigned));
    id str1 = [theObject init];
    NSLog(@"%@", [str1 class]);//打印:NSString
    
    id str2 = [[NSString alloc] initWithString:@"test"];
    NSLog(@"%@", [str2 class]);//打印:__NSCFConstantString
    

    2-2、 在指定位置创建类实例

    id objc_constructInstance ( Class cls, void *bytes );
    

    2-3、 销毁类实例:

    不会释放并移除任何与其相关的引用

    void * objc_destructInstance ( id obj );
    

    3、实例操作函数

    3-1、针对整个对象进行操作的函数

    // 返回指定对象的一份拷贝
    id object_copy ( id obj, size_t size );
    // 释放指定对象占用的内存
    id object_dispose ( id obj );
    
    NSObject *a = [[NSObject alloc] init];
    id newB = object_copy(a, class_getInstanceSize(MyClass.class));
    object_setClass(newB, MyClass.class);
    object_dispose(a);
    

    3-2、针对对象实例变量进行操作的函数

      // 修改类实例的实例变量的值
      Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
      // 获取对象实例变量的值
      Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
      // 返回指向给定对象分配的任何额外字节的指针
      void * object_getIndexedIvars ( id obj );
      // 返回对象中实例变量的值
      id object_getIvar ( id obj, Ivar ivar );
      // 设置对象中实例变量的值
      void object_setIvar ( id obj, Ivar ivar, id value );
    

    实例变量的Ivar已经知道,那么调用object_getIvar会比object_getInstanceVariable函数快,相同情况下,object_setIvar也比object_setInstanceVariable快

    3-3、针对对象的类进行操作的函数

      // 返回给定对象的类名
      const char * object_getClassName ( id obj );
      // 返回对象的类
      Class object_getClass ( id obj );
      // 设置对象的类
      Class object_setClass ( id obj, Class cls );
    

    4、获取类定义

    • OC动态运行库会自动注册代码中定义的所有类。我们可以在运行时创建类定义并使用objc_addClass函数来注册他们。
    • 如果类在运行时未注册:
      objc_lookUpClass会返回nil;
      objc_getClass会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil;
      objc_getRequiredClass函数的操作与objc_getClass相同,只不过如果没有找到类,则会杀死进程;
      // 获取已注册的类定义的列表;
      //我们不能假设从该函数中获取的类对象是继承自NSObject体系的,所以在这些类上调用方法是,都应该先检测一下这个方法是否在这个类中实现。
      int objc_getClassList ( Class *buffer, int bufferCount );
      // 创建并返回一个指向所有已注册类的指针列表
      Class * objc_copyClassList ( unsigned int *outCount );
      // 返回指定类的类定义
      Class objc_lookUpClass ( const char *name );
      Class objc_getClass ( const char *name );
      Class objc_getRequiredClass ( const char *name );
      // 返回指定类的元类
      // 如果指定的类没有注册,则该函数会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。不过,每个类定义都必须有一个有效的元类定义,所以这个函数总是会返回一个元类定义,不管它是否有效。
      Class objc_getMetaClass ( const char *name );
    

    eg:

    int numClasses;
    Class * classes = NULL;
    numClasses = objc_getClassList(NULL, 0);
    if (numClasses > 0) {
        classes = malloc(sizeof(Class) * numClasses);
        numClasses = objc_getClassList(classes, numClasses);
        NSLog(@"number of classes: %d", numClasses);
        for (int i = 0; i < numClasses; i++) {
            Class cls = classes[i];
            NSLog(@"class name: %s", class_getName(cls));
        }
        free(classes);
    }
    

    Runtime笔记二:关联对象Associated Object

    上述笔记主要是参考:Objective-C Runtime 运行时之一:类与对象
    个人根据自己的理解,把举例部分和注意事项合并到一起。方便查看。
    部分代码后续上传到github。

    相关文章

      网友评论

        本文标题:Runtime笔记一:类与对象

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