iOS底层收集

iOS进阶-04类的结构

2020-02-05  本文已影响0人  ricefun
初观类的结构

先看源码:
NSObjectd定义

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

Class定义

typedef struct objc_class *Class;

objc_class定义

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() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    void setInfo(uint32_t set) {
        assert(isFuture()  ||  isRealized());
        data()->setFlags(set);
    }
    ...
    ...
    ...

objc_object定义

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

上述源码总结:

类的内部结构

先上调试代码:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject {
    NSString *hobby;//成员变量
}
@property (nonatomic,copy) NSString *nickName;//属性
- (void)eat;
+ (void)sleep;

@end
NS_ASSUME_NONNULL_END
#import "Person.h"
@implementation Person
- (void)eat {    
}

+ (void)sleep {
}
@end

#####
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"hello world!");
    }
    return 0;
}

在NSLog 打断点进行LLDB调试,获取class_data_bits_t bits内存情况:
1.打印当前Person类的内存情况

(lldb) x/4gx Person.class
0x1000026d0: 0x001d8001000026a9 0x0000000100b38140
0x1000026e0: 0x00000001003db270 0x0000000000000000

我们已经知道0x001d8001000026a9 是isa,又根据下面objc_class 源码可知:0x0000000100b38140 是superclass,
0x00000001003db270是cache,但是bits我们不知道,反正不可能是0x0000000000000000

//objc_class 源码
struct objc_class : objc_object {
    // Class ISA; //8个字节
    Class superclass;//8个字节
    cache_t cache; //16个字节
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    ...
    ...
    ...
}

如何获取到class_data_bits_t bits?我在上面objc_class源码上标ISA 、superclass、cache的字节占用大小三者加起来刚好是32个字节,换成16进制就是20;所以bits就是0x1000026d0偏移20字节位0x1000026f0。所以继续LLDB调试:

为什么是8、8、16,我在OC底层原理探索-03中放过一张数据类型占用字节大小的图,请自行查看

(lldb) p (class_data_bits_t *)0x1000026f0//需要使用class_data_bits_t *)进行强转
(class_data_bits_t *) $1 = 0x00000001000026f0
(lldb) p $1->data()//根据源码方法 bits.data();获取class_rw_t,就是下面$3
(class_rw_t *) $3 = 0x0000000102d2a230
(lldb) p *$3//打印class_rw_t的内部结构
(class_rw_t) $4 = {
  flags = 2148139008
  version = 0
  ro = 0x00000001000024e8
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000100002420
        arrayAndFlag = 4294976544
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x00000001000024d0
        arrayAndFlag = 4294976720
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
  demangledName = 0x0000000000000000
}
//class_rw_t 部分源码
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;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
    ...
    ...
}

对比 class_rw_t源码,是不是惊人的相似,说明class_rw_t数据我们已经拿到了;既然能拿到class_rw_t,其内部的class_ro_t是不是也能拿到。继续LLDB调试:

(lldb) p $4.ro
(const class_ro_t *) $6 = 0x00000001000024e8
(lldb) p $6
(const class_ro_t *) $6 = 0x00000001000024e8
(lldb) p *$6
(const class_ro_t) $7 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100001dd3 "\x02"
  name = 0x0000000100001dcc "Person"
  baseMethodList = 0x0000000100002420
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100002488
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000024d0
}
class_ro_t 源码
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

对比 class_ro_t源码,是不是同样的相似,说明class_ro_t数据我们已经拿到了;同样我们可以打印其内部的baseProtocols 、ivars 、baseMethodList,继续LLDB调试:

