第四天 类的本质

之前遗留问题解决:

  • 1.callAlloc中if (fastpath(cls->canAllocFast()))方法不走的原因

callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
   if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        if (fastpath(cls->canAllocFast())) {
          ...
        } else {
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
   }
}

点进去canAllocFast的方法可以查看

bool canAllocFast() {
        assert(!isFuture());
        return bits.canAllocFast();
    }

->

#if FAST_ALLOC
...
#else
 bool canAllocFast() {
        return false;
    }
#endif

canAllocFast方法中可以看到有一个宏定义FAST_ALLOC
在全局搜索宏定义FAST_ALLOC,发现FAST_ALLOC也是在一个宏定义中

#if !__LP64__
...
#elif 1
...
#else
...
#define FAST_ALLOC              (1UL<<2)
#endif

可以发现此宏定义只走中间#elif 1的代码,回到上面判断FAST_ALLOC方法会发现,FAST_ALLOC不会走,只走bool canAllocFast() { return false; }方法,继续往上查看,if (fastpath(cls->canAllocFast())) {} else {}只走else{}方法。
流程图如下:

不走快速创建方法的原因.png


  • 2.类 & 元类的创建时机

    1.通过lldb进行调试,可查看
    2.通过 ‘command + b’编译的可执行文件,通过 ‘machoView‘进行查看,发现已经存在相应的类。

  • 3.指针 & 内存偏移

1.普通指针
int a = 10; 是值拷贝,原因是在常量区已经存在一个为10的常量,int a = 10,是将常量 10拷贝给了 a,是值拷贝,同理int b = 10也一样是值拷贝。
打印,发现 ab的值是一样的,但是地址不一样,如下代码:

int a = 10; //
int b = 10; //
NSLog(@"%d -- %p",a,&a);
NSLog(@"%d -- %p",b,&b);

打印结果:
10 -- 0x7ffeefbff4fc
10 -- 0x7ffeefbff4f8

2. 指针拷贝

Person *p1 = [LGPerson alloc];
Person *p2 = [LGPerson alloc];
NSLog(@"%@ -- %p",p1,&p1);
NSLog(@"%@ -- %p",p2,&p2);

打印结果:
 -- 0x7ffeefbff4f0(指向 “指针内存”的指针 -> 及 指针的指针)
 -- 0x7ffeefbff4e8

如上代码,可以发现,p1p2指向的内存空间、指针的指针都是不一样的,说明是两个全新的值。

普通指针 和 指针拷贝 如图:

普通指针、指针拷贝的区别.png

3.内存偏移
数组指针:
创建一个数组,如下代码:

int a[4] = {1,2,3,4};
int *b = a;
NSLog(@"%p - %p - %p",&a,&a[0],&a[1]);
打印:

打印结果:
0x7ffeefbff510(&a 的指针) - 0x7ffeefbff510( &a[0]的指针) - 0x7ffeefbff514(&a[1]的指针)

打印内存空间:
(lldb) x/4gx 0x7ffeefbff510
0x7ffeefbff510: 0x0000000200000001 0x0000000400000003
0x7ffeefbff520: 0x00007ffeefbff540 0x8cd419990ace003a

从结果中可以看到:
&a&a[0]的指针地址是相同的。
0x0000000200000001值就是0x7ffeefbff510所指向的空间,也就是 元素的首地址就是第一个元素的地址
结论:地址中的首地址是代表着这个对象的地址

第一个元素地址0x7ffeefbff510 和 第二个元素地址0x7ffeefbff514相差4 也就是 int的所占的4字节。
那么打印如下代码会发现 可以通过地址偏移,打印出不同的元素:

NSLog(@"%p - %p - %p",b,b+1,b+2);
打印结果:
0x7ffeefbff510(&a[0]) - 0x7ffeefbff514(&a[1]) - 0x7ffeefbff518(&a[2])

还以可通过 对地址取值,来获取对应的值。

p *b+1
(int) $0 = 2
p *b+2
(int) $1 = 3

*b+1,中b+1b偏移地址,* + 地址,是对地址所对应的内存空间进行取值。


问题:类的结构是什么?

探索结构

创建一个person对象,并且获取到person的类 pClass

 Person *person = [Person alloc];
 Class pClass     = object_getClass(person);
