iOS 分类的加载
iOS 分类的加载
前言
通过上一篇对类加载的分析探索,我们了解了
dyld
进入程序,加载完镜像文件,在objc_init
方法中注册回调函数,然后通过map_images
的一系列操作,将其加载到内存中。
在map_images
中,通过_read_images
方法,先创建表,遍历所有的类将其映射到表中,然后将SEL
、协议
添加到对应的表中,对类和非懒加载类
进行初始化,对ro
、rw
赋值等等一系列流程。那么今天我们先来看几道关于ro
、rw
的面试题,然后再了解一下类和非懒加载类
以及分类Category
的加载。
1. Runtime 面试题
问 :可否给类动态添加成员变量?为什么?
答 :动态创建的类,可以添加成员变量,已经注册好的类,不能动态添加成员变量。
分析如下:
首先,我们使用Runtime API
编写下面代码:
// 1: 动态创建类
Class LGPerson = objc_allocateClassPair([NSObject class], "LGPerson", 0);
// 2: 添加成员变量
// ivar - ro - ivarlist
class_addIvar(LGPerson, "lgName", sizeof(NSString *), log2(sizeof(NSString *)), "@");
// 3: 注册到内存
objc_registerClassPair(LGPerson);
通过上面代码进行 动态创建类、添加成员变量、然后注册到内存,运行代码,程序可以正常运行。
当我们对 步骤二 和 步骤三 顺序互换,先注册到内存,再添加成员变量,此时,程序就会崩溃,接下来通过源码来分析一下。
通过之前的学习,知道,成员变量是存储在Class
中class_rw_t *data()
中的ro
中的ivar_list_t * ivars
里面。如下源码:
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();
}
...
}
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_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;
...
...
}
通过上一篇的学习,我们知道ro
中在程序编译时就进行赋值的,只能读取,不能进行改变,rw
是在对类初始化时,进行赋值的。而rw—>ro
的赋值也是在这个时候完成。
我们查看Runtime
将注册类的API
objc_registerClassPair
,源码如下:
/***********************************************************************
* objc_registerClassPair
* fixme
* Locking: acquires runtimeLock
**********************************************************************/
void objc_registerClassPair(Class cls)
{
mutex_locker_t lock(runtimeLock);
checkIsKnownClass(cls);
if ((cls->data()->flags & RW_CONSTRUCTED) ||
(cls->ISA()->data()->flags & RW_CONSTRUCTED))
{
_objc_inform("objc_registerClassPair: class '%s' was already "
"registered!", cls->data()->ro->name);
return;
}
if (!(cls->data()->flags & RW_CONSTRUCTING) ||
!(cls->ISA()->data()->flags & RW_CONSTRUCTING))
{
_objc_inform("objc_registerClassPair: class '%s' was not "
"allocated with objc_allocateClassPair!",
cls->data()->ro->name);
return;
}
// Clear "under construction" bit, set "done constructing" bit
// 替换
cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
// Add to named class table.
addNamedClass(cls, cls->data()->ro->name);
}
其中关键步骤
cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
在注册时,将RW_CONSTRUCTING | RW_REALIZING
替换为RW_CONSTRUCTED
。
通过查看动态添加成员变量class_addIvar API
BOOL
class_addIvar(Class cls, const char *name, size_t size,
uint8_t alignment, const char *type)
{
if (!cls) return NO;
if (!type) type = "";
if (name && 0 == strcmp(name, "")) name = nil;
mutex_locker_t lock(runtimeLock);
checkIsKnownClass(cls);
assert(cls->isRealized());
// No class variables
if (cls->isMetaClass()) {
return NO;
}
// Can only add ivars to in-construction classes.
if (!(cls->data()->flags & RW_CONSTRUCTING)) {
return NO;
}
// Check for existing ivar with this name, unless it's anonymous.
// Check for too-big ivar.
// fixme check for superclass ivar too?
if ((name && getIvar(cls, name)) || size > UINT32_MAX) {
return NO;
}
class_ro_t *ro_w = make_ro_writeable(cls->data());
// fixme allocate less memory here
ivar_list_t *oldlist, *newlist;
if ((oldlist = (ivar_list_t *)cls->data()->ro->ivars)) {
size_t oldsize = oldlist->byteSize();
newlist = (ivar_list_t *)calloc(oldsize + oldlist->entsize(), 1);
memcpy(newlist, oldlist, oldsize);
free(oldlist);
} else {
newlist = (ivar_list_t *)calloc(sizeof(ivar_list_t), 1);
newlist->entsizeAndFlags = (uint32_t)sizeof(ivar_t);
}
uint32_t offset = cls->unalignedInstanceSize();
uint32_t alignMask = (1<<alignment)-1;
offset = (offset + alignMask) & ~alignMask;
ivar_t& ivar = newlist->get(newlist->count++);
#if __x86_64__
// Deliberately over-allocate the ivar offset variable.
// Use calloc() to clear all 64 bits. See the note in struct ivar_t.
ivar.offset = (int32_t *)(int64_t *)calloc(sizeof(int64_t), 1);
#else
ivar.offset = (int32_t *)malloc(sizeof(int32_t));
#endif
*ivar.offset = offset;
ivar.name = name ? strdupIfMutable(name) : nil;
ivar.type = strdupIfMutable(type);
ivar.alignment_raw = alignment;
ivar.size = (uint32_t)size;
ro_w->ivars = newlist;
cls->setInstanceSize((uint32_t)(offset + size));
// Ivar layout updated in registerClass.
return YES;
}
看上面源码中的关键代码,
// Can only add ivars to in-construction classes.
if (!(cls->data()->flags & RW_CONSTRUCTING)) {
return NO;
}
当我们在注册类时,对cls->ISA()->changeInfo
和cls->changeInfo
进行修改,所以在上面判断中直接返回NO
,而不会在后续对ro
中的ivar
赋值。所以不能对注册好的类,进行动态添加成员变量。
那么接下来对
LGPerson
类,添加属性,并打印,如下代码,问能否打印?为什么?
定义如下方法:
void lg_class_addProperty(Class targetClass , const char *propertyName){
objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //type
objc_property_attribute_t ownership0 = { "C", "" }; // C = copy
objc_property_attribute_t ownership = { "N", "" }; //N = nonatomic
objc_property_attribute_t backingivar = { "V", [NSString stringWithFormat:@"_%@",[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]].UTF8String }; //variable name
objc_property_attribute_t attrs[] = {type, ownership0, ownership,backingivar};
class_addProperty(targetClass, propertyName, attrs, 4);
}
添加属性到
rw
// 添加property - rw
lg_class_addProperty(LGPerson, "subject");
[person setValue:@"master" forKey:@"subject"];
NSLog(@"%@",[person valueForKey:@"subject"]);
答案:不能打印,我们只添加了subject
属性到rw
,由于是我们动态添加的,系统并未生成setter
和getter
,而赋值打印相当于调用了这两个方法,所以不能打印。
我们需要添加如下代码,将setter
和getter
添加到方法列表中。
添加setter + getter 方法
class_addMethod(LGPerson, @selector(setSubject:), (IMP)lgSetter, "aaa@qq.com:@");
class_addMethod(LGPerson, @selector(subject), (IMP)lgName, "@@:");
小结:
1. 动态创建的类,可以动态添加成员变量和属性,而已经创建好注册好的类,不能动态添加成员变量。
2. 动态添加的属性,默认不会生成 setter 和 getter,需要将setter + getter
方法添加到方法列表中。
2. 类和非懒加载类的加载
2.1 类和非懒加载类分析
通过上一篇的学习得知,在_read_images()
方法中,在将类添加到表中之后,会进行一系列的操作,比如:将SEL
注册到哈希表中、将协议
添加到表中、初始化非懒加载类、初始化懒加载类、处理分类category
等。
那么什么是懒加载类,什么是非懒加载类呢?
接下来,先创建LGTeacher
、LGStudent
、LGPerson
三个类,在其中前两个类中,实现如下load
方法:
+(void)load
{
NSLog(@"%s",__func__);
}
然后在_read_images()
方法中,初始化非懒加载类
的时候,打印类信息,如下
然后运行代码,查看控制台,
只打印了有load
方法的两个类,并未找到没有实现load
方法的LGPerson
,而LGPerson
是在main
函数里进行了调用,实例出一个对象。
由此:
load
方法会将类的加载提前,将类的编译期提前到加载数据的地方。而实现了load
方法的类,就是非懒加载类。而懒加载类是当你用到此类的时候再进行加载实现。
接下来,我们分析一下 非懒加载类的加载 和 懒加载类的加载。
2.2 非懒加载类的加载
在上一篇中分析中得知,当进入_read_images()
方法中的非懒加载类的加载步骤后,流程如下:
- 获取所有
非懒加载类
,classref_t *classlist = _getObjc2NonlazyClassList(hi, &count)
- 循环读取
非懒加载类
,将其加到内存,addClassTableEntry(cls)
- 实现所有
非懒加载类
(实例化类对象的一些信息,例如rw),realizeClassWithoutSwift(cls)
- 在
realizeClassWithoutSwift(cls)
中,对cls
的supClass
、isa
以及rw->ro
等进行赋值,然后进入methodizeClass(cls)
,用ro
中的数据对rw
进行赋值。
rw->ro
的赋值
2.3 懒加载类的加载
懒加载类是在调用的时候才会加载,那么我们在main
函数中,创建LGPerson
,然后打下断点,进行分析。
当我们调用alloc
方法时,会进入方法查找流程,必然会进入lookUpImpOrForward
方法。
然后判断进入realizeClassMaybeSwiftAndLeaveLocked
方法,
查看realizeClassMaybeSwiftAndLeaveLocked
方法源码:
static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}
在realizeClassMaybeSwiftAndLeaveLocked()
调用realizeClassMaybeSwiftMaybeRelock()
方法,源码如下:
/***********************************************************************
* realizeClassMaybeSwift (MaybeRelock / AndUnlock / AndLeaveLocked)
* Realize a class that might be a Swift class.
* Returns the real class structure for the class.
* Locking:
* runtimeLock must be held on entry
* runtimeLock may be dropped during execution
* ...AndUnlock function leaves runtimeLock unlocked on exit
* ...AndLeaveLocked re-acquires runtimeLock if it was dropped
* This complication avoids repeated lock transitions in some cases.
**********************************************************************/
static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
// 判断是否是Swift
if (!cls->isSwiftStable_ButAllowLegacyForNow()) { // 否
// Non-Swift class. Realize it now with the lock still held.
// fixme wrong in the future for objc subclasses of swift classes
// 初始化,
realizeClassWithoutSwift(cls);
if (!leaveLocked) lock.unlock();
} else {
// Swift class. We need to drop locks and call the Swift
// runtime to initialize it.
lock.unlock();
cls = realizeSwiftClass(cls);
assert(cls->isRealized()); // callback must have provoked realization
if (leaveLocked) lock.lock();
}
return cls;
}
通过上面
realizeClassMaybeSwiftMaybeRelock
源码分析,先判断是否是Swift
,不是,则调用realizeClassWithoutSwift(cls)
对类初始化,对superClass
、isa
、rw
赋值,和上面的非懒加载类
一样。
当类初始化完成后,会进入lookUpImpOrForward
的方法查找流程,然后进行初始化,分配空间等等的操作。
注意:
当一个类继承另一个类的时候,子类实现了load
方法,子类变成了非懒加载类
,而父类也会变成非懒加载类
。通过realizeClassWithoutSwift
方法中的下面的代码,递归对supercls
初始化。
3. 分类 Category 的加载
首先,我们创建一个LGTeacher
类和LGTeacher (test)
分类如下:
@interface LGTeacher : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *subject;
@property (nonatomic, assign) int age;
- (void)sayHello;
+ (void)sayMaster;
@end
#import "LGTeacher.h"
@implementation LGTeacher
//+ (void)load{
// NSLog(@"%s",__func__);
//}
@end
@interface LGTeacher (test)
@property (nonatomic, copy) NSString *cate_p1;
@property (nonatomic, copy) NSString *cate_p2;
- (void)cate_instanceMethod1;
- (void)cate_instanceMethod2;
+ (void)cate_classMethod1;
+ (void)cate_classMethod2;
@end
@implementation LGTeacher (test)
//+ (void)load{
// NSLog(@"分类 load");
//}
- (void)setCate_p1:(NSString *)cate_p1{
}
- (NSString *)cate_p1{
return @"cate_p1";
}
- (void)cate_instanceMethod2{
NSLog(@"%s",__func__);
}
+ (void)cate_classMethod2{
NSLog(@"%s",__func__);
}
@end
3.1 clang 初探 分类 Category 的结构
通过clang
,查看c++
文件,
_category_t
的结构:
// attachlist 方法 对象 C++
struct _category_t {
const char *name; // 谁的分类
struct _class_t *cls; // 类
const struct _method_list_t *instance_methods; // 对象方法列表
const struct _method_list_t *class_methods; // 类方法列表
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
// OC
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);
};
为什么会有两个方法列表呢?
对象方法存在类中,类方法存在元类中。当通过attachLists
方法添加方法时,需要添加到不同的类中。
3.2 类 与 分类 Category 的搭配加载
1. 懒加载的分类(未实现load
方法)
我们知道在read_iamge
方法中,在类初始化时,是通过在methodizeClass
方法中,用ro
对rw
进行赋值的,通过attachCategories
方法,将分类中的方法添加到方法列表中的。
通过断点调试,判断为LGTeacher
时,
category_list
为NULL
,当为NULL
时,attachCategories
直接返回,
那么分类的方法是否添加呢?我们通过
lldb
分析一下:
methods
里面有值,
打印methods
里面的方法,
我们发现,方法列表中已经有分类的方法,那么说明,懒加载的分类的加载时在编译时处理的,不需要添加到表中,直接添加到相应
data()
的ro
里面,然后在初始化类的时候,直接用ro->baseMethods()
对rw->methods
赋值。即下面的代码
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
那么 懒加载的分类(未实现load
方法) 搭配 懒加载类 和 非懒加载类 时,又会出现两中情况。
上面说的是搭配 懒加载类 的情况,而两者的
分类的加载
都是一样的,是在编译期进行处理,直接添加到相应data()
的ro
中,主要的区别就是在对类的加载,上面的类和非懒加载类的加载已经说过,懒加载类
是在发送消息时,通过lookuporforward
->realizeClassWithoutSwift
->methodlizeClass
的流程加载的。而非懒加载类
是通过read_images
->realizeClassWithoutSwift
->methodlizeClass
的流程加载的.
2. 非懒加载的分类(实现load
方法)
当分类
实现load
方法时,其加载也会被提前,即read_iamges
方法中对分类的处理,如下关键代码:
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) { }
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
// ✅判断是否是对象方法
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
// ✅为类添加未附加的类别
addUnattachedCategoryForClass(cat, cls, hi);
const char *cname = cls->demangledName();
const char *oname = "LGTeacher";
if (cname && (strcmp(cname, oname) == 0)) {
printf("_getObjc2CategoryList :%s \n",cname);
}
// ✅为判断是否是懒加载类
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) { }
}
// // ✅判断是否是类方法
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
// ✅为判断是否是懒加载类
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) { }
}
}
}
先读取分类,判断分类中的方法是否是对象方法,然后addUnattachedCategoryForClass
为类添加未附加的类别。然后判断是否是懒加载类,不是懒加载类
则调用remethodizeClass
方法,将分类方法添加的主类的方法列表中
remethodizeClass
方法源码:
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
//✅添加分类方法
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
而懒加载类
的加载是在发送消息时,通过lookuporforward
->realizeClassWithoutSwift
->methodlizeClass
的流程加载的。
所以,当搭配
非懒加载类
的时候,会进入remethodizeClass
方法,进而调用attachCategories()
方法,将分类的方法贴到主类里面。
那么当
非懒加载分类
搭配懒加载类
的时候,此时就会出现,分类已经加载,而主类还未加载。分类的方法不知要添加到哪里主类里面去,那这样的分类提前加载是不是没有意义呢?
当然不是,那么接下来,系统会调用下面方法,
最终进入到prepare_load_methods
方法中,然后调用realizeClassWithoutSwift
方法,源码如下:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// map_images 完毕了
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
const class_ro_t *ro = (const class_ro_t *)cls->data();
const char *cname = ro->name;
const char *oname = "LGTeacher";
if (strcmp(cname, oname) == 0) {
printf("prepare_load_methods :%s \n",cname);
}
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
而当从此进入realizeClassWithoutSwift
方法,进而调用methodizeClass
方法时,此时methodizeClass
方法中cats
不为空,然后调用attachCategories()
方法,将分类的方法贴到主类里面。
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
总结
本篇介绍了 懒加载类 和 非懒加载类 的加载,以及 懒加载分类 和 非懒加载分类 搭配 懒加载类 和 非懒加载类 的4种组合情况的加载原理。
-
实现了
load
方法的类为非懒加载类
,在启动时初始化 -
未实现
load
方法的方法的类为懒加载类
,在调用的时候初始化 -
非懒加载类
的加载:read_iamges
- 循环读取非懒加载类,将其加到内存,
addClassTableEntry(cls)
- 在
realizeClassWithoutSwift(cls)
中,对cls
的supClass
、isa
以及rw->ro
等进行赋值,然后进入methodizeClass(cls)
,用ro
中的数据对rw
进行赋值。
-
懒加载类
的加载:在调用的时候初始化,比然进入lookUpImpOrForward
方法- 进入方法查找流程,进入
lookUpImpOrForward
-
!cls->isRealized()
判断是否是懒加载类
,不是,开始方法查找 - 是
懒加载类
,进入realizeClassMaybeSwiftAndLeaveLocked
- 调用
realizeClassWithoutSwift
- 进入方法查找流程,进入
-
懒加载分类
的加载是在编译期处理的,直接添加到相应的data()
的ro
中,在类初始化的时候,直接用ro->baseMethods()
对rw->methods
赋值。 -
非懒加载分类
的加载 +懒加载类
-
read_image
中 对分类的处理 - 判断是否是
对象方法
还是类方法
, - 为类添加未附加的类别,
addUnattachedCategoryForClass
- 判断是否是
懒加载类
,cls->isRealized()
,此次为懒加载类
,不进入判断 - 进入
prepare_load_methods
- 进入
realizeClassWithoutSwift
- 进入
methodizeClass
,此时category_list *cats
不为空 - 调用
attachCategories()
方法,将分类的方法贴到主类里面。
-
-
非懒加载分类
的加载 +非懒加载类
-
read_image
中 对分类的处理 - 判断是否是
对象方法
还是类方法
, - 为类添加未附加的类别,
addUnattachedCategoryForClass
- 判断是否是
懒加载类
,cls->isRealized()
,此次为非懒加载类
,进入判断 - 进入
remethodizeClass
- 调用
attachCategories()
方法,将分类的方法贴到主类里面。
-
推荐阅读
-
iOS 分类的加载
-
ProgressBar改变成我们想要的加载动画
-
iOS 应用程序加载
-
IOS开发(4)xcode9.2 删除项目默认的Main.storyboard
-
iOS开发笔记之Xcode9.2下SDK开发-生成Framework的图解流程
-
Oracle操作的部分ddl语句 博客分类: 数据库 oraclePostgreSQL
-
OC开发之——分类的基本应用(31)
-
基于Axis的Web Service客户端调用 博客分类: webservice WebWebServicelog4jjunitApache
-
基于Axis的Web Service客户端调用 博客分类: webservice WebWebServicelog4jjunitApache
-
大数据时代的 9 大Key-Value存储数据库 博客分类: 架构研究