区块链技术博客
www.b2bchain.cn

iOS runtime——看这一篇就够了求职学习资料

本文介绍了iOS runtime——看这一篇就够了求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。

对技术面试,学习经验等有一些体会,在此分享。

本文篇幅比较长,创作的目的为了自己日后温习知识所用,希望这篇文章能对你有所帮助。如发现任何有误之处,肯请留言纠正,谢谢。

一、深入代码理解 instance、class object、metaclass

iOS runtime——看这一篇就够了

1、instance对象实例

我们经常使用id来声明一个对象,那id的本质又是什么呢?查看objc/objc.h文件

/// An opaque type that represents an Objective-C class. typedef struct objc_class *Class;  /// Represents an instance of a class. struct objc_object {     Class _Nonnull isa  OBJC_ISA_AVAILABILITY; };  /// A pointer to an instance of a class. typedef struct objc_object *id;

我们创建的一个对象或实例其实就是一个struct objc_object结构体,而我们常用的id也就是这个结构体的指针。

这个结构体只有一个成员变量,这是一个Class类型的变量isa,也是一个结构体指针,isa指针就指向对象所属的类

一个 NSObject 对象占用多少内存空间?
一个NSObject实例对象只有一个isa指针,所以一个isa指针的大小,他在64位的环境下占8个字节,在32位环境上占4个字节。

 NSObject *obj = [[NSObject alloc] init];  NSLog(@"class_getInstanceSize--%zd", class_getInstanceSize([NSObject new]));

输出结果:

class_getInstanceSize--8

2、class object(类对象)/metaclass(元类)

看结构体objc_class的定义

struct objc_class {     Class isa  OBJC_ISA_AVAILABILITY;  #if !__OBJC2__     Class super_class                                        OBJC2_UNAVAILABLE;     const char *name                                         OBJC2_UNAVAILABLE;     long version                                             OBJC2_UNAVAILABLE;     long info                                                OBJC2_UNAVAILABLE;     long instance_size                                       OBJC2_UNAVAILABLE;     struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;     struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;     struct objc_cache *cache                                 OBJC2_UNAVAILABLE;     struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE; #endif  } OBJC2_UNAVAILABLE; /* Use `Class` instead of `struct objc_class *` */
  • Class superclass;——用于获取父类,也就是元类对象,它也是一个Class类型
  • cache_t cache;——是方法缓存
  • class_data_bits_t bits;——用于获取类的具体信息,看到bits
  • class_rw_t *data()函数,该函数的作用就是获取该类的可读写信息,通过class_data_bits_t的bits.data()方法获得,class_rw_t后面会介绍
class_rw_t* data() {         return (class_rw_t *)(bits & FAST_DATA_MASK); }

该结构体的第一个成员变量也是isa指针,这就说明了Class本身其实也是一个对象,我们称之为类对象。类对象中的元数据存储的都是如何创建一个实例的相关信息,那么类对象和类方法应该从哪里创建呢?就是从isa指针指向的结构体创建,类对象的isa指针指向的我们称之为元类(metaclass),元类中保存了创建类对象以及类方法所需的所有信息。

3、isa指针与superclass相关逻辑图
iOS runtime——看这一篇就够了

isa逻辑图

4、总结 + 代码校验

  • 对象 的类(Superclass)是 类(对象) ;
  • 类(对象) 的类(Superclass)是 元类,和类同名;
  • 元类 的类(Superclass)是 根元类 NSObject;
  • 根元类 的类(Superclass)是 自己 ,还是NSObject;
  • 对象的isa指针指向类(对象) ;
  • 类对象的isa指针指向元类,和类同名;
  • 元类的isa指针指向跟根元类 NSObject;
  • 根元类 NSObject的isa指针指向自己。

isa验证

    NSString *string = @"字符串";     Class class1 = object_getClass(string);//NSString类对象     Class metaClass = object_getClass(class1);//NSString元类     Class rootMetaClass = object_getClass(metaClass);//根元类     Class rootRootMetaClass = object_getClass(rootMetaClass);//根元类     NSLog(@"%p 实例对象 ",string);     NSLog(@"%p 类 %@",class1,NSStringFromClass(class1));     NSLog(@"%p 元类 %@",metaClass,NSStringFromClass(metaClass));     NSLog(@"%p 根元类 %@",rootMetaClass,NSStringFromClass(rootMetaClass));     NSLog(@"%p 根根元类 %@",rootRootMetaClass,NSStringFromClass(rootRootMetaClass));      Class rootMetaClass_superclass = rootMetaClass.superclass;//根元类的superclass     NSLog(@"根根元类的superclass:%@",NSStringFromClass(rootMetaClass_superclass));

输出结果:

0x102d48078 实例对象  0x1d80e3d10 类 __NSCFConstantString 0x1d80e3cc0 元类 __NSCFConstantString 0x1d80c66c0 根元类 NSObject 0x1d80c66c0 根根元类 NSObject 根根元类的superclass:NSObject

superclass验证

