RuntimeiOS面试题相关知识点面试题

ios 底层原理 : 类与类结构分析

2021-07-01  本文已影响0人  木槿WEIXIAO

本文主要分析类与类结构

类的分析

类的分析主要是分析 isa 的走向与继承关系

准备

@interface LGPerson : NSObject
{
    NSString *hobby;
}
@property(nonatomic,copy)NSString * lg_name;
- (void)sayHello;
+ (void)sayBye;
@end
@implementation LGPerson
- (void)sayHello
{
    
}
+ (void)sayBye
{
    
}
@end
@interface LGTeacher : LGPerson

@end
@implementation LGTeacher

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...

        LGPerson *person = [LGPerson alloc];
        LGTeacher *teacher = [LGTeacher alloc];
        NSLog(@"Hello, World! %@ %@",person,teacher);

    }
    return 0;
}

元类

首先我们通过案例的 lldb 引出元类

元类的说明

总结

从图中可以看出

NSObject到底有几个

从图中可以看出,最后的根元类是 NSObject,这个 NSObject 和我们平时开发过程中的NSObject 是同一个嘛?
以下用两种方法可以验证

方式 1: lldb 命令

image.png

从图中可以看出,最后 NSObject 类的元类也是 NSObject,和上面 LGPerson 的根元类是同一个,所以可以得出结论:内存中只存在一份根元类 NSObject,根元类的元类指向他自己

方式 2:代码验证

通过三种不同的方式获取类,看他们的打印地址是否相同

        Class class1 = LGPerson.class;
        Class class2 = object_getClass([LGPerson alloc]);
        Class class3 = [LGPerson alloc].class;
        NSLog(@"啦啦啦   %p %p %p",class1,class2,class3);

以下是运行结果


image.png

从结果看出,打印的地址都是同一个,所以得出结论:NSObject 在内存中只有一份,根元类也一样只有一份

[面试题]类存在几份

由于类的信息在内存中只存在一份,所以类对象只有一份

注明的 isa 走位图

image.png

isa 走位

superclass 走位(继承关系)

objc_class & objc_object

isa 走位理清楚了,又来了一个新问题,为什么对象和类都有 isa 属性?这里就该提到两个结构体:objc_class & objc_object
下面在这两个结构体的基础上,对上述问题进行探索
在上一篇底层 7中,从 clang 编译过的main.m 文件,可以看到以下 c++代码
- 其中 class 是 isa 指针的类型,是由 objc_class 类型定义的
- 而 objc_class 是一个结构体,所有的 class 都是以 objc_class 为模板创建的

struct NSObject_IMPL {
    Class isa;
};


typedef struct objc_class *Class;
struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};

objc_class 和 objc_object 有什么关系?

objc_object 与对象的关系

总结

objc_object objc_class NSObject isa整体关系如下

未命名文件.png

类结构分析

主要分析类信息中主要存储哪些内容

补充知识:内存偏移

在分析类结构之前,需要了解内存偏移,因为类信息访问时,需要用到内存偏移

[普通指针]

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

打印结果如下


image.png

[对象指针]

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

打印结果如图所示


image.png

[数组指针]

        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);

打印结果如下


image.png

探索类信息中有哪些内容

在探索类信息之前,我们并不知道类信息中有哪些内容,我们可以先得到类的首地址,然后通过内存平移取出里面的值
根据前面提到的objc_class的定义,有以下几个属性

struct objc_class : objc_object {
    // Class ISA; //8字节
    Class superclass; //Class 类型 8字节
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
    //....方法部分省略,未贴出
}

计算cache类的内存大小

进入cache类,cache_t的定义(只贴出了结构体中非static修饰的属性,主要是因为static类型修饰的属性,不存在结构体内存当中)

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个字节

获取bits

image.png

探索属性列表,即property_list

通过查看class_rw_t源码实现,可以看到内部提供了获取,方法列表,属性列表,协议列表的方法


image.png

在获取bits的基础上,通过class_rw_t提供的方法,来获取属性列表


image.png

问题:探索成员变量的存储

由此可以得出,property_list中并没有成员变量,属性与成员变量的区别就是有没有set get方法,如果有就是属性,没有则是成员变量

探索方法列表,即method_list

在LGPerson增加两个方法,一个类方法,一个实例方法

@interface LGPerson : NSObject
@property(nonatomic,copy)NSString * name;
@property(nonatomic,copy)NSString * nickName;
- (void)sayHello;
+ (void)sayBye;
@end
@implementation LGPerson
- (void)sayHello
{
    
}
+ (void)sayBye
{
    
}
@end
image.png

新问题的探索 (探索成员变量的存储)

通过查看objc_class的类结构,发现bits里面的class_rw_t,除了property_list,method_list,protocols方法,还有一个ro方法,其返回类型是class_ro_t,通过查看他的定义,发现其中有一个ivar_list_t * ivars;属性,所以我们猜测,成员变量就存在于ivars里面

(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100008240 LGPerson
(lldb) p (class_data_bits_t *)0x0000000100008260
(class_data_bits_t *) $1 = 0x0000000100008260
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000100755860
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000200
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
(lldb) p $3.ro
(const class_ro_t *) $4 = 0x0000000100008088
  Fix-it applied, fixed expression was: 
    $3.ro()
(lldb) p $3.ro()
(const class_ro_t *) $5 = 0x0000000100008088
(lldb) p *$5
(const class_ro_t) $6 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
   = {
    ivarLayout = 0x0000000100003ec9 "\U00000002"
    nonMetaclass = 0x0000000100003ec9
  }
  name = {
    std::__1::atomic<const char *> = "LGPerson" {
      Value = 0x0000000100003ec0 "LGPerson"
    }
  }
  baseMethodList = 0x0000000100003e70
  baseProtocols = nil
  ivars = 0x00000001000080d0
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008118
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $6.ivars
(const ivar_list_t *const) $7 = 0x00000001000080d0
(lldb) p *$7
(const ivar_list_t) $8 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 2)
}
(lldb) p $8.get(0)
(ivar_t) $9 = {
  offset = 0x0000000100008210
  name = 0x0000000100003f0d "_name"
  type = 0x0000000100003f60 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $8.get(1)
(ivar_t) $10 = {
  offset = 0x0000000100008214
  name = 0x0000000100003f13 "_nickName"
  type = 0x0000000100003f60 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $8.get(2)
Assertion failed: (i < count), function get, file /Users/dev1852/Downloads/学习demo/objc4-818-master/objc4-818.2/runtime/objc-runtime-new.h, line 625.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb) 
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];

//省略
}

结论

问题:探索类方法的存储

由此可知,methods_list中只有实例方法,没有类方法,那么问题就是,类方法存储在哪里。
在前面我们提到了元类,类的isa就是指向元类,元类是用来存储类相关信息的,所以猜测,类方法是否存储在元类的bits当中,可以通过lldb命令验证一下


image.png

通过图中元类列表打印结果,可以得到以下结论

上一篇 下一篇

猜你喜欢

热点阅读