iOS-OC底层四:类的属性、方法和协议

一、对象、ISA、类、元类、根元类间的关系

核心知识点:

  • 类声明对象,给对象分配多少内存是依据类,对象的ISA指向类
  • 对象在内存中第一个8字节储存的是ISA
  • ISA中的shiftClass段就是类,即类为对象的isa&mask
  • 类也是一种对象,objc_class继承自objc_object,类的第一个8字节也是ISA,所以万物皆对象,万物皆有isa
  • 类的ISA中的shiftClass为元类,即元类为类的isa&mask,类是由元类进行声明
  • NSObject类的ISA指向NSObject类的元类,NSObject类的元类的ISA指向它自己
  • 元类的ISA指向元类的元类,我们称NSObject类的元类为根元类
  1. 元类的创建和声明是编译器完成,元类用来存储类方法的相关信息,元类本身是没有名称的,由于与相关联,所以使用了同类名一样的名称
  2. 对象之间不存在继承关系,只有类和元类之间存在继承关系
  3. 类与元类在系统中,只存在一份,即类对象只有一份
  4. 无论类继承层次多深,类的元类的ISA指向的都是NSObject根元类,由对象开始查找ISA,第三次及以后绝对指向NSObject
ISA指向和类继承关系图.png

1.1 准备验证代码

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface TESTPerson : NSObject
@property (nonatomic, copy) NSString *testname;
@end

@implementation TESTPerson

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        TESTPerson *obj = [TESTPerson alloc];
        TESTPerson *obj1 = [TESTPerson alloc];
        NSLog(@"class:%@",[TESTPerson class]);
        NSLog(@"class:%@",[obj class]);
        NSLog(@"class:%@", object_getClass(obj));
        NSLog(@"\n");
        NSLog(@"class:%@",[[obj class] class]);
        NSLog(@"class:%@",[[[obj class] class] class]);
        NSLog(@"class:%@",[[[[obj class] class] class] class]);
        NSLog(@"\n");
        NSLog(@"class:%@", object_getClass(object_getClass(obj)));
        NSLog(@"class:%@", object_getClass(object_getClass(object_getClass(obj))));
        NSLog(@"class:%@", object_getClass(object_getClass(object_getClass(object_getClass(obj)))));
        NSLog(@"\n");
        
    }
    return 0;
}

1.2 获取类的内存分布的三种方式:

  1. 根据对象的实例化方法:[obj class]
  2. 根据类对象的类方法:[TESTPerson class]
  3. 根据runtime中的object_getClass(obj)

为什么嵌套调用时,结果不一致?

  • class获取的永远是obj的类,因为传的是self
  • object_getClass会将obj->getIsa()当成下一次的obj,如果嵌套两层,相当于调用两次getIsa(),对应的代码obj->getIsa()->getIsa()

三种方式的源码分析:

- (Class)class {
    return object_getClass(self);
}
//第一步,走类方法class
+ (Class)class {
    return self;
}

//第二步,走objc_opt_class获取类信息
Class
objc_opt_class(id obj)
{
#if __OBJC2__
    if (slowpath(!obj)) return nil;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        return cls->isMetaClass() ? obj : cls;
    }
#endif
    return ((Class(*)(id, SEL))objc_msgSend)(obj, @selector(class));//走这里
}
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

1.3 Class的本质是objc_class

打开源码,在main方法里面输入Class *class。然后通过Class点进去,可以发现都是typedef struct objc_class *Class;,这说明Class(类)都是struct objc_class创建而来的。

#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

可以看到objc_class是继承于objc_object的,而objc_class本身是没有isa的,isa继承自objc_object,说明万物皆对象,对象是由类创建的,类由元类创建。

objec_class继承自object_object.png

1.4 验证类信息只有一份

验证类信息只有一份.png

二、类结构分析

2.1 重要:分析方法

分析的原理:基于类在内存中占用的地址是连续的,我们只需要从类的定义中,算出我们想查找的信息在哪个位置。具体做法就是通过struct objc_class : objc_object {} ,在这个定义类的结构体中,计算各成员变量所占的字节数,然后通过类的首地址,做相应字节的偏移,就能得到我们想要的信息。

再仔细看看objc_class的源码:

struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
    //底下全是方法
    ......
}

C语言无法在结构体中定义函数,但是C++可以做到。C++中delete是删除地址空间,此处operator用作重载运算符。我们大概可以猜到前4句代码是析构函数功能,用作内存释放的函数。其他函数可以很确定绝对是函数的声明或者是内部函数实现。

所以我要弄清以下问题:

方法是否占用内存空间,会不会造成成员变量偏移?

通过以下代码予以验证

static声明的变量和方法不影响偏移位置.png

同样的道理:计算struct cache_t所占用的内存大小:

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets; // 是一个结构体指针类型,占8字节
    explicit_atomic<mask_t> _mask; //是mask_t 类型,而 mask_t 是 unsigned int 的别名,占4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets; //是指针,占8字节
    mask_t _mask_unused; //是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占4字节
    
#if __LP64__
    uint16_t _flags;  //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
#endif
    uint16_t _occupied; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节

计算出cache类的内存大小 = 12 + 2 + 2 = 16字节。

我们所需要查看的信息在class_data_bits_t bits;中,在bits前面有3个成员变量,isa,superclass,这两个都是Class类型,都占有8字节,cache占用16字节,那么bits的内存就是基于类的首地址偏移32字节。

2.2 class_data_bits_t具体操作

什么是class_data_bits_t?在源码中,全局搜索,共有三处。

//第一处
// class_data_bits_t is the class_t->data field (class_rw_t pointer plus flags)
// The extra bits are optimized for the retain/release and alloc/dealloc paths.

//第二处
struct class_data_bits_t {
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;

     // 代码过多,自动省略
    ...
public:

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    // 代码过多,自动省略
    ...
};

//第三处
class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

struct class_rw_t{...} 中,有我们熟悉的methodspropertiesprotocols。所以,我们应该先获取bits中class_rw_t *类型的data,再访问data中的方法,属性和协议。

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

推荐阅读更多精彩内容