(lldb) p $7.baseProperties
(property_list_t *const) $8 = 0x00000001000024d0
(lldb) p *$8//打印属性list
(property_list_t) $9 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1//只有一个属性,即nickName
    first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
  }
}
(lldb) p $9.get(0)//打印第一个属性,存在
(property_t) $10 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
(lldb) p $9.get(1) //打印第二个属性,就报错了
Assertion failed: (i < count), function get, file /Users/Desktop/runtime/objc-runtime-new.h, line 117.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb) p $7.ivars
(const ivar_list_t *const) $11 = 0x0000000100002488
(lldb) p *$11 //打印ivars 成员变量数组
(const ivar_list_t) $12 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2//有两个hobby和nickName
    first = {
      offset = 0x00000001000025f8
      name = 0x0000000100001e18 "hobby"
      type = 0x0000000100001eb5 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $12.get(0)//打印第1个ivar
(ivar_t) $13 = {
  offset = 0x00000001000025f8
  name = 0x0000000100001e18 "hobby"
  type = 0x0000000100001eb5 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $12.get(1)//打印第2个ivar
(ivar_t) $14 = {
  offset = 0x0000000100002600
  name = 0x0000000100001e68 "_nickName"
  type = 0x0000000100001eb5 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7.baseMethodList
(method_list_t *const) $15 = 0x0000000100002420
(lldb) p *$15//打印实例方法列表
(method_list_t) $16 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4//有4个?分别是 eat、nickName的get/set方法,还有一个系统自带的cxx_destruct方法
    first = {
      name = "eat"
      types = 0x0000000100001e72 "v16@0:8"
      imp = 0x0000000100001c50 (LGTest`-[Person eat] at Person.m:12)
    }
  }
}
(lldb) p $16.get(0)
(method_t) $17 = {
  name = "eat"
  types = 0x0000000100001e72 "v16@0:8"
  imp = 0x0000000100001c50 (LGTest`-[Person eat] at Person.m:12)
}
(lldb) p $16.get(1)
(method_t) $18 = {
  name = ".cxx_destruct"
  types = 0x0000000100001e72 "v16@0:8"
  imp = 0x0000000100001ce0 (LGTest`-[Person .cxx_destruct] at Person.m:10)
}
(lldb) p $16.get(2)
(method_t) $19 = {
  name = "setNickName:"
  types = 0x0000000100001e82 "v24@0:8@16"
  imp = 0x0000000100001ca0 (LGTest`-[Person setNickName:] at Person.h:15)
}
(lldb) p $16.get(3)
(method_t) $20 = {
  name = "nickName"
  types = 0x0000000100001e7a "@16@0:8"
  imp = 0x0000000100001c70 (LGTest`-[Person nickName] at Person.h:15)
}
(lldb) p $16.get(4)//打印第5个就报错了
Assertion failed: (i < count), function get, file /Users/Desktop/runtime/objc-runtime-new.h, line 117.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb) 

到这里,我想各位已经对Class的结构有了初步的认识了
总结

完整LLDB调试过程如下
(lldb) x/4gx Person.class
0x1000026d0: 0x001d8001000026a9 0x0000000100b38140
0x1000026e0: 0x00000001003db270 0x0000000000000000
(lldb) p (class_data_bits_t *)0x1000026f0
(class_data_bits_t *) $1 = 0x00000001000026f0
(lldb) p $1->data()
(class_rw_t *) $3 = 0x0000000102d2a230
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148139008
  version = 0
  ro = 0x00000001000024e8
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000100002420
        arrayAndFlag = 4294976544
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x00000001000024d0
        arrayAndFlag = 4294976720
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
  demangledName = 0x0000000000000000
}
(lldb) p $4.ro
(const class_ro_t *) $6 = 0x00000001000024e8
(lldb) p *$6
(const class_ro_t) $7 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100001dd3 "\x02"
  name = 0x0000000100001dcc "Person"
  baseMethodList = 0x0000000100002420
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100002488
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000024d0
}
(lldb) p $7.baseProperties
(property_list_t *const) $8 = 0x00000001000024d0
(lldb) p *$8
(property_list_t) $9 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
  }
}
(lldb) p $9.get[0]
error: reference to non-static member function must be called
(lldb) p $9.get(0)
(property_t) $10 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
(lldb) p $9.get(1) 
Assertion failed: (i < count), function get, file /Users/Desktop/码/runtime/objc-runtime-new.h, line 117.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb) p $7.ivars
(const ivar_list_t *const) $11 = 0x0000000100002488
(lldb) p *$11 
(const ivar_list_t) $12 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x00000001000025f8
      name = 0x0000000100001e18 "hobby"
      type = 0x0000000100001eb5 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $12.get(0)
(ivar_t) $13 = {
  offset = 0x00000001000025f8
  name = 0x0000000100001e18 "hobby"
  type = 0x0000000100001eb5 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $12.get(1)
(ivar_t) $14 = {
  offset = 0x0000000100002600
  name = 0x0000000100001e68 "_nickName"
  type = 0x0000000100001eb5 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7.baseMethodList
(method_list_t *const) $15 = 0x0000000100002420
(lldb) p *$15
(method_list_t) $16 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "eat"
      types = 0x0000000100001e72 "v16@0:8"
      imp = 0x0000000100001c50 (LGTest`-[Person eat] at Person.m:12)
    }
  }
}
(lldb) p $16.get(0)
(method_t) $17 = {
  name = "eat"
  types = 0x0000000100001e72 "v16@0:8"
  imp = 0x0000000100001c50 (LGTest`-[Person eat] at Person.m:12)
}
(lldb) p $16.get(1)
(method_t) $18 = {
  name = ".cxx_destruct"
  types = 0x0000000100001e72 "v16@0:8"
  imp = 0x0000000100001ce0 (LGTest`-[Person .cxx_destruct] at Person.m:10)
}
(lldb) p $16.get(2)
(method_t) $19 = {
  name = "setNickName:"
  types = 0x0000000100001e82 "v24@0:8@16"
  imp = 0x0000000100001ca0 (LGTest`-[Person setNickName:] at Person.h:15)
}
(lldb) p $16.get(3)
(method_t) $20 = {
  name = "nickName"
  types = 0x0000000100001e7a "@16@0:8"
  imp = 0x0000000100001c70 (LGTest`-[Person nickName] at Person.h:15)
}
(lldb) p $16.get(4)
Assertion failed: (i < count), function get, file /Users/Desktop/runtime/objc-runtime-new.h, line 117.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
类的结构图解
上一篇下一篇

猜你喜欢

热点阅读