iOS底层探索之类结构

一、前置知识

CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。

1.1 C语言指针变量的运算(加法、减法和比较运算)

指针 变量保存的是 地址,而地址本质上是一个整数,所以指针变量可以进行部分运算,例如加法、减法、比较等,请看下面的代码:

        int    a = 10,   *pa = &a, *paa = &a;
        double b = 99.9, *pb = &b;
        char   c = '@',  *pc = &c;
        //最初的值
        NSLog(@"最初的值:");
        NSLog(@"&a=%p, &b=%p, &c=%p \n", &a, &b, &c);
        NSLog(@"pa=%p, pb=%p, pc=%p \n", pa, pb, pc);
        //加法运算
        pa++; pb++; pc++;
        NSLog(@"各自+1后:");
        NSLog(@"pa=%p, pb=%p, pc=%p \n", pa, pb, pc);
        //减法运算
        NSLog(@"然后-2后:");
        pa -= 2; pb -= 2; pc -= 2;
        NSLog(@"pa=%p, pb=%p, pc=%p \n", pa, pb, pc);
        //比较运算
        if(pa == paa){
            NSLog(@"pa和paa相等: %d\n", *paa);
        }else{
            NSLog(@"pa和paa不相等: %d\n", *pa);
        }
最初的值:
&a=0x7ffeed1bbd80, &b=0x7ffeed1bbd78, &c=0x7ffeed1bbd87
pa=0x7ffeed1bbd80, pb=0x7ffeed1bbd78, pc=0x7ffeed1bbd87
各自+1后:
pa=0x7ffeed1bbd84, pb=0x7ffeed1bbd80, pc=0x7ffeed1bbd88
然后-2后:
pa=0x7ffeed1bbd7c, pb=0x7ffeed1bbd70, pc=0x7ffeed1bbd86
pa和paa不相等: 1079572889

从运算结果可以看出:papbpc 每次加 1,它们的地址分别增加 4、8、1,正好是 intdoublechar 类型的长度;减 2 时,地址分别减少 8、16、2,正好是 intdoublechar 类型长度的 2 倍。

这很奇怪,指针变量加减运算的结果跟数据类型的长度有关,而不是简单地加 1 或减 1,这是为什么呢?

apa 为例,a 的类型为int,占用 4 个字节,pa 是指向 a 的指针,如下图所示:

c指针运算-0.jpg

刚开始的时候,pa 指向 a 的开头,通过 *pa 读取数据时,从 pa 指向的位置向后移动 4 个字节,把这 4 个字节的内容作为要获取的数据,这 4 个字节也正好是变量 a 占用的内存。

如果 pa++;使得地址加 1 的话,就会变成如下图所示的指向关系:

c指针运算-1.jpg

这个时候 pa 指向整数 a 的中间,*pa 使用的是红色虚线画出的 4 个字节,其中前 3 个是变量 a 的,后面 1 个是其它数据的,把它们“搅和”在一起显然没有实际的意义,取得的数据也会非常怪异。

如果pa++ ;使得地址加 4 的话,正好能够完全跳过整数 a,指向它后面的内存,如下图所示:

c指针运算-2.jpg

总结:指针做 + 、- 运算的时候,是按指向内容类型倍数运算。

二、Class (进入正题)

上篇文章分析isa的时候,遇到了 ClassClass 是什么,内部又是怎么定义的?

源码用的还是objc4-787.1版本。

2.1 源码查看
typedef struct objc_class *Class;

继续查看 objc_class,这个时候发现有3个 objc_class 的定义,都是 struct,但是注意一个是 runtime.h 中的,会发现 已经标记 OBJC2_UNAVAILABLE(OC2不可用了)。一个是 objc-runtime-old.h (旧的不管)中的,还有个 objc-runtime-new.h 中的,跟进 objc-runtime-new.h 中的代码:

struct objc_class : objc_object {
    // 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

    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
  // 中间省略很多方法 ...
    bool isMetaClass() {
        ASSERT(this);
        ASSERT(isRealized());
#if FAST_CACHE_META
        return cache.getBit(FAST_CACHE_META);
#else
        return data()->flags & RW_META;
#endif
    }