    NSString *string = @"字符串";     Class class1 = object_getClass(string);//NSString类对象     Class class2 = class1.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class1),NSStringFromClass(class2));     Class class3 = class2.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class2),NSStringFromClass(class3));     Class class4 = class3.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class3),NSStringFromClass(class4));     Class class5 = class4.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class4),NSStringFromClass(class5));     Class class6 = class5.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class5),NSStringFromClass(class6));

输出结果:

 __NSCFConstantString 的superclass是 __NSCFString  __NSCFString 的superclass是 NSMutableString NSMutableString 的superclass是 NSString NSString 的superclass是 NSObject NSObject 的superclass是 (null)

二、class_rw_t 与 class_ro_t

1、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;     } };
  • uint32_t instanceSize;——instance对象占用的内存空间
  • const char * name;——类名
  • const ivar_list_t * ivars;——类的成员变量列表

class_ro_t存储了当前类在编译期就已经确定的属性、方法以及遵循的协议,里面是没有分类的方法的。那些运行时添加的方法将会存储在运行时生成的class_rw_t中。
ro即表示read only,是无法进行修改的。

2、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; // 指向只读的结构体,存放类初始信息      /*      这三个都是二位数组,是可读可写的,包含了类的初始内容、分类的内容。      这三个二位数组中的数据有一部分是从class_ro_t中合并过来的。      */     method_array_t methods; // 方法列表(类对象存放对象方法,元类对象存放类方法)     property_array_t properties; // 属性列表     protocol_array_t protocols; //协议列表      Class firstSubclass;     Class nextSiblingClass;      //...     }

3、class_rw_t生成时机

class_rw_t生成在运行时,在编译期间,class_ro_t结构体就已经确定,objc_class中的bits的data部分存放着该结构体的地址。在runtime运行之后,具体说来是在运行runtime的realizeClass 方法时,会生成class_rw_t结构体,该结构体包含了class_ro_t,并且更新data部分,换成class_rw_t结构体的地址。

类的realizeClass运行之前:
iOS runtime——看这一篇就够了

然后在加载 ObjC 运行时的过程中在 realizeClass 方法中:

  • 从 class_data_bits_t 调用 data 方法,将结果从 class_rw_t 强制转换为 class_ro_t 指针
  • 初始化一个 class_rw_t 结构体
  • 设置结构体 ro 的值以及 flag
  • 最后设置正确的 data。
const class_ro_t *ro = (const class_ro_t *)cls->data(); class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); rw->ro = ro; rw->flags = RW_REALIZED|RW_REALIZING; cls->setData(rw);

但是,在这段代码运行之后 class_rw_t 中的方法,属性以及协议列表均为空。这时需要 realizeClass 调用 methodizeClass 方法来将类自己实现的方法(包括分类)、属性和遵循的协议加载到 methods、 properties 和 protocols 列表中。

realizeClass 方法执行过后的类所占用内存的布局:
iOS runtime——看这一篇就够了

细看两个结构体的成员变量会发现很多相同的地方,他们都存放着当前类的属性、实例变量、方法、协议等等。区别在于:class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定,它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_t是class_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容。

4、method_t

上面我们剖析了class_rw_t、class_ro_t这两个重要部分的结构,并且主要关注了其中的方法列表部分,而从上面的分析,可发现里面最基本也是重要的单位是method_t,这个结构体包含了描述一个方法所需要的各种信息。

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

变量介绍可以参考之前文章:iOS 代码注入—— hook 实践

三、Runtime 初始化函数

1、一”码”当先

