阅读 2116

🐻objc1.0 与 objc2.0 解析

面试梳理

🐻iOS面试梳理 - 2020年8月初

转C++代码

用LLVM中的clang编译器:

clang -rewrite-objc ViewController.m
复制代码

如果需要导出相关系统库 可使用

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m
复制代码

会得到一个cpp的文件。

objc1.0

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;
复制代码
typedef struct objc_class *Class;


struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};
复制代码

typedef 用途一:类型别名,此处声明一个 objc_class * 指针的别名为Class

  • isa 实际为 objc_class * isa 一个类型为objc_class的指针。

isa 说明,实例对象指向父类,类对象指向元类,元类指向根元类,根元类指向自己。

  • super_class 实际为 objc_class * isa 一个类型为objc_class的指针。

指向父类,父类指向其父类,最终指向NSObject类

const是C语言的一种关键字,它所限定的变量是不允许被改变的,从而起到保护的作用! const关键字可以用于修饰变量,参数,返回值,甚至函数体。

  • name 是一个不可变的 char类型的指针,类名。

long 4字节

  • version 类的版本信息,long 类型

  • instance_size 实例变量大小,要考虑内存对齐

C类型 字节(32位) 字节(64位)
char 1 1
short int 2 2
int 4 4
long int 4 8
long long int 8 8
float 4 4
double 8 8
iOS 指针 4 8

内存对齐规则:

一、第一个变量的offet为0

二、存储起始未知必须是数据成员大小的整数倍,如: int为4字节,则要从4的整数倍地址开始存储。

三、结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的需要补齐

四、iOS本身会做16字节对齐,也就是最终大小必须是16的整数倍。

Person *p = [Person alloc];
p.name = @"Kaemi";  //  NSString  8
p.age = 18;         //  int       4
p.height = 188;     //  long      8
p.hobby = @"game";  //  NSString  8

NSLog(@"申请内存大小为:%lu——-系统开辟内存大小为:%lu",class_getInstanceSize([p class]),malloc_size((__bridge const void *)(p)));
复制代码

复制代码打印结果: 申请内存大小为:40---系统开辟内存大小为:48

instance_size的大小必须是固定的,如果是动态变化的,那么同一类型的不同对象 instance_size大小将会不同。

objc_ivar_liststruct objc_ivar_list * _Nullable ivars

struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}
复制代码

struct objc_ivar ivar_list[1] 这是C种struct的用法,表示变量名称为ivar_list内容为objc_ivar的数组

  • ivar_count 变量数量
  • space 未知
  • objc_ivar结构如下
struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif

复制代码

_Nullable 表示对象可以是 NULL 或 nil,_Nonnull 表示对象不应该为空

总结:objc_ivar_list,表示变量名称为ivar_list内容为objc_ivar的数组

methodLists 为一个类型为objc_method_list *的可空的指针

这是个二级指针,二级指针多用于取地址传值的操作

struct objc_method_list {
    struct objc_method_list * _Nullable obsolete             OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;

复制代码
  • objc_method_list这明显是个单向链表,那为什么还要用个objc_method method_list[1]困惑。
typedef struct objc_method *Method;
复制代码
struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;

复制代码
  • method_types,它是个 char 指针,存储着方法的参数类型和返回值类型

SEL 方法名 IMP 函数指针

cacheobjc_cache类型的指针

typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE;
};
复制代码

mask 制定分配缓存buckets的数量 occupied 已分配的buckets的数量 buckets 缓存数组,数量小于mask+1 = total

protocols 是一个类型为objc_protocol_list的指针

#ifdef __OBJC__
@class Protocol;
#else
typedef struct objc_object Protocol;
#endif
复制代码
struct objc_protocol_list {
    struct objc_protocol_list * _Nullable next;
    long count;
    __unsafe_unretained Protocol * _Nullable list[1];
};

复制代码

objc_class 到此分析结束。

再分析分类 Category

typedef struct objc_category *Category;
复制代码
struct objc_category {
    char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
    char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
}
复制代码

category_name:分类名 class_name:类名

其他的和objc_class相同,我们看到是没有ivars的。

objc2.0

typedef struct objc_class *Class;
typedef struct objc_object *id;
复制代码
struct objc_object {
private:
    isa_t isa;

public:

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

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    
    // 其他方法声明...
}
复制代码
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();
    }
    
    // 其他方法声明...
}
复制代码

objc_class 继承 objc_object,在objc2.0中类也是个对象

  • isa 类型为isa_t,其结构为:
 union isa_t {
      isa_t() { }
      isa_t(uintptr_t value) : bits(value) { }
      Class cls;
      uintptr_t bits;
  # if __arm64__
  #   define ISA_MASK        0x0000000ffffffff8ULL
  #   define ISA_MAGIC_MASK  0x000003f000000001ULL
  #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer : 1 ; 32位还是64位
        uintptr_t has_assoc  : 1 ; 对象是否含有关联引用,如果没有关联引用,可以更快释放对象
        uintptr_t has_cxx_dtor:1 ; 表示是否有C++析构函数或者OC的析构函数
        uintptr_t shiftcls : 33 ;类的指针;对象指向类的内存地址,也就是isa指向的地址
        uintptr_t magic: 6 ; 固定值为 0xd2,用于在调试时分辨对象是否未完成初始化。
        uintptr_t weakly_referenced:1;对象是否被引用或者曾经被弱引用
        uintptr_t deallocating:1 ;对象是否被释放中国
        uintptr_t has_sidetable_rc:1 ; 对象引用计数太大,是否超出存储区域
        uintptr_t extra_rc:19 对象的引用计数
    }
  #           define RC_ONE   (1ULL<<45)
  #       define RC_HALF  (1ULL<<18)
  # elif __x86_64__ // ····
    # else
  // ····
  # endif };
};
复制代码

isa 中的结构体进行了位域声明,总共8字节,64位。 isa说明:实例对象指向父类,类对象指向元类,元类指向根元类,根元类指向自己。

  • superclass 和objc1.0中的规律相同

  • cache_t cache 方法缓存 cache_t结构如下:

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
    
    // 其他方法声明...
}
复制代码

bucket_t *_buckets;bucket_t的结构:

struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    MethodCacheIMP _imp;
    cache_key_t _key;
#else
    cache_key_t _key;
    MethodCacheIMP _imp;
#endif

    // 其他声明...
}
复制代码

bucket_t 是一个散列表,key为cache_key_t,_imp为MethodCacheIMP

  • bits 的类型为class_data_bits_t
struct class_data_bits_t {
    // Values are the FAST_ flags above.
    uintptr_t bits;
    
public:
    class_rw_t *data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    
    // 其他定义...
}
复制代码

class_data_bits_t 中有个 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;
    
    // 其他定义...
}
复制代码

我们看到其中包含const修饰的class_ro_t 再看下,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_rw_t提供了运行时对类的扩展能力。

二者都有类的方法、属性、成员变量、协议,但是存储的结构体不同

  • ivar_t
struct ivar_t {
#if __x86_64__
    // *offset was originally 64-bit on some x86_64 platforms.
    // We read and write only 32 bits of it.
    // Some metadata provides all 64 bits. This is harmless for unsigned 
    // little-endian values.
    // Some code uses all 64 bits. class_addIvar() over-allocates the 
    // offset for their benefit.
#endif
    int32_t *offset;
    const char *name;
    const char *type;
    // alignment is sometimes -1; use alignment() instead
    uint32_t alignment_raw;
    uint32_t size;

    uint32_t alignment() const {
        if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
        return 1 << alignment_raw;
    }
};
复制代码

属性、成员变量的名称、类型、内存偏移量、大小

  • Category
#if __OBJC2__
typedef struct category_t *Category;
复制代码
struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
复制代码

category_t 存储了分类中可以拓展的实例方法、类方法、协议、实例属性和类属性。在 App 启动时,Runtime 加载完类后,会通过调用 attachCategories 函数进行向类中添加 Category 的工作。原理就是向 class_rw_t 中的 method_array_t, property_array_t, protocol_array_t 数组中分别添加 method_list_t, property_list_t, protocol_list_t 指针

  • Protocol
struct protocol_t : objc_object {
    const char *mangledName;
    struct protocol_list_t *protocols;
    method_list_t *instanceMethods;
    method_list_t *classMethods;
    method_list_t *optionalInstanceMethods;
    method_list_t *optionalClassMethods;
    property_list_t *instanceProperties;
    uint32_t size;   // sizeof(protocol_t)
    uint32_t flags;
    // Fields below this point are not always present on disk.
    const char **_extendedMethodTypes;
    const char *_demangledName;
    property_list_t *_classProperties;
    
    // 其它定义...
}
复制代码

protocol_t 继承于 objc_object,其中定义了协议中声明的实例方法,类方法,可选实例方法,可选类方法,实例属性和类属性等。此外,由于 Swift 还支持 Protocol 多继承,所以需要 protocols 数组来做兼容。

struct protocol_list_t {
    // count is 64-bit by accident. 
    uintptr_t count;
    protocol_ref_t list[0]; // variable-size
    
    // 其它定义...
}
复制代码