    // Like isMetaClass, but also valid on un-realized classes
    bool isMetaClassMaybeUnrealized() {
        static_assert(offsetof(class_rw_t, flags) == offsetof(class_ro_t, flags), "flags alias");
        static_assert(RO_META == RW_META, "flags alias");
        return data()->flags & RW_META;
    }

    // NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

    bool isRootClass() {
        return superclass == nil;
    }
    bool isRootMetaclass() {
        return ISA() == (Class)this;
    }
    // 省略 ...
    
    // 以前分析alloc的时候,熟悉的instanceSize
    uint32_t alignedInstanceStart() const {
        return word_align(unalignedInstanceStart());
    }

    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize;
    }

    // Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }

    size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }

        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

    void setInstanceSize(uint32_t newSize) {
        ASSERT(isRealized());
        ASSERT(data()->flags & RW_REALIZING);
        auto ro = data()->ro();
        if (newSize != ro->instanceSize) {
            ASSERT(data()->flags & RW_COPIED_RO);
            *const_cast<uint32_t *>(&ro->instanceSize) = newSize;
        }
        cache.setFastInstanceSize(newSize);
    }

}

发现 objc_class 继承与 objc_object,继续跟进 objc_object

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
    Class rawISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    
    uintptr_t isaBits() const;

    // 省略很多方法 方法 方法 ...
}

发现 objc_object 只有一个私有属性 isa_t isa;

2.2 验证经典isa流程图
isa流程图.png

这个isa流程图,见过好多次了,明白其流程,但是不知其怎么来的,

在分析 alloc 的时候,会初始化 instanceisa 赋值该对象的 Class

根据源码可知:

  • 只要继承与 NSObject 的对象都会有一个 isa
  • isa 中最重要的就是 shiftcls,其实就是指向 Class
        GLPerson *supP = [[GLPerson alloc] init];
        // GLStudent 继承于 GLPerson
        GLStudent *subS = [[GLStudent alloc] init];
isa走位打印.png

验证 subS 也是一样的流程。

2.3 类(对象)只存在一份
        Class c1 = object_getClass(subS1);
        Class c2 = object_getClass(subS2);
        Class c3 = object_getClass(subS3);
        Class c4 = object_getClass(subS4);
        
        NSLog(@"c1:%p; c2:%p; c3:%p; c4:%p", c1, c2, c3, c4);

console: c1:0x1026ca820; c2:0x1026ca820; c3:0x1026ca820; c4:0x1026ca820

三、objc_class

对象初始化的时候,好像没有要初始化属性、方法吧,那属性、方法、协议这些东西是放在那里了。

找了下 objc-runtime-old.h旧定义文件) 里面的定义,发现 objc_class 里面直接是有 ivars methodLists protocols 这些信息的。

而在 objc-runtime-new.h新定义文件)里面这些属性没了,而多了一个 class_data_bits_t bits 属性。

class_data_bits_t 源码:

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 里面有个方法 class_rw_t* data(),会返回 class_rw_t 的数据。

继续查看 class_rw_t

struct class_rw_t {
    // 省略 ...
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }
}

可在里面找到 method_array_tproperty_array_tprotocol_array_t 这些方法。从字面意思应该能看出这就是我们要找的东西了。

3.1 获取 class_data_bits_t

运行环境是在配置好可运行的 objc 源码的工程。

struct objc_class : objc_object {
    // 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
    // 省略...
}

通过 objc_class 的源码可知,bits 存在第4个成员。只要计算出前面3个成员的大小,就可以通过内存偏移得到 bitsisa 根据以前文章的分析可知是8字节。Class superclass 是一个指针类型,所以也是8字节。看下 cache_t 源码:

struct cache_t {
// typedef uint32_t mask_t;
// typedef unsigned int uint32_t; 所以mask_t类型是4字节
// typedef unsigned long           uintptr_t; 所以 uintptr_t 类型是8字节
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets; // 指针8字节
    explicit_atomic<mask_t> _mask;  // 4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets; // long 8字节
    mask_t _mask_unused; // 4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    explicit_atomic<uintptr_t> _maskAndBuckets; // long 8字节
    mask_t _mask_unused; // 4字节
#else
#error Unknown cache mask storage type.
#endif

// typedef unsigned short uint16_t; 这是uint16_t的定义
#if __LP64__
    uint16_t _flags;  // 2字节
#endif
    uint16_t _occupied; // 2字节

// 省略很多方法和静态变量...
}

cache_t 源码总结:不管第一个if else走哪个,加上下面2个变量,总共占用16字节。

计算偏移量

  • 继承的isa : 8字节;
  • Class superclass : 8字节;
  • cache_t cache:16字节;

所以,要想获取 bits,总共需要在类的首地址上偏移32字节。

(lldb) x/4gx GLPerson.class
0x100002620: 0x00000001000025f8 0x0000000100334140
0x100002630: 0x00000001006a8540 0x0001802400000003
// 在GLPerson类首地址0x100002620的基础上偏移32字节。记得是16进制哦
(lldb) p (class_data_bits_t *)0x100002640
(class_data_bits_t *) $4 = 0x0000000100002640
3.2 打印 class_rw_t 中存储
@protocol GLPersonWalkProtocol <NSObject>
- (void)walk;
@end

@interface GLPerson : NSObject <GLPersonWalkProtocol>

@property (nonatomic, strong) NSString *name; /**<  8个字节  */
@property (nonatomic, strong) NSString *nickName; /**<  8个字节  */

- (void)sayHello;
+ (void)goSchool;

@end

@interface GLStudent : GLPerson
@end
---
        GLPerson *supP = [[GLPerson alloc] init];
        GLStudent *subS = [[GLStudent alloc] init];

通过 xppo 命令打印

lldb) x/4gx supP
0x1006a4730: 0x001d800100002625 0x0000000000000000
0x1006a4740: 0x0000000000000000 0x0000000000000000
(lldb) po 0x001d800100002625 & 0x00007ffffffffff8ULL
GLPerson

(lldb) p 0x001d800100002625 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 4294977056 // 这是GLPerson类
(lldb) x/4gx $2  // 打印GLPerson类的地址和内存
0x100002620: 0x00000001000025f8 0x0000000100334140
0x100002630: 0x00000001006a8540 0x0001802400000003
// 另一种方式打印GLPerson类的地址和内存,再次验证是一样的
(lldb) x/4gx GLPerson.class  
0x100002620: 0x00000001000025f8 0x0000000100334140
0x100002630: 0x00000001006a8540 0x0001802400000003
// 通过类的首地址偏移32字节得到$4,$4即是class_data_bits_t的首地址
(lldb) p (class_data_bits_t *)0x100002640
(class_data_bits_t *) $4 = 0x0000000100002640
// 调用class_data_bits_t的方法的到class_rw_t数据
(lldb) p *$4->data()
(class_rw_t) $5 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294976416
  }
  firstSubclass = GLStudent
  nextSiblingClass = NSUUID
}

总结流程:

  1. 打印 GLPerson.class 的首地址和内存;
  2. 通过地址偏移获取 class_data_bits_t *指针赋值给 $4 变量;
  3. 通过 *$4 取地址里面的值可得到 class_data_bits_t
  4. 然后调用 data() 方法,返回 class_rw_t 数据;
  5. 通过 p 打印 class_rw_t;
3.3 获取 method_array_t
(lldb) x/4gx GLPerson.class
0x1000022b8: 0x0000000100002290 0x0000000100333140
0x1000022c8: 0x0000000100709790 0x0001802400000003
(lldb) p (class_data_bits_t *)0x1000022d8
(class_data_bits_t *) $2 = 0x00000001000022d8
(lldb) p *$2->data()
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294975632
  }
  firstSubclass = GLStudent
  nextSiblingClass = NSUUID
}
(lldb) p $3.methods()
(const method_array_t) $4 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x00000001000020d8
      arrayAndFlag = 4294975704
    }
  }
}
(lldb) p $4.list
(method_list_t *const) $5 = 0x00000001000020d8
(lldb) p *$5
(method_list_t) $6 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 6
    first = {
      name = "sayHello"
      types = 0x0000000100000f7c "v16@0:8"
      imp = 0x0000000100000d80 (`-[GLPerson sayHello])
    }
  }
}
(lldb) p $6.get(0)
(method_t) $7 = {
  name = "sayHello"
  types = 0x0000000100000f7c "v16@0:8"
  imp = 0x0000000100000d80 (`-[GLPerson sayHello])
}
(lldb) p $6.get(1)
(method_t) $8 = {
  name = ".cxx_destruct"
  types = 0x0000000100000f7c "v16@0:8"
  imp = 0x0000000100000e30 (`-[GLPerson .cxx_destruct])
}
(lldb) p $6.get(2)
(method_t) $9 = {
  name = "name"
  types = 0x0000000100000f90 "@16@0:8"
  imp = 0x0000000100000d90 (`-[GLPerson name])
}
(lldb) p $6.get(3)
(method_t) $10 = {
  name = "setName:"
  types = 0x0000000100000f98 "v24@0:8@16"
  imp = 0x0000000100000db0 (`-[GLPerson setName:])
}
(lldb) p $6.get(4)
(method_t) $11 = {
  name = "setNickName:"
  types = 0x0000000100000f98 "v24@0:8@16"
  imp = 0x0000000100000e00 (`-[GLPerson setNickName:])
}
(lldb) p $6.get(5)
(method_t) $12 = {
  name = "nickName"
  types = 0x0000000100000f90 "@16@0:8"
  imp = 0x0000000100000de0 (`-[GLPerson nickName])
}
(lldb) p $6.get(6)
Assertion failed: (i < count), function get, file /Users/xulong/Desktop/学习/源码/alloc流程分析/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

总结流程

  1. 通过 *$2->data() 获取到 (class_rw_t) $3
  2. 调用 (class_rw_t) $3methods 方法获取 (const method_array_t) $4;
  3. 调用 $4.list方法 获取指向 method_list_t 的指针 (method_list_t *const) $5;
  4. p *$5$5 指向的存储内容,得到 (method_list_t) $6
  5. method_list_t 继承于 entsize_list_tt, entsize_list_tt有个方法 Element& get(uint32_t i) ,可根据第一个item偏移地址获取要取的item。
  6. 通过 get(0)、get(1) ... 获取方法列表知道越界。

可以发现方法列表里有我们定义的实例方法,却没有类方法 + (void)goSchool;

3.4 获取 property_array_t

同样方式也可打印出 const property_array_t properties()

(lldb) x/4gx GLPerson.class
0x1000022b8: 0x0000000100002290 0x0000000100333140
0x1000022c8: 0x0000000100709790 0x0001802400000003
(lldb) p (class_data_bits_t *)0x1000022d8
(class_data_bits_t *) $2 = 0x00000001000022d8
(lldb) p *$2->data()
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294975632
  }
  firstSubclass = GLStudent
  nextSiblingClass = NSUUID
}
(lldb) p $3.properties()
(const property_array_t) $13 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x00000001000021b8
      arrayAndFlag = 4294975928
    }
  }
}
(lldb) p $13.list
(property_list_t *const) $14 = 0x00000001000021b8
(lldb) p *$14
(property_list_t) $15 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 2
    first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
  }
}
(lldb) p $15.get(0)
(property_t) $16 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
(lldb) p $15.get(1)
(property_t) $17 = (name = "nickName", attributes = "T@\"NSString\",&,N,V_nickName")
(lldb) p $15.get(2)
Assertion failed: (i < count), function get, file /Users/xulong/Desktop/学习/源码/alloc流程分析/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

继续探索:类结构下篇


参考

C语言指针:http://c.biancheng.net/view/1992.html

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