iOS底层探索之类的结构(下)

类的结构探索分析.png

在之前的几篇博客里面,已经介绍了,类的底层结构,还有isa的走位流程,元类的继承链,对象方法,类方法的存放位置,以及如何通过lldb获取成员和属性、对象方法,类方法。

iOS底层探索之类的结构(上)

iOS底层探索之类的结构(中)

本篇博客主要做一些补充和扩展。

成员变量和属性

开发年限比较久的iOS程序员,都知道在iOS5之前,经常看到一个大括号里面定义了成员变量,同时用了@property声明,而且还在@implementation中使用@synthesize方法。

其实,发生这种状况根本原因是苹果将默认编译器从GCC转换为LLVM(low level virtual machine),才不再需要为属性声明实例变量了。

在没有更改之前,属性的正常写法需要成员变量+ @property + @synthesize成员变量三个步骤。 更换为LLVM之后,编译器在编译过程中发现没有新的实例变量后,就会生成一个下划线开头的实例变量。因此现在我们不必在声明一个实例变量。

现在@property声明的属性不仅仅默认给我们生成一个_类型的成员变量,同时也会生成setter/getter方法。

类信息

clang源码

  • 属性 = 带下划线成员变量 + setter + getter ⽅法
  • 实例变量 : 特殊的成员变量 (类的实例化)

从以上clang底层源码也可以验证,@property声明的属性默认会生成一个_类型的成员变量,同时也会生成setter/getter方法。

objc_setProperty

但是我们从上面的底层源码发现了一个问题,有的set方法里面是通过objc_setProperty获取属性,有的是通过内存的平移。这是什么原因了呢?通过对比不同属性设置带有copy和不带有copy的底层源码分析,可以找到原因是copy这个设置的问题!那么为验证这个结论,我们去底层看看!

LLVM

那我们去llvm源码里面看看,objc_setProperty的实现逻辑,你怎么就知道去llvm不是去objc源码看呢?请看下图分析

在这里插入图片描述

首先在底层也就是下层,苹果的源码是固定的,是不会改变的(这里的不变是指不会根据你代码来改变,因为底层逻辑已经写好了)。在上层,我们程序开发人员,会写很多中属性,那么就会有各种各种的奇奇怪怪的代码出现,就有各种的set形式的代码,比如setName、setAge、setXX。。。等等。

这对编译器来说,只是_cmd的方法名字不一样,那就需要一个中间层来把这些奇奇怪怪的,做一下处理,以便于和底层去交互,因为底层逻辑已经封装好,不可能动态的改变的。那我们去研究objc_setProperty就不可能在运行时了,只能在编译时了,所以我们去就llvm里面看看!

那上层和objc_setProperty之间有着怎样的联系呢?

getGetPropertyFn

我们在llvm里面找到了getGetPropertyFn这个函数,是获取创建的CreateRuntimeFunction,里面有objc_setProperty这个参数。那么为什么创建呢?继续往下看看,在哪里调用了
getGetPropertyFn()

既然调用了,肯定是有条件的,是真的是是因为propertycopy吗?我们找到条件,就可以反推知道objc_setProperty的作用了。继续往下看
PropertyImplStrategy

看看上面的代码有没有发现什么?这有个属性imp的策略PropertyImplStrategy,那么再继续往下找
PropertyImplStrategy

看到没有,如果IsCopy为真就Kind = GetSetProperty,这也就验证了博客开篇的猜想,只有属性里面用copy修饰的,才会有objc_setProperty

代码获取类的信息

iOS底层探索之类的结构(上)
iOS底层探索之类的结构(中)
在之前的博客里面,我们已经知道了,可以在控制台通过lldb获取类的信息,那么现在我们通过代码来获取下。

class_copyMethodList

void lgObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[i];
        //获取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        
        LGLog(@"Method, name: %@", key);
    }
    free(methods);
}

class_getInstanceMethod

//获取实例方法
void lgInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

class_getClassMethod

//获取类方法
void lgClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

//    - (void)sayHello;
//    + (void)sayHappy;
    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

class_getMethodImplementation

void lgIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));// 0
    // sel -> imp 方法的查找流程 imp_farw
    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy)); // 0
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);

}

代码测试验证


代码测试验证

IMP,SEL

  • SEL : 类成员方法的指针,但不同于C语言中的函数指针,函数指针直接保存了方法的地址,但SEL只是方法编号。
  • IMP:一个函数指针,保存了方法的地址
  • SEL和IMP的关系就:SEL就相当于书本的⽬录标题,IMP就是书本的⻚码,函数就是具体页码对应的内容。

扩展

我们已经学了这么多类的相关的知识了,那么我们看看下面这个面试题

    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; 
    BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; 
    BOOL re3 = [(id)[JPPerson class] isKindOfClass:[JPPerson class]];    
    BOOL re4 = [(id)[JPPerson class] isMemberOfClass:[JPPerson class]];    
 
    NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n,%hhd,%hhd",re1,re2,re3,re4,re9,re10);

    BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       
    BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     
    BOOL re7 = [(id)[JPPerson alloc] isKindOfClass:[JPPerson class]];       
    BOOL re8 = [(id)[JPPerson alloc] isMemberOfClass:[JPPerson class]];     
    NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

代码运行结果


代码运行结果

+ isKindOfClass

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- isKindOfClass

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

+ isMemberOfClass

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- isMemberOfClass

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
- (Class)class {
    return object_getClass(self);
}

分析

  • re1 传入的是NSObject的类,获取元类NSObject不等,继续寻找获取元类的父类为NSObject与传入的值相等,返回true
  • re2是NSObject类调用类方法isMemberOfClassNSObject类比较,很明显,NSObject的元类NSObject本身并不相等,所以返回false
  • re3 传入的是JPPerson的类,获取元类JPPerson不等,继续寻找获取元类的父类为NSObject的元类,与传入的值依旧不等,继续往上NSObject元类的父类为NSObject依旧不等,再往上就是nil,最后返回false
    -re4JPPerson类调用类方法isMemberOfClassJPPerson类比较,JPPerson的元类JPPerson本身并不相等,所以返回false.
  • re5 传入的是NSObject的实例,获取对象的类,与NSObject相等,返回true
  • re7 传入的是JPPerson的实例,获取对象的类,与JPPerson相等,返回true
    -re8JPPerson的实例调用实例方法isMemberOfClassJPPerson类比较,明显的他们是相同的,所以返回true

更多内容持续更新

🌹 请动动你的小手,点个赞👍🌹

🌹 喜欢的可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我,哈哈😁🌹

🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容