/*********************************************************************** * _objc_init * Bootstrap initialization. Registers our image notifier with dyld. * Called by libSystem BEFORE library initialization time **********************************************************************/  void _objc_init(void) {     static bool initialized = false;     if (initialized) return;     initialized = true;      // fixme defer initialization until an objc-using image is found?     environ_init();     tls_init();     static_init();     lock_init();     exception_init();      _dyld_objc_notify_register(&map_images, load_images, unmap_image); }

_dyld_objc_notify_register(&map_images, load_images, unmap_image)。这个函数里面的三个参数分别是另外三个函数:

  • map_images — Process the given images which are being mapped in by dyld.(处理那些正在被dyld映射的镜像文件)
  • load_images — Process +load in the given images which are being mapped in by dyld.(处理那些正在被dyld映射的镜像文件中的+load方法)
  • unmap_image — Process the given image which is about to be unmapped by dyld.(处理那些将要被dyld进行去映射操作的镜像文件)

我们查看一下map_images方法,点进去:

/*********************************************************************** * map_images * Process the given images which are being mapped in by dyld. * Calls ABI-agnostic code after taking ABI-specific locks. * * Locking: write-locks runtimeLock **********************************************************************/ void map_images(unsigned count, const char * const paths[],            const struct mach_header * const mhdrs[]) {     mutex_locker_t lock(runtimeLock);     return map_images_nolock(count, paths, mhdrs); }

2、+load方法
Apple文档是这样描述的:
iOS runtime——看这一篇就够了
load函数调用特点如下:
当类被引用进项目的时候就会执行load函数(在main函数开始执行之前),与这个类是否被用到无关,每个类的load函数只会自动调用一次.由于load函数是系统自动加载的,因此不需要调用父类的load函数,否则父类的load函数会多次执行。

  1. 当父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类
  2. 类中的load方法执行顺序要优先于类别(Category)
  3. 当有多个类别(Category)都实现了load方法,这几个load方法都会执行,但执行顺序不确定(其执行顺序与类别在Compile Sources中出现的顺序一致)
  4. 当然当有多个不同的类的时候,每个类load 执行顺序与其在Compile Sources出现的顺序一致

代码验证:

  • 新建两个类 Class1、Class2
  • 新建Class1子类Class1_Son
  • 新建Class1子两个分类Class1+category1、Class1+category2

Compile Sources文件顺序如图:
iOS runtime——看这一篇就够了
每一个类和分类里面都实现+(void)load方法

+(void)load {     NSLog(@"%s",__FUNCTION__); }

打印结果如下:

2022-01-07 15:41:21.334615+0800 loadTest[44548:3453142] +[Class1 load] 2022-01-07 15:41:21.335476+0800 loadTest[44548:3453142] +[Class1_Son load] 2022-01-07 15:41:21.335626+0800 loadTest[44548:3453142] +[Class2 load] 2022-01-07 15:41:21.335733+0800 loadTest[44548:3453142] +[Class1(category2) load] 2022-01-07 15:41:21.335897+0800 loadTest[44548:3453142] +[Class1(category1) load]

3、+initialize方法
Apple文档是这样描述的:
iOS runtime——看这一篇就够了
initialize函数调用特点如下:
initialize在类或者其子类的第一个方法被调用前调用。即使类文件被引用进项目,但是没有使用,initialize不会被调用。由于是系统自动调用,也不需要再调用 [super initialize] ,否则父类的initialize会被多次执行。假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的

  1. 父类的initialize方法会比子类先执行
  2. 当子类未实现initialize方法时,会调用父类initialize方法,子类实现initialize方法时,会覆盖父类initialize方法.
  3. 当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)

代码验证:

  • 新建两个类 Class1
  • 新建Class1子类Class1_Son
  • 新建Class1子两个分类Class1+category1、Class1+category2
    Compile Sources文件顺序如图:
    iOS runtime——看这一篇就够了

每一个类和分类里面都实现+(void)initialize方法
“`
+(void)initialize
{

本文篇幅比较长,创作的目的为了自己日后温习知识所用,希望这篇文章能对你有所帮助。如发现任何有误之处,肯请留言纠正,谢谢。

一、深入代码理解 instance、class object、metaclass

iOS runtime——看这一篇就够了

1、instance对象实例

我们经常使用id来声明一个对象,那id的本质又是什么呢?查看objc/objc.h文件

/// An opaque type that represents an Objective-C class. typedef struct objc_class *Class;  /// Represents an instance of a class. struct objc_object {     Class _Nonnull isa  OBJC_ISA_AVAILABILITY; };  /// A pointer to an instance of a class. typedef struct objc_object *id;

我们创建的一个对象或实例其实就是一个struct objc_object结构体,而我们常用的id也就是这个结构体的指针。

这个结构体只有一个成员变量,这是一个Class类型的变量isa,也是一个结构体指针,isa指针就指向对象所属的类

一个 NSObject 对象占用多少内存空间?
一个NSObject实例对象只有一个isa指针,所以一个isa指针的大小,他在64位的环境下占8个字节,在32位环境上占4个字节。

 NSObject *obj = [[NSObject alloc] init];  NSLog(@"class_getInstanceSize--%zd", class_getInstanceSize([NSObject new]));

输出结果:

class_getInstanceSize--8

2、class object(类对象)/metaclass(元类)

看结构体objc_class的定义

struct objc_class {     Class isa  OBJC_ISA_AVAILABILITY;  #if !__OBJC2__     Class super_class                                        OBJC2_UNAVAILABLE;     const char *name                                         OBJC2_UNAVAILABLE;     long version                                             OBJC2_UNAVAILABLE;     long info                                                OBJC2_UNAVAILABLE;     long instance_size                                       OBJC2_UNAVAILABLE;     struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;     struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;     struct objc_cache *cache                                 OBJC2_UNAVAILABLE;     struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE; #endif  } OBJC2_UNAVAILABLE; /* Use `Class` instead of `struct objc_class *` */
  • Class superclass;——用于获取父类,也就是元类对象,它也是一个Class类型
  • cache_t cache;——是方法缓存
  • class_data_bits_t bits;——用于获取类的具体信息,看到bits
  • class_rw_t *data()函数,该函数的作用就是获取该类的可读写信息,通过class_data_bits_t的bits.data()方法获得,class_rw_t后面会介绍
class_rw_t* data() {         return (class_rw_t *)(bits & FAST_DATA_MASK); }

该结构体的第一个成员变量也是isa指针,这就说明了Class本身其实也是一个对象,我们称之为类对象。类对象中的元数据存储的都是如何创建一个实例的相关信息,那么类对象和类方法应该从哪里创建呢?就是从isa指针指向的结构体创建,类对象的isa指针指向的我们称之为元类(metaclass),元类中保存了创建类对象以及类方法所需的所有信息。

3、isa指针与superclass相关逻辑图
iOS runtime——看这一篇就够了

isa逻辑图

4、总结 + 代码校验

  • 对象 的类(Superclass)是 类(对象) ;
  • 类(对象) 的类(Superclass)是 元类,和类同名;
  • 元类 的类(Superclass)是 根元类 NSObject;
  • 根元类 的类(Superclass)是 自己 ,还是NSObject;
  • 对象的isa指针指向类(对象) ;
  • 类对象的isa指针指向元类,和类同名;
  • 元类的isa指针指向跟根元类 NSObject;
  • 根元类 NSObject的isa指针指向自己。

isa验证

    NSString *string = @"字符串";     Class class1 = object_getClass(string);//NSString类对象     Class metaClass = object_getClass(class1);//NSString元类     Class rootMetaClass = object_getClass(metaClass);//根元类     Class rootRootMetaClass = object_getClass(rootMetaClass);//根元类     NSLog(@"%p 实例对象 ",string);     NSLog(@"%p 类 %@",class1,NSStringFromClass(class1));     NSLog(@"%p 元类 %@",metaClass,NSStringFromClass(metaClass));     NSLog(@"%p 根元类 %@",rootMetaClass,NSStringFromClass(rootMetaClass));     NSLog(@"%p 根根元类 %@",rootRootMetaClass,NSStringFromClass(rootRootMetaClass));      Class rootMetaClass_superclass = rootMetaClass.superclass;//根元类的superclass     NSLog(@"根根元类的superclass:%@",NSStringFromClass(rootMetaClass_superclass));

输出结果:

0x102d48078 实例对象  0x1d80e3d10 类 __NSCFConstantString 0x1d80e3cc0 元类 __NSCFConstantString 0x1d80c66c0 根元类 NSObject 0x1d80c66c0 根根元类 NSObject 根根元类的superclass:NSObject

superclass验证

    NSString *string = @"字符串";     Class class1 = object_getClass(string);//NSString类对象     Class class2 = class1.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class1),NSStringFromClass(class2));     Class class3 = class2.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class2),NSStringFromClass(class3));     Class class4 = class3.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class3),NSStringFromClass(class4));     Class class5 = class4.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class4),NSStringFromClass(class5));     Class class6 = class5.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class5),NSStringFromClass(class6));

输出结果:

 __NSCFConstantString 的superclass是 __NSCFString  __NSCFString 的superclass是 NSMutableString NSMutableString 的superclass是 NSString NSString 的superclass是 NSObject NSObject 的superclass是 (null)

二、class_rw_t 与 class_ro_t

1、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;     } };
  • uint32_t instanceSize;——instance对象占用的内存空间
  • const char * name;——类名
  • const ivar_list_t * ivars;——类的成员变量列表

class_ro_t存储了当前类在编译期就已经确定的属性、方法以及遵循的协议,里面是没有分类的方法的。那些运行时添加的方法将会存储在运行时生成的class_rw_t中。
ro即表示read only,是无法进行修改的。

2、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; // 指向只读的结构体,存放类初始信息      /*      这三个都是二位数组,是可读可写的,包含了类的初始内容、分类的内容。      这三个二位数组中的数据有一部分是从class_ro_t中合并过来的。      */     method_array_t methods; // 方法列表(类对象存放对象方法,元类对象存放类方法)     property_array_t properties; // 属性列表     protocol_array_t protocols; //协议列表      Class firstSubclass;     Class nextSiblingClass;      //...     }

3、class_rw_t生成时机

class_rw_t生成在运行时,在编译期间,class_ro_t结构体就已经确定,objc_class中的bits的data部分存放着该结构体的地址。在runtime运行之后,具体说来是在运行runtime的realizeClass 方法时,会生成class_rw_t结构体,该结构体包含了class_ro_t,并且更新data部分,换成class_rw_t结构体的地址。

类的realizeClass运行之前:
iOS runtime——看这一篇就够了

然后在加载 ObjC 运行时的过程中在 realizeClass 方法中:

  • 从 class_data_bits_t 调用 data 方法,将结果从 class_rw_t 强制转换为 class_ro_t 指针
  • 初始化一个 class_rw_t 结构体
  • 设置结构体 ro 的值以及 flag
  • 最后设置正确的 data。
const class_ro_t *ro = (const class_ro_t *)cls->data(); class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); rw->ro = ro; rw->flags = RW_REALIZED|RW_REALIZING; cls->setData(rw);

但是,在这段代码运行之后 class_rw_t 中的方法,属性以及协议列表均为空。这时需要 realizeClass 调用 methodizeClass 方法来将类自己实现的方法(包括分类)、属性和遵循的协议加载到 methods、 properties 和 protocols 列表中。

realizeClass 方法执行过后的类所占用内存的布局:
iOS runtime——看这一篇就够了

细看两个结构体的成员变量会发现很多相同的地方,他们都存放着当前类的属性、实例变量、方法、协议等等。区别在于:class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定,它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_t是class_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容。

4、method_t

上面我们剖析了class_rw_t、class_ro_t这两个重要部分的结构,并且主要关注了其中的方法列表部分,而从上面的分析,可发现里面最基本也是重要的单位是method_t,这个结构体包含了描述一个方法所需要的各种信息。

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

变量介绍可以参考之前文章:iOS 代码注入—— hook 实践

三、Runtime 初始化函数

1、一”码”当先

/*********************************************************************** * _objc_init * Bootstrap initialization. Registers our image notifier with dyld. * Called by libSystem BEFORE library initialization time **********************************************************************/  void _objc_init(void) {     static bool initialized = false;     if (initialized) return;     initialized = true;      // fixme defer initialization until an objc-using image is found?     environ_init();     tls_init();     static_init();     lock_init();     exception_init();      _dyld_objc_notify_register(&map_images, load_images, unmap_image); }

_dyld_objc_notify_register(&map_images, load_images, unmap_image)。这个函数里面的三个参数分别是另外三个函数:

  • map_images — Process the given images which are being mapped in by dyld.(处理那些正在被dyld映射的镜像文件)
  • load_images — Process +load in the given images which are being mapped in by dyld.(处理那些正在被dyld映射的镜像文件中的+load方法)
  • unmap_image — Process the given image which is about to be unmapped by dyld.(处理那些将要被dyld进行去映射操作的镜像文件)

我们查看一下map_images方法,点进去:

/*********************************************************************** * map_images * Process the given images which are being mapped in by dyld. * Calls ABI-agnostic code after taking ABI-specific locks. * * Locking: write-locks runtimeLock **********************************************************************/ void map_images(unsigned count, const char * const paths[],            const struct mach_header * const mhdrs[]) {     mutex_locker_t lock(runtimeLock);     return map_images_nolock(count, paths, mhdrs); }

2、+load方法
Apple文档是这样描述的:
iOS runtime——看这一篇就够了
load函数调用特点如下:
当类被引用进项目的时候就会执行load函数(在main函数开始执行之前),与这个类是否被用到无关,每个类的load函数只会自动调用一次.由于load函数是系统自动加载的,因此不需要调用父类的load函数,否则父类的load函数会多次执行。

  1. 当父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类
  2. 类中的load方法执行顺序要优先于类别(Category)
  3. 当有多个类别(Category)都实现了load方法,这几个load方法都会执行,但执行顺序不确定(其执行顺序与类别在Compile Sources中出现的顺序一致)
  4. 当然当有多个不同的类的时候,每个类load 执行顺序与其在Compile Sources出现的顺序一致

代码验证:

  • 新建两个类 Class1、Class2
  • 新建Class1子类Class1_Son
  • 新建Class1子两个分类Class1+category1、Class1+category2

Compile Sources文件顺序如图:
iOS runtime——看这一篇就够了
每一个类和分类里面都实现+(void)load方法

+(void)load {     NSLog(@"%s",__FUNCTION__); }

打印结果如下:

2022-01-07 15:41:21.334615+0800 loadTest[44548:3453142] +[Class1 load] 2022-01-07 15:41:21.335476+0800 loadTest[44548:3453142] +[Class1_Son load] 2022-01-07 15:41:21.335626+0800 loadTest[44548:3453142] +[Class2 load] 2022-01-07 15:41:21.335733+0800 loadTest[44548:3453142] +[Class1(category2) load] 2022-01-07 15:41:21.335897+0800 loadTest[44548:3453142] +[Class1(category1) load]

3、+initialize方法
Apple文档是这样描述的:
iOS runtime——看这一篇就够了
initialize函数调用特点如下:
initialize在类或者其子类的第一个方法被调用前调用。即使类文件被引用进项目,但是没有使用,initialize不会被调用。由于是系统自动调用,也不需要再调用 [super initialize] ,否则父类的initialize会被多次执行。假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的

  1. 父类的initialize方法会比子类先执行
  2. 当子类未实现initialize方法时,会调用父类initialize方法,子类实现initialize方法时,会覆盖父类initialize方法.
  3. 当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)

代码验证:

  • 新建两个类 Class1
  • 新建Class1子类Class1_Son
  • 新建Class1子两个分类Class1+category1、Class1+category2
    Compile Sources文件顺序如图:
    iOS runtime——看这一篇就够了

每一个类和分类里面都实现+(void)initialize方法
“`
+(void)initialize
{

本文篇幅比较长,创作的目的为了自己日后温习知识所用,希望这篇文章能对你有所帮助。如发现任何有误之处,肯请留言纠正,谢谢。

一、深入代码理解 instance、class object、metaclass

iOS runtime——看这一篇就够了

1、instance对象实例

我们经常使用id来声明一个对象,那id的本质又是什么呢?查看objc/objc.h文件

/// An opaque type that represents an Objective-C class. typedef struct objc_class *Class;  /// Represents an instance of a class. struct objc_object {     Class _Nonnull isa  OBJC_ISA_AVAILABILITY; };  /// A pointer to an instance of a class. typedef struct objc_object *id;

我们创建的一个对象或实例其实就是一个struct objc_object结构体,而我们常用的id也就是这个结构体的指针。

这个结构体只有一个成员变量,这是一个Class类型的变量isa,也是一个结构体指针,isa指针就指向对象所属的类

一个 NSObject 对象占用多少内存空间?
一个NSObject实例对象只有一个isa指针,所以一个isa指针的大小,他在64位的环境下占8个字节,在32位环境上占4个字节。

 NSObject *obj = [[NSObject alloc] init];  NSLog(@"class_getInstanceSize--%zd", class_getInstanceSize([NSObject new]));

输出结果:

class_getInstanceSize--8

2、class object(类对象)/metaclass(元类)

看结构体objc_class的定义

struct objc_class {     Class isa  OBJC_ISA_AVAILABILITY;  #if !__OBJC2__     Class super_class                                        OBJC2_UNAVAILABLE;     const char *name                                         OBJC2_UNAVAILABLE;     long version                                             OBJC2_UNAVAILABLE;     long info                                                OBJC2_UNAVAILABLE;     long instance_size                                       OBJC2_UNAVAILABLE;     struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;     struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;     struct objc_cache *cache                                 OBJC2_UNAVAILABLE;     struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE; #endif  } OBJC2_UNAVAILABLE; /* Use `Class` instead of `struct objc_class *` */
  • Class superclass;——用于获取父类,也就是元类对象,它也是一个Class类型
  • cache_t cache;——是方法缓存
  • class_data_bits_t bits;——用于获取类的具体信息,看到bits
  • class_rw_t *data()函数,该函数的作用就是获取该类的可读写信息,通过class_data_bits_t的bits.data()方法获得,class_rw_t后面会介绍
class_rw_t* data() {         return (class_rw_t *)(bits & FAST_DATA_MASK); }

该结构体的第一个成员变量也是isa指针,这就说明了Class本身其实也是一个对象,我们称之为类对象。类对象中的元数据存储的都是如何创建一个实例的相关信息,那么类对象和类方法应该从哪里创建呢?就是从isa指针指向的结构体创建,类对象的isa指针指向的我们称之为元类(metaclass),元类中保存了创建类对象以及类方法所需的所有信息。

3、isa指针与superclass相关逻辑图
iOS runtime——看这一篇就够了

isa逻辑图

4、总结 + 代码校验

  • 对象 的类(Superclass)是 类(对象) ;
  • 类(对象) 的类(Superclass)是 元类,和类同名;
  • 元类 的类(Superclass)是 根元类 NSObject;
  • 根元类 的类(Superclass)是 自己 ,还是NSObject;
  • 对象的isa指针指向类(对象) ;
  • 类对象的isa指针指向元类,和类同名;
  • 元类的isa指针指向跟根元类 NSObject;
  • 根元类 NSObject的isa指针指向自己。

isa验证

    NSString *string = @"字符串";     Class class1 = object_getClass(string);//NSString类对象     Class metaClass = object_getClass(class1);//NSString元类     Class rootMetaClass = object_getClass(metaClass);//根元类     Class rootRootMetaClass = object_getClass(rootMetaClass);//根元类     NSLog(@"%p 实例对象 ",string);     NSLog(@"%p 类 %@",class1,NSStringFromClass(class1));     NSLog(@"%p 元类 %@",metaClass,NSStringFromClass(metaClass));     NSLog(@"%p 根元类 %@",rootMetaClass,NSStringFromClass(rootMetaClass));     NSLog(@"%p 根根元类 %@",rootRootMetaClass,NSStringFromClass(rootRootMetaClass));      Class rootMetaClass_superclass = rootMetaClass.superclass;//根元类的superclass     NSLog(@"根根元类的superclass:%@",NSStringFromClass(rootMetaClass_superclass));

输出结果:

0x102d48078 实例对象  0x1d80e3d10 类 __NSCFConstantString 0x1d80e3cc0 元类 __NSCFConstantString 0x1d80c66c0 根元类 NSObject 0x1d80c66c0 根根元类 NSObject 根根元类的superclass:NSObject

superclass验证

    NSString *string = @"字符串";     Class class1 = object_getClass(string);//NSString类对象     Class class2 = class1.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class1),NSStringFromClass(class2));     Class class3 = class2.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class2),NSStringFromClass(class3));     Class class4 = class3.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class3),NSStringFromClass(class4));     Class class5 = class4.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class4),NSStringFromClass(class5));     Class class6 = class5.superclass;     NSLog(@"%@ 的superclass是 %@",NSStringFromClass(class5),NSStringFromClass(class6));

输出结果:

 __NSCFConstantString 的superclass是 __NSCFString  __NSCFString 的superclass是 NSMutableString NSMutableString 的superclass是 NSString NSString 的superclass是 NSObject NSObject 的superclass是 (null)

二、class_rw_t 与 class_ro_t

1、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;     } };
  • uint32_t instanceSize;——instance对象占用的内存空间
  • const char * name;——类名
  • const ivar_list_t * ivars;——类的成员变量列表

class_ro_t存储了当前类在编译期就已经确定的属性、方法以及遵循的协议,里面是没有分类的方法的。那些运行时添加的方法将会存储在运行时生成的class_rw_t中。
ro即表示read only,是无法进行修改的。

2、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; // 指向只读的结构体,存放类初始信息      /*      这三个都是二位数组,是可读可写的,包含了类的初始内容、分类的内容。      这三个二位数组中的数据有一部分是从class_ro_t中合并过来的。      */     method_array_t methods; // 方法列表(类对象存放对象方法,元类对象存放类方法)     property_array_t properties; // 属性列表     protocol_array_t protocols; //协议列表      Class firstSubclass;     Class nextSiblingClass;      //...     }

3、class_rw_t生成时机

class_rw_t生成在运行时,在编译期间,class_ro_t结构体就已经确定,objc_class中的bits的data部分存放着该结构体的地址。在runtime运行之后,具体说来是在运行runtime的realizeClass 方法时,会生成class_rw_t结构体,该结构体包含了class_ro_t,并且更新data部分,换成class_rw_t结构体的地址。

类的realizeClass运行之前:
iOS runtime——看这一篇就够了

然后在加载 ObjC 运行时的过程中在 realizeClass 方法中:

  • 从 class_data_bits_t 调用 data 方法,将结果从 class_rw_t 强制转换为 class_ro_t 指针
  • 初始化一个 class_rw_t 结构体
  • 设置结构体 ro 的值以及 flag
  • 最后设置正确的 data。
