iOS 底层原理 iOS 进阶之路

OC底层原理九:类的原理分析

2020-09-14  本文已影响0人  markhetao

OC底层原理 学习大纲

上一节我们了解了isa内部结构,了解了结构的关系。

1. 类与isa指针的关系

objc4源码中,加入测试代码。在NSLog打印出加上断点

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        
        HTPerson * person = [[HTPerson alloc]init];
        
        NSLog(@"%@", person);
    }
    return 0;
}

p person打印,x/4gx打印,p/x&与上ISA_MASK、打印获取到的isa中的shiftcls地址。成功找到HTPerson

image.png

如果看不到,需要回看上一节。了解isa内部构造和获取类地址的方法。

我们发现,0x00000001000026300x0000000100002608都打印出来是HTPerosn的。2个地址不一样为什么打印出来结果一样?

NSObject

我们发现,一直顺着摸,摸到NSObject后,内存地址不再发生变化。

为了解答上面2个不同地址都是打印了HTPerson的问题。我们需要先了解一个新东西: 👇

2. 元类(Meta)

元类的定义和创建都由系统控制,由编译器自动完成,不受我们管理

对象的isa来自于也是对象。那isa指向哪里呢?

类既然是对象,就需要管理方法属性的存储和归属。而这个管理者,就是元类(Meta)

上面2个不同地址都打印HTPerson的问题,实际上打印路径是: HTPerosn -> HTPerson元类 -> NSObject

问题: 既然你说打印到了元类, 那HTPerson元类NSObject之后,为什么就结束了? 不应该再打印一次NSObject元类吗?

image.png

发现[NSObject class]打印的地址与之前打印的地址不一致

image.png

果然,NSObjectisa指针指向了NSObject元类。 地址和HTPerson元类isa指针地址一致。

那么,类在内存中会存在多份(多地址)吗?

#import <objc/runtime.h>
#import "HTPerson.h"

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        
        Class class1 = [HTPerson class];
        Class class2 = [HTPerson alloc].class;
        Class class3 = object_getClass([HTPerson alloc]);
        Class class4 = [HTPerson alloc].class;
        
        NSLog(@"%p", class1);
        NSLog(@"%p", class2);
        NSLog(@"%p", class3);
        NSLog(@"%p", class4);
        
    }
    return 0;
}
image.png

我们多种方式读取类,通过打印可以发现,所有地址都一样。

问题: 不直接继承NSObject类,Isa是如何指向的呢?
@interface HTMan : HTPerson
@end

@implementation HTMan
@end

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        
        HTMan * man = [[HTMan alloc]init];
        
        NSLog(@"%p", man);
        
    }
    return 0;
}

代码中HTMan继承自HTPerson,而HTPerson继承自NSObject

image.png

但是isa的指向,却是HTMan->HTMan元类->NSObject元类

总结
isa指向

误区

  • 上述是isa的指针指向,并非类的继承关系。
  • 继承关系,实例对象无继承关系。
  • NSObject没有父类(父类为Null)。
    所以我们类的继承,溯源只需要找到NSObject。
    OC语言中:NSObject是对象的始祖。万物皆对象
  • NSObject根元类isa指针是直接指向NSObject根元类

3. OC对象的本质

首先了解2个结构体: objc_object (根对象)和 objc_class(根类)

我们打开objc4源码,搜索struct objc_object

objc_object部分代码

搜索struct objc_class

objc_class部分代码

源码搜索时,注意看结构体尾部的声明。 UNAVAILABLE已废弃的不要耗费精力了。

image.png

我们发现,objc_class继承自objc_object

  1. object_object拥有isa属性,所以objc_class也拥有isa

  2. 万物皆对象(object)。

使用方法:

3. 类的结构分析

老规矩,先从案例下手,看懂了再总结

#import <Foundation/Foundation.h>
#import "HTPerson.h"

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        
        HTPerson * person = [[HTPerson alloc]init];
        
        NSLog(@"%p", person);
        
    }
    return 0;
}
image.png

请问:

1. 为什么第一个地址是HTPeroson?第二个是NSObject

  • objc_class第一个地址存放的是isa地址,第二个存放的是superclass类地址
    (参考上面objc_object部分代码objc_class部分代码图。)

2. 第二个地址打印的是NSObject类还是元类?