`lldb调试`
(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389(Class ISA;) 0x0000000100b37140(Class superclass;)
0x1000023c0: 0x00000001003da290(cache_t cache;) 0x0000000000000000

(lldb) po 0x0000000100b37140
NSObject

输出第二个内存空间发现是NSObject,继续输出第三个发现输出不了。

(lldb) po 0x00000001003da290
4299006608

(lldb) p 0x00000001003da290
(long) $7 = 4299006608

下面通过对源码的探索,研究 类 的结构。


2.对源码探索,研究类的属性

通过源码对Class pClass = object_getClass(person);中的Class进行跟踪发下:

typedef struct objc_class *Class;

发现Classobjc_class的一个对象,继续对objc_class点击查看发现如下代码:

struct objc_class : objc_object {
    // Class ISA; // 8
    Class superclass; // 8
    cache_t cache;    // 16 不是8 
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
...
}

到这发现objc_class是继承 objc_object的一种结构体类型,也就是类也是继承与对象的,也验证了那句“万物皆对象”的说法。

注意:由于源码有 新 和 旧两种,objc_class的结构体类型也有种结构,上面所展示的是 新 的结构。

继续通过源码查找,发现objc_object的结构如下:

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

那么NSObject又是什么呢?

@interface NSObject  {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

NSObject也是对objc_object结构的一种仿写。

问题1:Class ISA为什么isa是一个Class类型呢?
答:1.万物皆对象,isa是可以由Class接收的。
2.早期调用isa是用来返回类的,后面是通过 nonpointer区分 纯净isa和有内容的isa。
3.通过源码查找isa如下代码return (Class)(isa.bits & ISA_MASK);进行class强转了。

objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}  

3.研究objc_object的内部结构,查找 实例方法

在上面知道了objc_object的结构是这样的:

struct objc_class : objc_object {
    // Class ISA; // 8
    Class superclass; // 8
    cache_t cache;    // 16 不是8 
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    class_rw_t *data() { 
          return bits.data();
    }
...
}
  • Class ISA
  • Class superclass点击Class,发现结构是typedef struct objc_class *Class,这里的是*Class,也就是一个指针,也就是一个结构体指针,指针占8字节。
  • cache_t cache是一个结构体,不是结构体指针,是根据全部成员所占的内存。
    struct bucket_t *_buckets;是结构体,查表得是8字节
    mask_t _mask; 查看typedef uint32_t mask_t;uint32_t(int类型),占4字节。
    同理mask_t _occupied;也是4字节。
    得出,cache_t占16字节。
struct cache_t {
    struct bucket_t *_buckets; // 8
    mask_t _mask;  // 4
    mask_t _occupied; // 4
...
}

类定义的一些 属性 和 方法:

@interface Person : NSObject{
    NSString *hobby;
}

@property (nonatomic, copy) NSString *nickName;

- (void)sayHello;
+ (void)sayHappy;

@end

Person中定义nickName属性,是在bits中,下面要读取bits中的数据。

Person *person = [Person alloc];
Class pClass     = object_getClass(person);

(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389 0x0000000100afe140
0x1000023c0: 0x00000001003a1280 0x0000000000000000

通过objc_class中的结构得知0x001d800100002389是isa,0x0000000100afe140是superClass,0x00000001003a1280是cache,则0x0000000000000000是bits。

为什么bits中存有属性等数据呢?

查看源码发现,class_rw_t中的method_array_tproperty_array_tprotocol_array_t是通过return bits.data();返回的。

class_rw_t *data() { 
          return bits.data();
    }
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
...
}

但是bits无法读出,只能通过内存偏移去查找。
从之前的了解,得知 isasuperclass都是8字节,cache_t cache是16字节。

下面进行lldb调试

(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389 0x0000000100afe140
0x1000023c0: 0x00000001003a1280 0x0000000000000000
(lldb) po 0x1000023d0
objc[3229]: Attempt to use unknown class 0x1018576c0.
4294976464
(lldb) p 0x1000023d0
(long) $2 = 4294976464

po 0x1000023d0是内存0x1000023b0偏移32,得到的bits的地址,但是不能输出对象,p 0x1000023d0也不能输出地址。

因为bits的类型是class_data_bits_t,进行强转,获得bits的指针

(lldb) p (class_data_bits_t *)0x1000023d0
(class_data_bits_t *) $3 = 0x00000001000023d0

上面提到class_rw_t中存在的属性、方法、协议是通过return bits.data();返回的。

(lldb) po $3->data()
0x00000001018576c0
(lldb) p $3->data()
(class_rw_t *) $5 = 0x00000001018576c0

得到class_rw_t *的指针$5,对class_rw_t进行取值 p *$5

(lldb) p *$5
(class_rw_t) $6 = {
  flags = 2148139008
  version = 0
  ro = 0x0000000100002308
  methods = {
    list_array_tt = {
       = {
        list = 0x0000000100002240
        arrayAndFlag = 4294976064
      }
    }
  }
  properties = {
    list_array_tt = {
       = {
        list = 0x00000001000022f0
        arrayAndFlag = 4294976240
      }
    }
  }
  protocols = {
    list_array_tt = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSDate
  demangledName = 0x0000000000000000
}

发现存在 properties,属性应该会在这个里面,输出properties

(lldb) p $6.properties
(property_array_t) $7 = {
  list_array_tt = {
     = {
      list = 0x00000001000022f0
      arrayAndFlag = 4294976240
    }
  }
}

探究property_array_t发现是一个 二位数组(entsize_list_tt),那么代表着是可以遍历输出。

struct property_list_t : entsize_list_tt {
};

但是输出 p $7(0)等是没有输出

(lldb) p $7(0)
error: type 'property_array_t' does not provide a call operator

应为没有得到结果,只能查看list

(lldb) p $7.list
(property_list_t *) $8 = 0x00000001000022f0

查看struct entsize_list_tt的源码,发现 有个Element first;就是第一个元素,试着输出

struct entsize_list_tt
{
    uint32_t entsizeAndFlags;
    uint32_t count;
    Element first;
...
}

输出第一元素:

(lldb) p $8->first
(property_t) $9 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")

发现是存在nickName这个属性的,但是还有一个hobby属性,是否也可以遍历?

(lldb) p $8.get[1]
error: member reference type 'property_list_t *' is a pointer; did you mean to use '->'?
error: reference to non-static member function must be called

遍历之后发现是不存在的。

留坑!! 为什么属性和会在 ro 中?等后面进行补齐!

其实具体的属性、方法、协议是存在 ro中。
输出 ro

(lldb) p $6.ro
(const class_ro_t *) $10 = 0x0000000100002308
(lldb) p *$10
(const class_ro_t) $11 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100001f89 "\x02"
  name = 0x0000000100001f80 "LGPerson"
  baseMethodList = 0x0000000100002240
  baseProtocols = 0x0000000000000000
  ivars = 0x00000001000022a8
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000022f0
}

发现存在baseProperties,试着查看。

(lldb) p $11.baseProperties
(property_list_t *const) $12 = 0x00000001000022f0
(lldb) p *$12
(property_list_t) $13 = {
  entsize_list_tt = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
  }
}

结果是存在nickName的,但是 count = 1代表着,元素只有一个,那hobby存在那?

为什么hobby不在baseProperties中呢?

结果是在 ivars

(lldb) p *$11.ivars
(const ivar_list_t) $15 = {
  entsize_list_tt = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x0000000100002378
      name = 0x0000000100001e64 "hobby"
      type = 0x0000000100001fa6 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $15.get(0)
(ivar_t) $16 = {
  offset = 0x0000000100002378
  name = 0x0000000100001e64 "hobby"
  type = 0x0000000100001fa6 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $15.get(1)
(ivar_t) $17 = {
  offset = 0x0000000100002380
  name = 0x0000000100001e6a "_nickName"
  type = 0x0000000100001fa6 "@\"NSString\""
  alignment_raw = 3
  size = 8
}

ivarscount = 2代表存在两个元素,遍历之后如上 成员变量hobby和属性nickName都存在。

有一条可以得知,属性 在底层会生成一个 带下划线"_"的成员变量,也就是上面的_nickName成员变量。


3.对源码探索,研究类的方法

2中进行了 ro 内容的输出,可以发现存在一个 baseMethodList,方法会存在这个里面吗?,继续探究

(lldb) p *$10
(const class_ro_t) $11 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100001f89 "\x02"
  name = 0x0000000100001f80 "LGPerson"
  baseMethodList = 0x0000000100002240
  baseProtocols = 0x0000000000000000
  ivars = 0x00000001000022a8
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000022f0
}

输出:baseMethodList

(lldb) p $10.baseMethodList
(method_list_t *const) $19 = 0x0000000100002240
  Fix-it applied, fixed expression was: 
    $10->baseMethodList

(lldb) p *$19
(method_list_t) $20 = {
  entsize_list_tt = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "sayHello"
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
    }
  }
}

