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, "v@:@"); 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()方法,将分类的方法贴到主类里面。
- 点赞
- 收藏
- 分享
- 文章举报
- 菊花加载第三方--MBprogressHUD 分类: ios技术 2015-02-05 19:21 120人阅读 评论(0) 收藏
- iOS面试 类的扩展,分类,延迟的方法,懒加载
- iOS 通过分类实现 微信的导航栏"加载中..."
- iOS开发中使用[[UIApplication sharedApplication] openURL:]加载其它应用
- iOS平台 加载webView出现 code = -999 错误分析和解决办法
- iOS学习笔记-协议,代码块,分类
- iOS开发UI篇—懒加载
- iOS_WKWebView加载本地网页
- IOS开发UI篇之──自定义加载等待框(MBProgressHUD)
- UI基础:视图控制器.屏幕旋转.MVC 分类: iOS学习-UI 2015-07-02 22:21 62人阅读 评论(0) 收藏
- ios开发 -新浪微博(4)封装item 添加分类
- ios 下拉刷新,下拉加载数据 利用第三方
- iOS分类 UIView
- iOS开发UI篇—懒加载
- iOS 使用分类category和扩展extension
- IOS网络请求封装与下拉刷新上托加载更多
- iOS加载本地html,css样式失效问题
- iOS - 监听UITextField键盘删除键之分类
- 【iOS开发】---- 上拉加载更多(附Demo)
- iOS 静态库加载xib异常