67

iOS源码解析:runtime runtime的API

 5 years ago
source link: http://www.cocoachina.com/ios/20190213/26316.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

runtime的API有很多,内容很丰富,下面按照类方法,成员变量相关方法,属性相关方法,方法相关等进行说明。

Runtime API01 - 类

与类相关的API有下列这些:

    //动态创建一个类(参数:父类,类名,额外的内存空间)
    Class objc_allocateClassPair(Class  _Nullable __unsafe_unretained superclass, const char * _Nonnull name, size_t extraBytes);    
    //注册一个类(要在类注册之前添加成员变量)
    void objc_registerClassPair(Class  _Nonnull __unsafe_unretained cls);    
    //销毁一个类
    void objc_disposeClassPair(Class  _Nonnull __unsafe_unretained cls);    
    //获取isa指向的Class
    Class object_getClass(id  _Nullable obj);    
    //设置isa指向的Class
    Class object_setClass(id  _Nullable obj, Class  _Nonnull __unsafe_unretained cls);    
    //判断一个OC对象是否为Class
    BOOL object_isClass(id  _Nullable obj);    
    //判断一个Class是否为元类
    BOOL class_isMetaClass(Class  _Nullable __unsafe_unretained cls);    
    //获取父类
    CLass class_getSuperclass(Class  _Nullable __unsafe_unretained cls);

首先看后面几个API。

//获取isa指向的Class
    Class object_getClass(id  _Nullable obj);

这个API是获取传入对象的isa指针指向的对象,传入的是实例对象则返回类对象,传入的是类对象则返回元类对象。

 //设置isa指向的Class
    Class object_setClass(id  _Nullable obj, Class  _Nonnull __unsafe_unretained cls);

这个API是改变一个对象的isa指针的指向,比如下面代码:

Person *girl = [[Person alloc] init];
    object_setClass(girl, [Student class]);
    NSLog(@"%@", [girl class]);

调用 object_setClass() 使girl这个实例对象的isa指针指向Student类对象,所以打印的结果是:

Student
  //判断一个OC对象是否为Class
    BOOL object_isClass(id  _Nullable obj);

这个API用来判断传入的对象是否为类对象,由于元类对象也是类对象的一种,所以这里实际是判断传入的对象是否为类对象或元类对象,实例代码如下:

  Person *girl = [[Person alloc] init];
    Class girlClass = [girl class];
    Class girlMetaClass = object_getClass(girlClass);
    NSLog(@"%d, %d, %d", object_isClass(girl), object_isClass(girlClass), object_isClass(girlMetaClass));

打印结果如下:

0, 1, 1
//判断一个Class是否为元类
    BOOL class_isMetaClass(Class  _Nullable __unsafe_unretained cls);

这个API用来判断传入的类对象是否是元类对象,实例代码如下:

  Person *girl = [[Person alloc] init];
    Class girlClass = [girl class];
    Class girlMetaClass = object_getClass(girlClass);
    NSLog(@"%d, %d", class_isMetaClass(girlClass), class_isMetaClass(girlMetaClass));

打印结果:

0, 1
//获取父类
    CLass class_getSuperclass(Class  _Nullable __unsafe_unretained cls);

这个API是传入一个类对象,获取一个父类对象,例如下面的代码:

 Person *girl = [[Person alloc] init];
    Class girlClass = [girl class];
    NSLog(@"%@", class_getSuperclass(girlClass));

打印结果是:

NSObject

接下里再来看前面的三个API:

//动态创建一个类(参数:父类,类名,额外的内存空间)
    Class objc_allocateClassPair(Class  _Nullable __unsafe_unretained superclass, const char * _Nonnull name, size_t extraBytes);

    //注册一个类(要在类注册之前添加成员变量)
    void objc_registerClassPair(Class  _Nonnull __unsafe_unretained cls);

    //销毁一个类
    void objc_disposeClassPair(Class  _Nonnull __unsafe_unretained cls);

这三个API结合起来动态创建和销毁一个类。

第一个API中的参数分别是要创建的这个类要继承自哪个父类,类名,额外的内存空间(一般传0)。

所以可以像下面这样动态创建一个类:

Class teacherClass = objc_allocateClassPair([Person class], "Teacher", 0);

这就是创建了一个继承自Person类的子类,名称为Teacher。

然后我们可以使用下列API给新创建的类添加成员变量:

BOOL
class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, 
              uint8_t alignment, const char * _Nullable types)

这里的参数中,第一个参数是要传入类对象,第二个参数是成员变量名,第三个是成员变量占的内存空间,第四个是内存对齐,第五个是成员变量的类型,我们可以这样添加成员变量:

//创建了_age和_weight这两个成员变量
    class_addIvar(teacherClass, "_age", 4, 1, @encode(int));
    class_addIvar(teacherClass, "_weight", 4, 1, @encode(int));

我们还可以使用下列API给新创建的类添加方法:

BOOL
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types)

可以这样来添加:

Method method = class_getInstanceMethod([self class], @selector(teach));
    IMP imp = method_getImplementation(method);
    class_addMethod(teacherClass, @selector(teach), imp, "v16@0:8");

这样一来就为新创建的Teacher类添加了成员变量和方法。

添加完成员变量和方法后,我们再去注册这个类:

objc_registerClassPair(teacherClass);

那么我们怎么去给成员变量赋值和取值呢?由于没有set和get方法,所以肯定是不能直接取值和赋值,需要通过KVC来取值和赋值:

 id teacher = [[teacherClass alloc] init];
    [teacher setValue:@10 forKey:@"_age"];
    [teacher setValue:@20 forKey:@"_weight"];

    [teacher teach];
    NSLog(@"%@, %@", [teacher valueForKey:@"_age"], [teacher valueForKey:@"_weight"]);

Runtime API02 - 成员变量

这一部分主要讲与成员变量相关的API,主要有下列这些:

//获取一个成员变量
    Ivar class_getInstanceVariable(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name);

    //拷贝成员变量列表
    Ivar *class_copyIvarList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);

    //设置和获取成员变量的值
    void object_setIvar(id  _Nullable obj, Ivar  _Nonnull ivar, id  _Nullable value);
    id object_getIvar(id  _Nullable obj, Ivar  _Nonnull ivar);

    //动态添加成员变量(已经注册的类不能动态添加成员变量)
    BOOL class_addIvar(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types);

    //获取成员变量的相关信息
    const char *ivar_getName(Ivar  _Nonnull v);
    const char *ivar_getTypeEncoding(Ivar  _Nonnull v);

下面我们一个一个来分析:

//获取一个成员变量
    Ivar class_getInstanceVariable(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name);

这个就是传入类对象和成员变量的名字,返回一个Ivar类型的成员变量:

Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
 //拷贝成员变量列表
    Ivar *class_copyIvarList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);

这个API是返回这个类的所有成员变量的一个列表。这个API可以和下面这个API结合使用来打印类的所以成员变量:

//获取成员变量的相关信息
    const char *ivar_getName(Ivar  _Nonnull v);
    const char *ivar_getTypeEncoding(Ivar  _Nonnull v);
  unsigned int count;
    Ivar *ivarList = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivarList[i];
       const char *ivarName = ivar_getName(ivar);
        NSLog(@"%s", ivarName);
    }
    free(ivarList);

这样就会打印类的所有成员变量的值

//设置和获取成员变量的值
    void object_setIvar(id  _Nullable obj, Ivar  _Nonnull ivar, id  _Nullable value);
    id object_getIvar(id  _Nullable obj, Ivar  _Nonnull ivar);

这是给成员变量设值和取值,用法如下:

 Person *person = [[Person alloc] init];
    Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
    object_setIvar(person, nameIvar, @"xiaoli");
    id name = object_getIvar(person, nameIvar);
//动态添加成员变量(已经注册的类不能动态添加成员变量)
    BOOL class_addIvar(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types);

给类动态添加成员变量,这个之前已经使用过,只是要说明的是,不能在类注册后去动态添加成员变量,因为成员变量是存放在class_ro_t结构体中的,是不能修改的,因此一旦类注册成功后,class_ro_t结构体就不能修改,也就不能再添加成员变量。

简单应用

下面讲一个利用这类API的实际应用。首先拖拽一个UITextField到Main.storyboard,命名为textField。然后设置这个输入框的占位符:

self.textField.placeholder = @"请输入文字";

然后我们运行代码,发现占位文字显示出来了,但是是灰色,这种占位文字的颜色并不是我想要的,我想要改变占位文字的颜色,但是我在UITextField的API中又没有找到相关的API,怎么办呢?我想到这个占位文字这里的设计应该是用了一个UILabel,如果我真的找到了这个label,那么我就可以利用KVC去修改其textColor属性,我利用runtime的API打印一下其成员变量,看看有没有我要找的这个label:

 unsigned int count;
    Ivar *ivarList = class_copyIvarList([self.textField class], &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivarList[i];
       const char *ivarName = ivar_getName(ivar);
        NSLog(@"%s", ivarName);
    }
    free(ivarList);

打印结果:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK