iOS OC类的探究分析

1. OC对象isa指针指向探究

  前几篇文章我们对OC类的创建以及isa指针的数据存储结构进行了探究,接下来我们就要对isa指针指向进行探究。

1.1 使用LLDB命令查看isa指针指向

  首先,定义一个继承自NSObjec基类的Person类,在main函数中创建一个Person对象p,代码如下:

@interface Person : NSObject

@end

@implementation Person

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [Person alloc];
        
    }
    return 0;
}
复制代码

  在创建完Person类对象那行代码打上断点,运行程序,首先打印p对象的内存数据情况,如下图所示:

image.png

  获取到p对象的isa指针的值之后,就可以通过LLDB命令打印Person类对象内存数据情况了,如下图所示:

image.png

  获取到Person类对象isa指针的值之后,其实这个isa指针的值指向的就是元类对象的地址,通过类对象中的isa指针的值打印元类对象的内存情况,如下图所示:

image.png

  获取到元类对象的内存数据之后,我们就可以通过这个元类对象的isa指针的值获取到根元类对象的内存地址,我们最后来查看一下这个根元类对象的内存数据情况,如下图所示:

image.png

  最后通过打印的数据我们发现根元类对象的isa指针指向的起始就是其本身的内存地址。那么在这个过程中,我们就有个疑惑了,因为我们发现打印的类对象
与打印的元类对象都是Person,但是它们的isa指针的值却不一样(一个是0x1000080e0,一个是0x1000080b8),那怎么验证0x1000080b8这个就是元类对象的地址呢?我们可以查看这个MachO文件的二进制信息,查看类列表与类引用列表信息,如下图所示:

类列表信息

类引用列表信息

  可以发现这两个表中就只有Person类的信息,除此之外,就再也找不到其他类的信息了,但是我们可以在符号表中搜索到如下图所示信息:

image.png

  其中_OBJC_METACLASS_$_Person就是在编译的过程中系统以及编译器自动生成的元类。

1.2 使用NSLog打印输出对象isa指针链信息

然后紧接着,我们来看看在Person对象的创建过程中会生成几个类对象,在程序中运行如下代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Class pClass1 = [[Person alloc] class];
        Class pClass2 = [Person class];
        Class pClass3 = object_getClass([Person alloc]);
        Class pClass4 = [[Person alloc] class];
        
        NSLog(@"\n cls1: %p\n cls2: %p\n cls3:%p\n cls4:%p\n", pClass1, pClass2, pClass3, pClass4);
    }
    return 0;
}
复制代码

运行代码,输出结果如下所示:

image.png

  根据输出结果我们发现无论我们创建多少个Person对象,但获取的类对象都是同一个,因此我们也可以猜测一下,创建的Person对象中其isa指针链中最终所指向的根元类对象可能与创建的NSObject对象的isa指针链中的类对象、元类对象或根元类对象有一定的关系,为了验证这一猜测,我们在程序中编写如下代码来调试运行一下,代码如下:

void personClassInfo(void) {
    Person *p = [Person alloc];
    Class pCls = [p class];
    Class pMetaCls = object_getClass(pCls);
    Class pRootMetaCls = object_getClass(pMetaCls);
    Class pRootRootMetaCls = object_getClass(pRootMetaCls);
    
    NSLog(@"\n pCls: %p\n pMetaCls: %p\n pRootMetaCls: %p\n pRootRootMetaCls: %p\n", pCls, pMetaCls, pRootMetaCls, pRootRootMetaCls);
}

void objectClassInfo(void) {
    //NSObject实例对象
    NSObject *obj = [NSObject alloc];
    //获取NSObject类
    Class cls = object_getClass(obj);
    //获取NSObject元类
    Class metaCls = object_getClass(cls);
    //获取NSObject根元类
    Class rootMetaCls = object_getClass(metaCls);
    //获取NSObject根根元类
    Class rootRootMetaCls = object_getClass(rootMetaCls);
    
    NSLog(@"\n cls: %p\n metaCls: %p\n rootMetaCls: %p\n rootRootMetaCls: %p\n", cls, metaCls, rootMetaCls, rootRootMetaCls);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        personClassInfo();
        objectClassInfo();
    }
    return 0;
}
复制代码

  运行程序,查看打印输出的信息,如下图所示:

image.png

  我们再定义一个Student类,继承自Person类,查看一下Studentisa指针的指向链,代码如下:

@interface Student : Person

@end

@implementation Student

@end

void studentClassInfo(void) {
    Student *s = [Student alloc];
    Class sCls = [s class];
    Class sMetaCls = object_getClass(sCls);
    Class sRootMetaCls = object_getClass(sMetaCls);
    Class sRootRootMetaCls = object_getClass(sRootMetaCls);
    
    NSLog(@"\n sCls: %p\n sMetaCls: %p\n sRootMetaCls: %p\n sRootRootMetaCls: %p\n", sCls, sMetaCls, sRootMetaCls, sRootRootMetaCls);
}

//main函数代码如下
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        studentClassInfo();
        personClassInfo();
        objectClassInfo();
    }
    return 0;
}
复制代码

  运行程序,查看打印输出结果,如下图所示:

image.png

  根据以上探究的结论我们可以绘制出以下的对象isa指针指向链图,如下所示:

image.png

2. OC对象类以及元类继承链探究

  探究过OC对象isa指针的指向链后,我们再来探究一下OC对象类的继承链,编写代码如下:

void objClassInheritInfo(void) {
    NSObject *obj = [[NSObject alloc] init];
    Person     *p = [[Person alloc] init];
    Student    *s = [[Student alloc] init];
    
    Class objCls = object_getClass(obj);
    Class pCls   = object_getClass(p);
    Class sCls   = object_getClass(s);
    
    Class objClsSuperCls = class_getSuperclass(objCls);
    Class pClsSuperCls   = class_getSuperclass(pCls);
    Class sClsSuperCls   = class_getSuperclass(sCls);
    
    NSLog(@"\n sCls: %p , sClsSuperCls: %p\n pCls: %p , pClsSuperCls: %p\n objCls: %p , objClsSuperCls: %p\n", sCls, sClsSuperCls, pCls, pClsSuperCls, objCls, objClsSuperCls);
    
    Class objMetaCls = object_getClass(objCls);
    Class pMetaCls   = object_getClass(pCls);
    Class sMetaCls   = object_getClass(sCls);
    
    Class objMetaClsSuperCls = class_getSuperclass(objMetaCls);
    Class pMetaClsSuperCls   = class_getSuperclass(pMetaCls);
    Class sMetaClsSuperCls   = class_getSuperclass(sMetaCls);
    
    NSLog(@"\nsMetaCls: %p , sMetaClsSuperCls: %p\n pMetaCls: %p , pMetaClsSuperCls: %p\n objMetaCls: %p , objMetaClsSuperCls: %p\n", sMetaCls, sMetaClsSuperCls, pMetaCls, pMetaClsSuperCls, objMetaCls, objMetaClsSuperCls);
}
复制代码

  编译运行程序,查看运行结果,如下图所示:

image.png

  根据以上的运行结果,我们可以知道OC对象的类以及元类也存在着继承链,继承关系如下图所示:

image.png

  最后,我们将isa指针链与类与元类的继承链做一个汇总,得到的最终关系图如下图所示:

image.png

3. 内存对齐

  接下来在程序中编写如下代码:


void memoryOffset(void) {
    
    int a = 10; //
    int b = 10; //
    NSLog(@"%d -- %p",a,&a);
    NSLog(@"%d -- %p",b,&b);
    // 对象 -
    Person *p1 = [Person alloc];
    Person *p2 = [Person alloc];
    NSLog(@"%@ -- %p",p1,&p1);
    NSLog(@"%@ -- %p",p2,&p2);
    
    // 数组指针
    int c[4] = {1,2,3,4};
    int *d   = c;
    NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
    NSLog(@"%p - %p - %p",d,d+1,d+2);
    
    for (int i = 0; i<4; i++) {
        int value =  *(d+i);
        NSLog(@"%d",value);
    }
    // OC 类 结构 首地址 - 平移一些大小 -> 内容
    // LGPerson.class地址 - 平移 所有的值
    NSLog(@"指针 - 内存偏移");
}
复制代码

  在main函数中调用这个函数,并在各个NSLog函数打上断点,运行程序,查看各个局部变量的值及其在内存中的地址,ab变量输出情况如下所示:

image.png

  ab变量在内存空间栈区域分配的地址偏移相差4字节,正好是其类型值int所占内存的大小。再来看看Person类对象在内存中分配的内存地址,如下图所示:

image.png

  在上图每行的打印中第一个值是Person类对象在内存堆区域中分配的内存首地址,第二个值是p1指针变量在内存栈区域分配的内存首地址,与前面ab两个int类型的变量相比,示意图如下所示:

image.png

  根据上图我们可以知道变量在内存中的分配地址以及大小情况,那么数组变量的分配情况又是如何呢?我们来看看数组元素地址分配情况,如下图所示:

image.png

  我们发现只要我们知道数组的元素的首地址,想要获取特定位置元素的偏移值,只需要将数组元素首地址加上该元素在数组中的偏移值就可以获取得到该元素所在的内存地址,获取这个元素的值了,其实在结构体中,我们如果想要获取其某个成员变量的值,也只需要将这个结构体变量在内存中分配的首地址加上这个成员变量在结构体中的偏移量就可以获取到这个成员变量的值了,这也是在程序运行过程中获取类结构体中某个成员变量的值的关键所在。

4. 类的结构分析

4.1 类结构源码探究分析

  前几篇文章,我们已经探究过isa指针的类型在底层其实就是objc_class *,那么我们如果要深入探究类的结构就要从结构体objc_class入手,首先我们在objc源码中搜索objc_class结构体的代码,可以找到下面两个定义,第一个定义代码如下:


struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
复制代码

但是这个定义在现在的iOS版本中已经被废弃不再使用了,我们直接探究第二个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;       //占8字节
    Class superclass;   //占8字节
    cache_t cache;     //占16字节,cache_t这个结构体中有一个8字节大小的成员变量_bucketsAndMaybeMask以及一个8字节大小的共用体类型的成员变量       // formerly cache pointer and vtable
    class_data_bits_t bits; 
}
复制代码

通过上面的代码我们发现,其实结构体objc_class继承了结构体objc_object,而在结构体objc_object中就有一个8字节大小的(objc_class *类型的)isa指针,第二个成员变量也是一个8字节大小的(objc_class *类型的superclass指针,第三个成员变量是一个结构体变量(cache_t类型)cache,第四个成员变量是一个结构体变量(class_data_bits_t类型)bits,其实从bits这个结构体中可以获取类数据的二进制信息,我们再来查看class_data_bits_t结构体中的成员变量,以及部分重要方法,代码如下所示:

struct class_data_bits_t {
uintptr_t bits;
...
public:
class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
}
...
}
复制代码

通过其成员属性bits(64位系统中是unsigned long类型),可以获取到class_rw_t *类型的结构体指针,而在class_rw_t这个结构体中定义了如下的成员变量以及重要的部分方法,如下所示:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;
public:
    //获取类中定义的方法列表
    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 *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }
复制代码

在上面的代码中methods()函数返回的类型method_array_t其实是一个C++类,其代码定义如下:

class method_array_t : 
    public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
    typedef list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> Super;

 public:
    method_array_t() : Super() { }
    method_array_t(method_list_t *l) : Super(l) { }

    const method_list_t_authed_ptr<method_list_t> *beginCategoryMethodLists() const {
        return beginLists();
    }
    
    const method_list_t_authed_ptr<method_list_t> *endCategoryMethodLists(Class cls) const;
};
复制代码

其中method_array_t又是继承自类list_array_tt,而在类list_array_tt中又定义了一个结构体array_t,以及两个成员变量,代码如下:

class list_array_tt {
struct array_t {
        uint32_t count;
        Ptr<List> lists[0];

        static size_t byteSize(uint32_t count) {
            return sizeof(array_t) + count*sizeof(lists[0]);
        }
        size_t byteSize() {
            return byteSize(count);
        }
    };
    
public:
    union {
        Ptr<List> list;
        uintptr_t arrayAndFlag;
    };
}
复制代码

除此以外还定义了一个iterator类,用来获取array_t中的数据,

4.2 类的方法列表结构探究

  创建一个LGPerson类,然后在main函数中创建一个LGPerson类对象,通过LLDB调试看是否能获取到LGPerson类中定义的方法列表以及属性列表,代码如下所示:

//LGPerson.h文件
@interface LGPerson : NSObject {
    NSString *key;
}

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) BOOL isFun;
@property (nonatomic, assign) double height;

- (void)sayHello;

+ (void)sayWorld;

@end

//LGPerson.m文件
@implementation LGPerson

- (void)sayHello {
    NSLog(@"hello");
}

+ (void)sayWorld{
    NSLog(@"world");
}

@end

//main函数中代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
         
        LGPerson *p = [LGPerson alloc];
        
        p.name    = @"sss";
        p.hobby   = @"play";
        p.isFun   = true;
        p.height  = 183.5;
        
    }
    return 0;
}
复制代码

在创建p对象的下一行打上断点,运行程序,在LLDB中进行调试,步骤如下:

  • 获取LGPerson对象pisa指针的值

image.png

image.png

  • 获取LGPerson类对象的内存地址

image.png

  • 首地址偏移32字节获取指向objc_class结构体中成员变量bits的地址指针

image.png

  • 调用data()函数获取到class_rw_t *类型的指针变量

image.png

  • 通过获取到的class_rw_t *类型的指针变量获取class_rw_t结构体变量

image.png

  • class_rw_t结构体变量调用methods()函数获取C++类method_array_t的实例化对象

image.png

  • 获取方法列表

image.png

  • 获取方法列表数组指针以及method_list_t类实例对象

image.png

  • 依次输出这个方法列表数组中的数据

image.png

image.png

image.png

  通过以上探究,我们发现除了属性的get、set方法以及自定义的实例方法之外,并不包含定义的类方法。

4.2 类的属性列表结构探究

  探究完类的方法列表结构之后,紧接着我们在上面LLDB操作的基础上再来探究类的属性列表结构是如何的

  • 获取property_array_t结构体变量

image.png

  • 获取属性列表数组指针以及property_list_t类实例化对象

image.png

image.png

  • 打印输出属性列表信息

image.png

  通过以上探究,我们发现除了属性列表中只包含定义的OC对象的属性,并不包含OC类中定义的成员变量。

4.3 类方法结构探究

  既然在类的属性列表以及方法列表中找不到定义的类方法,我们就猜测类方法可能存在在元类中,探究过程如下所示:

(lldb) p/x p
(LGPerson *) $0 = 0x0000000101a252a0
(lldb) x/4gx $0
0x101a252a0: 0x011d8001000084d1 0x0000000000000000
0x101a252b0: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d8001000084d1 & 0x00007ffffffffff8
(long) $1 = 0x00000001000084d0
(lldb) x/4gx 0x00000001000084d0
0x1000084d0: 0x00000001000084a8 0x000000010036c140
0x1000084e0: 0x00000001003643b0 0x0000803800000000
(lldb) p/x 0x00000001000084a8 & 0x00007ffffffffff8
(long) $2 = 0x00000001000084a8
(lldb) p/x (class_data_bits_t *) (0x00000001000084a8 + 0x20)
(class_data_bits_t *) $3 = 0x00000001000084c8
(lldb) p/x *$3
(class_data_bits_t) $4 = (bits = 0x0000000101a25264)
(lldb) p/x $4.data()
(class_rw_t *) $5 = 0x0000000101a25260
(lldb) p/x *$5
(class_rw_t) $6 = {
  flags = 0xa0080001
  witness = 0x0001
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 0x0000000100008170
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff80171eb0
}
(lldb) p/x $6.methods()
(const method_array_t) $7 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x00000001000081b8
      }
      arrayAndFlag = 0x00000001000081b8
    }
  }
}
(lldb) p/x $7.list.ptr
(method_list_t *const) $8 = 0x00000001000081b8
(lldb) p *$8
(method_list_t) $9 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p/x $9.get(0).big()
(method_t::big) $10 = {
  name = 0x0000000100003e8e "sayWorld"
  types = 0x0000000100003f55 "v16@0:8"
  imp = 0x0000000100003c00 (KCObjcBuild`+[LGPerson sayWorld])
}
复制代码

4.3 类中定义的成员变量结构探究

类的成员变量探究过程如下:

(lldb) p/x p
(LGPerson *) $0 = 0x0000000101219de0
(lldb) x/4gx $0
0x101219de0: 0x011d8001000084d1 0x0000000000000000
0x101219df0: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d8001000084d1 & 0x00007ffffffffff8
(long) $1 = 0x00000001000084d0
(lldb) p/x (class_data_bits_t *)(0x00000001000084d0 + 0x20)
(class_data_bits_t *) $2 = 0x00000001000084f0
(lldb) p/x *$2
(class_data_bits_t) $3 = (bits = 0x0000000101219dc4)
(lldb) p/x $3.data()
(class_rw_t *) $4 = 0x0000000101219dc0
(lldb) p/x *$4
(class_rw_t) $5 = {
  flags = 0x80080000
  witness = 0x0000
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 0x00000001000081d8
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff80170a88 NSUUID
}
(lldb) p/x *($5.ro())
(const class_ro_t) $6 = {
  flags = 0x00000000
  instanceStart = 0x00000008
  instanceSize = 0x00000030
  reserved = 0x00000000
   = {
    ivarLayout = 0x0000000000000000
    nonMetaclass = nil
  }
  name = {
    std::__1::atomic<const char *> = "LGPerson" {
      Value = 0x0000000100003f38 "LGPerson"
    }
  }
  baseMethodList = 0x0000000100008220
  baseProtocols = nil
  ivars = 0x0000000100008300
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000083a8
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p/x *($6.ivars)
(const ivar_list_t) $7 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 0x00000020, count = 0x00000005)
}
(lldb) p/x $7.get(0)
(ivar_t) $8 = {
  offset = 0x0000000100008430
  name = 0x0000000100003edc "key"
  type = 0x0000000100003f41 "@\"NSString\""
  alignment_raw = 0x00000003
  size = 0x00000008
}
复制代码

因此最后我们发现类的成员变量存在

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享