const class_ro_t *ro = (const class_ro_t *)cls->data(); class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); rw->ro = ro; rw->flags = RW_REALIZED|RW_REALIZING; cls->setData(rw);

但是,在这段代码运行之后 class_rw_t 中的方法,属性以及协议列表均为空。这时需要 realizeClass 调用 methodizeClass 方法来将类自己实现的方法(包括分类)、属性和遵循的协议加载到 methods、 properties 和 protocols 列表中。

realizeClass 方法执行过后的类所占用内存的布局:
iOS runtime——看这一篇就够了

细看两个结构体的成员变量会发现很多相同的地方,他们都存放着当前类的属性、实例变量、方法、协议等等。区别在于:class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定,它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_t是class_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容。

4、method_t

上面我们剖析了class_rw_t、class_ro_t这两个重要部分的结构,并且主要关注了其中的方法列表部分,而从上面的分析,可发现里面最基本也是重要的单位是method_t,这个结构体包含了描述一个方法所需要的各种信息。

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

变量介绍可以参考之前文章:iOS 代码注入—— hook 实践

三、Runtime 初始化函数

1、一”码”当先

/*********************************************************************** * _objc_init * Bootstrap initialization. Registers our image notifier with dyld. * Called by libSystem BEFORE library initialization time **********************************************************************/  void _objc_init(void) {     static bool initialized = false;     if (initialized) return;     initialized = true;      // fixme defer initialization until an objc-using image is found?     environ_init();     tls_init();     static_init();     lock_init();     exception_init();      _dyld_objc_notify_register(&map_images, load_images, unmap_image); }

_dyld_objc_notify_register(&map_images, load_images, unmap_image)。这个函数里面的三个参数分别是另外三个函数:

  • map_images — Process the given images which are being mapped in by dyld.(处理那些正在被dyld映射的镜像文件)
  • load_images — Process +load in the given images which are being mapped in by dyld.(处理那些正在被dyld映射的镜像文件中的+load方法)
  • unmap_image — Process the given image which is about to be unmapped by dyld.(处理那些将要被dyld进行去映射操作的镜像文件)

我们查看一下map_images方法,点进去:

/*********************************************************************** * map_images * Process the given images which are being mapped in by dyld. * Calls ABI-agnostic code after taking ABI-specific locks. * * Locking: write-locks runtimeLock **********************************************************************/ void map_images(unsigned count, const char * const paths[],            const struct mach_header * const mhdrs[]) {     mutex_locker_t lock(runtimeLock);     return map_images_nolock(count, paths, mhdrs); }

2、+load方法
Apple文档是这样描述的:
iOS runtime——看这一篇就够了
load函数调用特点如下:
当类被引用进项目的时候就会执行load函数(在main函数开始执行之前),与这个类是否被用到无关,每个类的load函数只会自动调用一次.由于load函数是系统自动加载的,因此不需要调用父类的load函数,否则父类的load函数会多次执行。

  1. 当父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类
  2. 类中的load方法执行顺序要优先于类别(Category)
  3. 当有多个类别(Category)都实现了load方法,这几个load方法都会执行,但执行顺序不确定(其执行顺序与类别在Compile Sources中出现的顺序一致)
  4. 当然当有多个不同的类的时候,每个类load 执行顺序与其在Compile Sources出现的顺序一致

代码验证:

  • 新建两个类 Class1、Class2
  • 新建Class1子类Class1_Son
  • 新建Class1子两个分类Class1+category1、Class1+category2

Compile Sources文件顺序如图:
iOS runtime——看这一篇就够了
每一个类和分类里面都实现+(void)load方法

+(void)load {     NSLog(@"%s",__FUNCTION__); }

打印结果如下:

2022-01-07 15:41:21.334615+0800 loadTest[44548:3453142] +[Class1 load] 2022-01-07 15:41:21.335476+0800 loadTest[44548:3453142] +[Class1_Son load] 2022-01-07 15:41:21.335626+0800 loadTest[44548:3453142] +[Class2 load] 2022-01-07 15:41:21.335733+0800 loadTest[44548:3453142] +[Class1(category2) load] 2022-01-07 15:41:21.335897+0800 loadTest[44548:3453142] +[Class1(category1) load]

3、+initialize方法
Apple文档是这样描述的:
iOS runtime——看这一篇就够了
initialize函数调用特点如下:
initialize在类或者其子类的第一个方法被调用前调用。即使类文件被引用进项目,但是没有使用,initialize不会被调用。由于是系统自动调用,也不需要再调用 [super initialize] ,否则父类的initialize会被多次执行。假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的

  1. 父类的initialize方法会比子类先执行
  2. 当子类未实现initialize方法时,会调用父类initialize方法,子类实现initialize方法时,会覆盖父类initialize方法.
  3. 当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)

代码验证:

  • 新建两个类 Class1
  • 新建Class1子类Class1_Son
  • 新建Class1子两个分类Class1+category1、Class1+category2
    Compile Sources文件顺序如图:
    iOS runtime——看这一篇就够了

每一个类和分类里面都实现+(void)initialize方法
“`
+(void)initialize
{

部分转自互联网,侵权删除联系

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » iOS runtime——看这一篇就够了求职学习资料
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们