是存在"sayHello"这个方法的,同时也可以查看method_t的源码

struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;
...
};

发现验证了方法中有 name(方法名)、types(签名)、imp(指针函数,指向方法的实现)。

继续打印剩余的方法

nickName的get方法

(lldb) p $20.get(1)
(method_t) $22 = {
  name = "nickName"
  types = 0x0000000100001f93 "@16@0:8"
  imp = 0x0000000100001bf0 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
}

nickName的set方法

(lldb) p $20.get(2)
(method_t) $23 = {
  name = "setNickName:"
  types = 0x0000000100001f9b "v24@0:8@16"
  imp = 0x0000000100001c20 (LGTest`-[LGPerson setNickName:] at LGPerson.h:17)
}

".cxx_destruct"是系统默认添加的 c++的方法

(lldb) p $20.get(3)
(method_t) $24 = {
  name = ".cxx_destruct"
  types = 0x0000000100001f8b "v16@0:8"
  imp = 0x0000000100001c60 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}

突然发现+ (void)sayHappy;没有实现!!!!

在最开始探究类时候,创建一个一对象person,并通过Class pClass = object_getClass(person);获取到类pClass,后面研究的都是在pClass中,并且获得了 实例方法。
也就是说,实例方法是存在 中,那么 类方法是不是存在元类中呢?


查找类方法

按照上面的方法继续查找

获取pClass的内存段

(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389 0x0000000100afe140
0x1000023c0: 0x00000001003a1280 0x0000000000000000

通过isa & mask获取到 pClass的元类,并且打印内存段

(lldb) p/x 0x001d800100002389 & 0x00007ffffffffff8
(long) $1 = 0x0000000100002388
(lldb) x/4gx 0x0000000100002388
0x100002388: 0x001d800100afe0f1 0x0000000100afe0f0
0x100002398: 0x0000000101076da0 0x0000000100000003

内存偏移,获取到class_data_bits_t0x100002388 ->(偏移32字节)0x1000023a8

(lldb) p (class_data_bits_t *)0x1000023a8
(class_data_bits_t *) $2 = 0x00000001000023a8

调用函数data()获取class_rw_t

(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101076d00

读取ro

(lldb) p $3->ro
(const class_ro_t *) $4 = 0x00000001000021f8
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 389
  instanceStart = 40
  instanceSize = 40
  reserved = 0
  ivarLayout = 0x0000000000000000
  name = 0x0000000100001f80 "LGPerson"
  baseMethodList = 0x00000001000021d8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000000000000
}

继续读取baseMethodList

(lldb) p $5.baseMethodList
(method_list_t *const) $6 = 0x00000001000021d8
(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "sayHappy"
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001bc0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
    }
  }
}

发现找到"sayHappy"的方法。

也验证了 类方法 是存在 元类 中


总结

  • 1.属性在 objc_class -> class_data_bits_t bits -> class_rw_t* data() -> class_ro_t * ro -> property_list_t *baseProperties 中,成员变量存在 class_ro_t * ro -> ivar_list_t * ivars;中,并且 属性会在 ivars中生成一个 带下划线“_”的 成员变量。即 @property (nonatomic, copy) NSString *nickName;会在ivars中生成_ nickName

  • 2.实例方法存在类中。
    流程:objc_class -> class_data_bits_t bits -> class_rw_t* data() -> class_ro_t * ro -> method_list_t * baseMethodList;

  • 3.类方法存在元类中。

你可能感兴趣的:(第四天 类的本质)