iOS-Runtime机制

Runtime笔记一:类与对象

2018-05-15  本文已影响22人  蔚尼

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

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)

//我们在给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及属性

2-1、 成员变量

2-1-1、获取整个成员变量列表
  Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
   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-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 、获取所有方法

    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、 替代方法的实现:

  //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、动态创建类

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

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

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

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

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

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 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、获取类定义

  // 获取已注册的类定义的列表;
  //我们不能假设从该函数中获取的类对象是继承自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。

上一篇下一篇

猜你喜欢

热点阅读