image.png
  • NSObject.class打印的地址与第二个地址打印的一致。 与接着打印的NSObject元类地址不一致。 说明第二个地址打印的是NSObjetc自身类。

3. 内存偏移

老规矩,先从案例开始,在main.m文件中加入测试代码:

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        
        int f[4] = {10,20,30,40};

        NSLog(@"%p", &f);
        NSLog(@"%p", &f[0]);
        NSLog(@"%p", &f[1]);
        NSLog(@"%p", &f[2]);
        NSLog(@"%p", &f[3]);

    }
    return 0;
}

在打印的尾部加入断点

image.png

我们发现:

是不是瞬间有个小想法:

我们是否可以根据数据类型,确定内存占用空间的大小,通过内存值偏移,就可定位下一元素的指针地址。然后直接取出这个指针地址指向的

小拓展: 如何通过地址取出对应的

  • 知识点: 指针(地址)的指针

  • 地址强转为指定类型(int) -> 加*读取对象(指针)的指针 -> 打印目标值

image.png

快速读取: p *((int *)0x7ffeefbff5b0)

image.png

我们通过首地址偏移,果然:

image

完美👍

但前提是我们必须知道偏移值是多少,也就是存储的是什么类型

image.png

至此。我们已经掌握了地址偏移指针偏移

现在,我们来分析类的结构

4. 分析类结构

objc4源码中,搜索objc_class

struct objc_class : objc_object {
    // Class ISA;  。                          // 指针 - 占用8字节
    Class superclass;                          // 指针 - 占用8字节
    cache_t cache;                             //  ?
    class_data_bits_t bits;                  
};

如果我们要获取bits的首地址位置。只有cache_t类型不知道空间大小。 点进去看看:

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets;
    explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
    
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
#else
#error Unknown cache mask storage type.
#endif
    
#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;

};
image.png image.png image.png

这里的_sel_imp 是不是很眼熟。 😃 后续我们会详细介绍

进入uintptr_t

image.png

所以我们_buckets实际大小就是8字节

接下来我们看mask_t

image.png

发现它就是uint32_t类型。占用4字节

汇总一下:

image.png

回头继续分析object_class的内存大小

struct objc_class : objc_object {
    // Class ISA;  。                          // 指针 - 占用8字节
    Class superclass;                          // 指针 - 占用8字节
    cache_t cache;                             //       占用16字节
    class_data_bits_t bits;                   
};

所以我们得出结论。

现在,我们来读取bits

5. 读取bits

image.png

objc_class结构中,我们发现bits.data()返回的是class_rw_t类型

image.png

我们打印bits->data()检验一下

image.png

成功拿到,打印HTPerson的class_rw_t信息:

image.png

我们进入class_rw_t内部, 折叠所有函数。

Xcode折叠所有函数:command + shift + ←

image.png

6. 读取methods、properties和protocols

main.m加入测试代码:

@interface HTPerson : NSObject {
    NSString * hobby;
}

@property(nonatomic, copy) NSString * name;
@property(nonatomic, copy) NSString * nickname;

+(void) ClassFunction;

-(void) objectFunction;

@end

@implementation HTPerson

+(void) ClassFunction {
    NSLog(@"类方法");
}

-(void) objectFunction{
    NSLog(@"对象方法");
}

@end

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        
        HTPerson * person = [[HTPerson alloc]init];
        
        NSLog(@"%p", person);
    }
    return 0;
}

NSLog处加入断点运行代码。快速找到data()对象的位置:

image.png
methods

利用$2对象打印p $2->methods()

image.png

我从图中看到了list。继续打印p *$3.list:

image.png image.png
properties

利用$2对象打印p $2->properties()

接下来打印p * properties()属性方法。

image.png
protocols

利用$2对象打印p $2->protocols()

image.png
成员变量hobby去哪了?
image.png

打印ro

image.png

打印p *$23.ivars

image.png

打印每一个成员变量p $25.get(0)

image.png
为什么案例中的ClassFunction类方法没出现在Methods中?

对象的isa指向自己的类,所以对象方法存放到了HTPerosn中,难道类方法存放在它的isa上一级?HTPerson元类中

实践出真知:

果然。 对象的方法存在中,的方法存在元类

下一章

上一篇 下一篇

猜你喜欢

热点阅读