Objective-C基础之七(Runtime用法)

super关键字

super实际上是OC为咱们提供的一个关键字,主要是继承体系中用来调用类从父类继承过来的属性和方法,它只是一个标记,若是是使用super去调用方法,本质其实仍是拿到当前类对象,而后从其父类的缓存和方法列表进行查找。下面咱们就经过源码来进一步探索super的底层实现。git

源码解读

  • 首先,先建立XLPerson类,而且在XLPerson.m中增长以下方法:
- (instancetype)init
{
    self = [super init];
    if (self) {
        NSLog(@"init");
    }
    return self;
}
复制代码
  • 而后经过如下指令,将XLPerson.m转成C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc XLPerson.m
复制代码
  • 查看生成的XLPerson.cpp文件,找到对应的init方法以下:
static instancetype _I_XLPerson_init(XLPerson * self, SEL _cmd) {
    self = objc_msgSendSuper((__rw_objc_super){
        (id)self,
        (id)class_getSuperclass(objc_getClass("XLPerson"))
    }, sel_registerName("init"));
    if (self) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f3_lg91hwts5rjdlzjph0sn82m80000gp_T_XLPerson_d841fc_mi_0);
    }
    return self;
}
复制代码
  • 能够发现,在init方法中,[super init]最终转换成了objc_msgSendSuper函数,函数具备两个参数。github

  • 而后到objc源码中查找objc_msgSendSuper函数的实现,发如今源码中存在objc_msgSendSuper和objc_msgSendSuper2两个函数,那么到底super最终执行的是哪一个函数呢?能够经过Xcode断点,查看汇编代码来找到最终调用的方法。数组

    • 首先咱们在XLPerson的init函数中打个断点,而后在main函数中建立XLPerson对象,而且打开Xcode如下设置缓存

    • 而后运行程序,查看汇编代码,能够明确看出,super最终调用的是objc_msgSendSuper2函数bash

  • 而后在源码中查看objc_msgSendSuper2函数声明以下,objc_msgSendSuper2的前两个参数分别对应上文中的结构体__rw_objc_super和sel_registerName("init")markdown

OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
复制代码
  • 查看结构体objc_super的源码以下,其中objc_super有两个成员变量,一个是receiver,对应上文结构体中的self。一个是super_class,对应上文结构体中的class_getSuperclass(objc_getClass("XLPerson")。
/// Specifies the superclass of an instance. 
struct objc_super {
    //class的实例对象,用来接收消息
    __unsafe_unretained _Nonnull id receiver;
    //父类对象,用来决定方法查找的起点
    __unsafe_unretained _Nonnull Class super_class;
    /* super_class is the first class to search */
};
复制代码
  • 虽然objc_msgSendSuper2函数声明中第一个参数是objc_super类型的结构体,可是在实际传参过程中,objc_msgSendSuper2函数第一个参数传递的实际上是objc_super2类型的结构体,在下文的汇编代码中能够看出。
struct objc_super2 {
    //当前消息接收者,实例对象
    id receiver;
    //当前类对象
    Class current_class;
};
复制代码
  • 再次查找objc_msgSendSuper2函数的实现,发现objc_msgSendSuper2函数是由汇编来实现的,汇编代码以下
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
//p0其实就是x0寄存器,p16其实就是x16寄存器
//p0中存放着真正的消息接收者,而p16存放着当前class对象
ldp	p0, p16, [x0]		// p0 = real receiver, p16 = class
//拿到x16中保存的class对象地址,经过地址偏移拿到它的superclass的地址,从新赋值给p16寄存器
ldr	p16, [x16, #SUPERCLASS] // p16 = class->superclass
//调用CacheLookup进行方法查找,而p16就是CacheLookup的参数
CacheLookup NORMAL

END_ENTRY _objc_msgSendSuper2
复制代码

在objc_msgSendSuper2中,第一个参数是一个objc_super2类型的结构体。在arm64汇编中,x0寄存器通常用来存放参数或者返回值,此处的x0就存放告终构体objc_super2的地址值。架构

ldp p0, p16, [x0]表示从x0寄存器里存放的地址开始,取前8个字节的地址赋值给p0,取后8个字节的地址赋值给p16,此时,p0寄存器的值就是objc_super2结构体中receiver的地址,p16的值就是objc_super2结构体中current_class的地址。app

ldr p16, [x16, #SUPERCLASS]其实就是拿到current_class的superclass,[x16, #SUPERCLASS]其实就是将x16中存放的内存地址偏移8个字节。而Class底层结构中,第一个成员变量是isa指针,占用8个字节,第二个成员就是superclass。而x16里存放的地址值就是Class的地址值,偏移8个字节其实就是拿到了superclass指针。这也印证了上文中所说的,objc_msgSendSuper2函数的第一个参数实际上是objc_super2类型的结构体。iphone

SUPERCLASS其实就是当前架构下指针所占字节数,在arm64架构中,指针类型占8个字节。函数

拿到superclass指针以后,将superclass的地址值存放在p16寄存器中,而p16寄存器的值就是CacheLookup函数的参数,CacheLookup会到superclass的方法列表中去查找对应的方法。可是真正的消息接收者仍是当前的receiver。

super总结

  • 经过super来调用方法时,底层会转换成objc_msgSendSuper2函数
  • objc_msgSendSuper2函数函数传递至少两个参数,第一个参数是一个objc_super2类型的结构体,内部有两个成员,第一个成员是当前实例对象自己receiver,第二个成员是当前实例对象对应的类对象current_class。
  • 当调用[super xxxx]方法方法时,其实不是给receiver的superclass发送消息,而是给当前的receiver发送消息。可是在执行消息查找时,会首先拿到current_class的superclass,而后到superclass的方法缓存和方法列表中查询方法。
  • 也就是说,objc_super2的第一个成员变量receiver决定了谁是真正的消息接收者,而第二个成员变量current_class的superclass其实就决定了当前消息从哪里开始进行查找。

通常状况下,给对象receiver发送一个消息,首先会到receiver的缓存列表或者方法列表中去查找,找不到才会到superclass中去查找,而super则表示首先会给receiver发送一个消息,可是会先到recever的superclass中进行方法查找。这就是为何使用super会调用父类方法。

Demo解析

首先建立XLPerson类,而后建立XLTeacher类继承自XLPerson类,而后在XLTeacher.m中增长以下代码

@implementation XLTeacher

- (instancetype)init
{
    self = [super init];
    if (self) {
        NSLog(@"%@", [self class]);     
        NSLog(@"%@", [super class]);    
        
        NSLog(@"%@", [self superclass]);
        NSLog(@"%@", [super superclass]);
    }
    return self;
}

@end
复制代码

要想知道打印结果,还须要知道class和superclass的底层实现,在objc源码的NSObject.mm中能够看到具体的实现,以下

@implementation NSObject

+ (id)self {
    return (id)self;
}

- (id)self {
    return self;
}

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

+ (Class)superclass {
    return self->superclass;
}

- (Class)superclass {
    return [self class]->superclass;
}

@end
复制代码
  1. [self class],经过self(当前XLTeacher的实例对象)调用class方法其实就是给self发送一条@selector(class)消息,因为XLTeacher的实例对象以及它的父类都没有实现class方法,因此最终会查找到基类NSObject的方法列表中,最终找到方法实现,返回object_getClass(self);,也就是当前类对象XLTeacher。
  2. [super class],其实也是向self发送一条@selector(class)消息,只不过会先到XLTea的父类中去查找方法实现,因为父类没有实现class方法,因此最终找到基类NSObject的class方法,返回object_getClass(self);,结果也是XLTeacher。
  3. [self superclass],向self发送一条@selector(superclass),首先会在XLTeacher的缓存和方法列表中查找,XLTeacher没有实现此方法,所以会到XLTeacher的superclass中查找,父类也未实现,最后会找到NSObject中的superclass方法,返回[self class]->superclass,也就是XLPerson。
  4. [super superclass],利用super调用superclass方法,其实仍是向self发送@selector(superclass)消息,可是会直接先从XLTeacher的父类XLPerson中查找方法实现,因为XLPerson未实现此方法,最终会调用NSObject的superclass方法,返回[self class]->superclass,也就是XLPerson。

isKindOfClass和isMemberOfClass

源码解析

首先,查看源码中NSObject.mm的实现

@implementation NSObject
+ (BOOL)isMemberOfClass:(Class)cls {
    //经过object_getClass获取当前类对象的isa所指向的元类对象与cls进行比较
    return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    //比较当前实例对象的isa所指向的类对象与cls
    return [self class] == cls;
}

+ (BOOL)isKindOfClass:(Class)cls {  
    //遍历当前类对象的元类对象以及它的父类的元类对象,若是和cls相等就返回YES
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    //遍历当前实例对象所对应的类对象以及它的父类,若是和cls相等就返回YES
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
@end
复制代码
  • 类方法+isMemberOfClass:是经过isa获取当前类对象的元类对象,与参数中的对象进行对比,若是相等则返回YES。
  • 对象方法-isMemberOfClass:是经过isa获取当前实例对象的类对象,与参数中的对象进行对比,若是相等,则返回YES。
  • 类方法+isKindOfClass:经过遍历当前类对象所对应的元类对象以及父类所对应的元类对象,若是遍历到的元类对象与参数中的对象相等,则返回YES。
  • 对象方法-isKindOfClass:经过遍历当前实例对象对应的类对象以及它的父类,若是遍历到的类对象和参数中的对象相等,则返回YES。

Demo解析

建立XLPerson对象,继承自NSObject。而后在main函数中增长如下代码

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        BOOL result1 = [NSObject isMemberOfClass:[NSObject class]];
        BOOL result2 = [NSObject isKindOfClass:[NSObject class]];
        BOOL result3 = [XLPerson isMemberOfClass:[XLPerson class]];
        BOOL result4 = [XLPerson isKindOfClass:[XLPerson class]];
        
        NSLog(@"%d %d %d %d", result1, result2, result3, result4);
        
        XLPerson *person = [[XLPerson alloc] init];
        BOOL result5 = [person isMemberOfClass:[NSObject class]];
        BOOL result6 = [person isKindOfClass:[NSObject class]];
        BOOL result7 = [person isMemberOfClass:[XLPerson class]];
        BOOL result8 = [person isKindOfClass:[XLPerson class]];
        
        NSLog(@"%d %d %d %d", result5, result6, result7, result8);
    }
    return 0;
}
复制代码

类方法

  1. [NSObject isMemberOfClass:[NSObject class]]会返回NO,缘由是object_getClass(NSObject)拿到的是元类对象,而[NSObject class]是类对象,二者不相等。
  2. [NSObject isKindOfClass:[NSObject class]]会返回YES,本来获取NSObject的元类对象和[NSObject class]来进行对比,应该是不相等,可是比较特殊的一点是NSObject的元类对象的superclass指向它的类对象,因此此处返回YES。此处能够参考Objective-C基础之一(深刻理解OC对象)中isa和superclass的总结。
  3. [XLPerson isMemberOfClass:[XLPerson class]]会返回NO,获取到XLPerson的元类对象和[XLPerson class]进行比较,二者不相等。
  4. [XLPerson isKindOfClass:[XLPerson class]]会返回NO,首先会拿到XLPerson的元类对象和[XLPerson class]对比,发现不相等,而后会经过XLPerson的superclass拿到父类的元类和[XLPerson class]进行对比,发现仍是不相等,一直遍历到基类NSObject的元类,NSObject的元类的superclass指向NSObject的类对象,所以最后一次遍历是拿NSObject的类对象和[XLPerson class]进行比较,确定不相等,因此返回NO。

实例方法

  1. [person isMemberOfClass:[NSObject class]]返回NO,缘由是拿person的类对象和NSObject比较,显然不相等。
  2. [person isKindOfClass:[NSObject class]]返回YES,由于XLPerson本来就是继承自NSObject,而isKindOfClass则是经过继承链最终能找到NSObject的类对象和参数[NSObject class]进行对比,二者相等。
  3. [person isMemberOfClass:[XLPerson class]]返回YES,person的类对象就是XLPerson。
  4. [person isKindOfClass:[XLPerson class]]返回YES,缘由同上。

Runtime应用

使用runtime查看私有成员变量

利用runtime,咱们能够遍历类的全部属性或者成员变量,拿到属性或者成员变量咱们就能够作不少事情,好比结合KVC给系统类对象的私有属性赋值,或者将字典转换成模型等等。

访问系统类的私有属性而且赋值

想给系统类的私有属性赋值,前提是须要知道系统类有哪些私有属性,首先,建立NSObject的分类NSObject+Ivar,以下

@interface NSObject (Ivar)

+ (void)logIvar;

@end

@implementation NSObject (Ivar)

+ (void)logIvar{
    unsigned int count;
    Ivar *ivars = class_copyIvarList(self, &count);
    for (int i = 0; i < count; i++) {
        //取出成员变量
        Ivar ivar = ivars[i];
        NSString *ivarName = [[NSString alloc] initWithUTF8String:ivar_getName(ivar)];
        NSLog(@"%@", ivarName);
    }
    free(ivars);
}

@end
复制代码

而后以UITextField为例,调用[UITextField logIvar]方法,就能够打印出UITextField的全部成员变量,假如咱们须要修改输入框提示语的文字颜色,就能够找到其中的_placeholderLabel,而后经过KVC来拿到对应的属性进行设置

UITextField *field = [[UITextField alloc] initWithFrame:CGRectMake(100, 100, 200, 30)];
field.placeholder = @"我是提示语";
[self.view addSubview:field];
[field setValue:[UIColor redColor] forKey:@"_placeholderLabel.textColor"];
复制代码

在iOS 13以后是不容许访问系统类的私有成员变量的,此处仅仅是用做功能演示,运行会报错。

字典转模型

OC开发过程当中,涉及到不少字典转模型的需求,尤为是和后台进行交互。若是不使用runtime的话,就须要本身建立方法,本身去实现。以下

@interface XLPerson : NSObject

@property(nonatomic, copy)NSString *name;
@property(nonatomic, assign)int age;

+ (instancetype)personWithDic:(NSDictionary *)dic;

@end

@implementation XLPerson

+ (instancetype)personWithDic:(NSDictionary *)dic{
    XLPerson *person = [[XLPerson alloc] init];
    person.name = dic[@"name"];
    person.age = [dic[@"age"] intValue];
    
    return person;
}

@end
复制代码

上述方法能够实现字典转模型的需求,可是若是存在字典套字典的这种状况,那么上述方法实现起来就会很是麻烦,所以,runtime的做用就体现了出来,典型的字典转模型的工具如MJExtension也是利用runtime来实现。

建立NSObject的分类NSObject+Json,而后增长以下方法

@interface NSObject (Json)

+ (instancetype)objectWithDictionary:(NSDictionary *)dictionary;

@end

@implementation NSObject (Json)

+ (instancetype)objectWithDictionary:(NSDictionary *)dictionary{
    id object = [[self alloc] init];
    unsigned int count;
    Ivar *ivars = class_copyIvarList(self, &count);
    for (int i = 0; i < count; i++) {
        //取出成员变量
        Ivar ivar = ivars[i];
        NSMutableString *ivarName = [[NSMutableString alloc] initWithUTF8String:ivar_getName(ivar)];
        //移除字符串以前的_
        [ivarName deleteCharactersInRange:NSMakeRange(0, 1)];
        
        //给对应的属性设值
        id value = dictionary[ivarName];
        if (value) {
            [object setValue:value forKey:ivarName];
        }
    }
    free(ivars);
    return object;
}

@end
复制代码

使用方式以下

NSDictionary *dic = @{@"name" : @"张三", @"age" : @12};
XLPerson *person = [XLPerson objectWithDictionary:dic];
NSLog(@"%@ %d", person.name, person.age);
复制代码

若是想要实现字典嵌套字典转模型的方法,能够参考MJExtension的实现。

runtime之方法交换(Method Swizzing)

以前的文章中说到过,类的全部对象方法都是存放在类对象的方法列表中,也就是存放在objc_class中的class_rw_t中,class_rw_t的源码以下

struct class_rw_t {
    uint32_t flags;             //用来存放类的一些基本信息
    uint32_t version;           //版本号

    const class_ro_t *ro;       //class_ro_t类型指针

    method_array_t methods;     //方法列表
    property_array_t properties;//属性列表
    protocol_array_t protocols; //协议列表
}

复制代码

method_array_t是一个二维数组,内部存放了method_list_t,每一个method_list_t都存放着一个分类的全部方法,二维数组的最后一个method_list_t则存放着类编译时就生成的全部方法,而method_list_t中存放的就是method_t,每个方法对应一个method_t,结构以下

struct method_t {
    SEL name;           //方法选择器(方法名)
    const char *types;  //方法签名
    MethodListIMP imp;  //方法实现的地址
}
复制代码

在method_t中,imp则表示方法实现的地址,所以,若是想实现方法交换,只须要修改imp便可,而runtime就是经过交换两个method_t的imp来实现方法交换的。

建立XLPerson类,以下

@interface XLPerson : NSObject

- (void)run;
- (void)sleep;

@end

@implementation XLPerson

+ (void)initialize
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method methodRun = class_getInstanceMethod(self, @selector(run));
        Method methodSleep = class_getInstanceMethod(self, @selector(sleep));
        method_exchangeImplementations(methodRun, methodSleep);
    });
}

- (void)run{
    
    NSLog(@"%s", __func__);
}

- (void)sleep{
    NSLog(@"%s", __func__);
}
@end

复制代码

调用run方法和sleep方法会发现二者的实现被交换了,此处展现的是对象方法的交换,类方法交换和对象方法交换相似,更多方法交换实现能够参考Aspect

runtime经常使用Api

动态类

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

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

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

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

//设置isa指向的Class
Class object_setClass(id obj, Class cls)

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

//判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)

//获取父类
Class class_getSuperclass(Class cls)

复制代码

成员变量

//获取一个实例变量信息
Ivar class_getInstanceVariable(Class cls, const char *name)

//拷贝实例变量列表(最后须要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

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

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

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

复制代码

属性操做

//获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)

//拷贝属性列表(最后须要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

//动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                  unsigned int attributeCount)

//动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                      unsigned int attributeCount)

//获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
复制代码

方法操做

//得到一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)

//方法实现相关操做
IMP class_getMethodImplementation(Class cls, SEL name) 
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2) 

//拷贝方法列表(最后须要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)

//动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

//动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

//获取方法的相关信息(带有copy的须要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)

//选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)

//用block做为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
复制代码
相关文章
相关标签/搜索