扩展
扩展其实就是匿名的分类,我们知道了分类的加载过程,那么扩展的是在什么时候加载的呢?下面我们来探究下。
我们知道_read_images
方法是在dyld加载库完成后调用的,我们可以在这里拦截,然后看看此时ro中是否已经存在扩展的方法。
我们先添加扩展方法和属性:
@interface LGPerson ()
@property (nonatomic, copy) NSString *mName;
- (void)extM_method;
@end
@implementation LGPerson
- (void)extM_method{
NSLog(@"%s",__func__);
}
@end
然后我们在_read_images
中打断点,通过llvm来看下ro中是否有我们通过扩展添加的方法和属性。
(lldb) p ro
(const class_ro_t *) $0 = 0x00000001000012b8
(lldb) p *$0
(const class_ro_t) $1 = {
flags = 388
instanceStart = 8
instanceSize = 32
reserved = 0
ivarLayout = 0x0000000100000f8b "\x03"
name = 0x0000000100000f82 "LGPerson"
baseMethodList = 0x0000000100001150
baseProtocols = 0x0000000000000000
ivars = 0x0000000100001218
weakIvarLayout = 0x0000000000000000 <no value available>
baseProperties = 0x0000000100001280
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $1.baseMethodList
(method_list_t *const) $2 = 0x0000000100001150
(lldb) p *$2
(method_list_t) $3 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 24
count = 8
first = {
name = "extM_method"
types = 0x0000000100000f8d "v16@0:8"
imp = 0x0000000100000ba0 (objc-debug`-[LGPerson extM_method] at LGPerson.m:19)
}
}
}
(lldb) p $3.get(0)
(method_t) $4 = {
name = "extM_method"
types = 0x0000000100000f8d "v16@0:8"
imp = 0x0000000100000ba0 (objc-debug`-[LGPerson extM_method] at LGPerson.m:19)
}
(lldb) p $3.get(2)
(method_t) $6 = {
name = "mName"
types = 0x0000000100000f95 "@16@0:8"
imp = 0x0000000100000be0 (objc-debug`-[LGPerson mName] at LGPerson.m:12)
}
(lldb) p $3.get(3)
(method_t) $8 = {
name = "setMName:"
types = 0x0000000100000f9d "v24@0:8@16"
imp = 0x0000000100000c10 (objc-debug`-[LGPerson setMName:] at LGPerson.m:12)
}
然后发现我们添加的方法以及属性的setter和getter方法都已经存在ro中的baseMethodList中,所以可以判定,扩展是在编译期添加到ro中的。
添加扩展的方式除了在LGPerson.m文件中直接添加匿名的分类外,我们还可以创建一个LGPerson+LGExtension.h
文件,然后在该文件中添加扩展:
@interface LGPerson ()
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, copy) NSString *ext_subject;
- (void)extH_method;
@end
添加完成后我们再次在_read_images
方法中打印ro,发现我们这次添加的方法和属性并没有在ro中,为什么呢?
我们只需要在LGPerson中添加下面代码,再次运行,打印ro,此时ro中就存在添加的扩展方法和属性了。
#import "LGPerson+LGExtension.h"
因为我们通过扩展添加方法和属性,是针对于LGPerson的,如果没有在LGPerson引入该扩展,编译器就会认为该扩展无效,从而不会编译进去。
问题:我们都知道扩展可以增加属性,但是分类不能添加属性,为什么呢?
通过上面的分析,我们应该可以知道答案了,因为扩展是在编译期间加入到ro中的,而成员变量就是存在ro中,所以扩展可以添加属性。但是分类是在编译后attach到rw中的。rw中不存在ivar成员变量,所以分类就无法添加属性。
当然我们也在运行时通过关联对象实现分类添加属性的目的。
@interface LGPerson (LG)
@property (nonatomic, copy) NSString *cate_name;
@end
@implementation LGPerson (LG)
-(void)setCate_name:(NSString *)cate_name{
/**
参数一:id object : 给哪个对象添加属性,这里要给自己添加属性,用self。
参数二:void * == id key : 属性名,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过次key获得属性的值并返回。
参数三:id value : 关联的值,也就是set方法传入的值给属性去保存。
参数四:objc_AssociationPolicy policy : 策略,属性以什么形式保存。
*/
objc_setAssociatedObject(self, @"name",cate_name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)cate_name{
return objc_getAssociatedObject(self, @"name");
}
@end
这样我们就可以通过关联对象来实现为分类添加属性的目的。我们来看下底层是怎么处理关联对象的。
首先看下objc_setAssociatedObject
的实现
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
底层接口做了一层适配,我们继续跟踪_object_set_associative_reference
方法,
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
//......省略一些代码
id new_value = value ? acquireValue(value, policy) : nil;
{
// 关联对象的管理类
AssociationsManager manager;
// 获取关联的 HashMap -> 存储当前关联对象
AssociationsHashMap &associations(manager.associations());
// 对当前的对象的地址做按位去反操作 - 就是 HashMap 的key (哈希函数)
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
// 获取 AssociationsHashMap 的迭代器 - (对象的) 进行遍历
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
// 根据key去获取关联属性的迭代器
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
// 替换设置新值
j->second = ObjcAssociation(policy, new_value);
} else {
// 到最后了 - 直接设置新值
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
// 如果AssociationsHashMap从没有对象的关联信息表,
// 那么就创建一个map并通过传入的key把value存进去
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
// 如果传入的value是nil,并且之前使用相同的key存储过关联对象,
// 那么就把这个关联的value移除(这也是为什么传入nil对象能够把对象的关联value移除)
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
// 最后把之前使用传入的这个key存储的关联的value释放(OBJC_ASSOCIATION_SETTER_RETAIN策略存储的)
if (old_association.hasValue()) ReleaseValue()(old_association);
}
其中的manager
对象是管理者
AssociationsManager manager;
AssociationsHashMap``是哈希总表
AssociationsHashMap &associations(manager.associations());
因为我们可能好多对象都有关联属性,所以还需要根据对象的地址找到属于该对象的一个哈希表。
disguised_ptr_t disguised_object = DISGUISE(object);
然后判断if (new_value)
,如果此时我们关联属性的value值传nil的话,就会走else中的代码。
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
这里根据我们上面获取到的对象地址,映射到哈希表中的index,然后获取到属于该对象的哈希表。判断if (i != associations.end())
如果没有查找到结尾,就说明成功找到了属于该对象的哈希表。然后通过key值作为index在该对象的哈希表中查找对象的关联属性,然后将该属性删除。所以如果我们之前这个关联属性是有值的,然后我们重新设置为了nil,就会将该关联属性删除。
如果new_value存在的话,就会走if判断中的流程。然后根据对象查找属于该对象的哈希表,如果找到属于该对象的哈希表的话,就在对象的哈希表中查找对应关联属性进行赋值。如果没有找到属于该对象的哈希表,就会创建一张表,然后赋值。
我们来总结下,关联属性的赋值过程:manager管理管理属性的总表,然后我们可以根据对象的地址找到属于该对象的一张哈希表,然后根据关联属性的key找到对应的关联属性,然后就可以赋值处理了。
objc_getAssociatedObject
的实现和set方法差不多:从总表中找对象表,然后对象表中找关联属性,然后返回。
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
// 关联对象的管理类
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// 生成伪装地址。处理参数 object 地址
disguised_ptr_t disguised_object = DISGUISE(object);
// 所有对象的额迭代器
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
// 内部对象的迭代器
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 找到 - 把值和策略读取出来
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
// OBJC_ASSOCIATION_GETTER_RETAIN - 就会持有一下
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
我们通过关联对象达到了添加属性的目的,那么如果我们的属性释放了,相应的关联对象是否会释放掉呢?我们只要追踪一下dealloc方法就知道了,通过追踪dealloc
方法,我们追踪到下面的方法
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
在上面的方法中判断如果存在关联属性,就会调用_object_remove_assocations
,该方法就是移除关联属性的方法,方法详情见下面
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
所以结论是:当对象释放的时候,在dealloc方法中会移除关联属性。
+ (void)load
方法
我们在日常的开发中,经常用到load方法,那么load方法是在什么时候调用的呢?下面我们来探究下
我们回到_objc_init
方法,里面调用了下面的方法
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
我们现在知道了,map_images只要是用来读取各种class、category、protocal等信息,然后存储起来。那么load_images是干什么的呢?
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
load_images中有两个主要的功能:call_load_methods
用来调用load方法,prepare_load_methods
为调用load方法做一些准备工作。
首先我们来看看prepare_load_methods
方法。
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]));
}
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
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);
}
}
prepare_load_methods
方法中首先通过_getObjc2NonlazyClassList
获取到非懒加载类的列表,因为实现了load的方法的class都是非懒加载类。然后调用了schedule_class_load
。在这个方法中又调用了add_class_to_loadable_list(cls);
,继续追踪该方法。
method = cls->getLoadMethod();
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
在add_class_to_loadable_list
中调用了上面的代码,将cls和method进行存储起来。cls就是我们前面获取到的非懒加载类,method就是对应class的load方法。到此为止,就是获取非懒加载class列表,然后将class和load方法存到loadable_class
中。
我们返回到prepare_load_methods
,下面还有对category的处理。
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClassWithoutSwift(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
和上面类的处理相似,获取到非懒加载的category列表,然后遍历category调用add_category_to_loadable_list
进行处理。
method = _category_getLoadMethod(cat);
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
和class的处理方式一样,获取到cat和cat对应的load方法,然后进行存储到loadable_categories
中。
到此为止,准备调用load方法的准备工作已经完毕,分别将class和cate的load方法存储到了不同的地方。
下面我们回到load_images方法查看call_load_methods();
是怎么调用load方法的。
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
在该方法中通过一个do while 循环调用call_class_loads
和call_category_loads
,调用class的load方法以及category的load方法。
我们先来看call_class_loads
方法。
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
循环处理class,load_method
是个调用load方法的函数,接收id和SEL参数。通过SEL就能找到对应的imp进行load的调用。
typedef void(*load_method_t)(id, SEL);
将cls和SEL_load传到上面的函数中,完成了cls的load方法调用。
接下来我们返回到call_load_methods
方法中查看call_category_loads();
的调用。
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
和class的处理方式相同,通过load_method调用cat的load方法。
到此也完成了对分类的load方法调用。
问题:class和category都实现了load方法,会怎么调用呢?
从上面的分析中我们可以知道,在处理load方法的时候,先调用了class的load方法,然后再调用category的load方法。
那么普通的方法呢,class和分类都实现会怎么样呢?
在map_images分析中,category是通过attach到rw中methodList。attach的过程是将新的list添加到栈的头部,所以调用方法的时候从头部开始查找,自然会先找到category中的方法然后进行调用。这就会造成category的中的方法会覆盖class中方法的假像。
initialize方法
我们都知道initialize也是调用比较早的方法,那么它是在什么时候调用的呢?
我们可以通过实验发现,只要class调用方法,不管是哪个方法,都会触发initialize的调用。所以我们可以推测应该是在方法的调用流程中处理了initialize。
我们来的我们熟悉的lookUpImpOrForward
方法。发现下面与initialize相关的代码
if (initialize && !cls->isInitialized()) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// If sel == initialize, class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
跟踪来到initializeAndMaybeRelock
方法,在该方法中调用了下面代码
initializeNonMetaClass(nonmeta);
然后initializeNonMetaClass
方法中调用了下面的call方法
callInitialize(cls);
callInitialize
方法的实现如下
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
给cls发送SEL_initialize消息,走普通的消息发送流程,从而完成了initialize的调用。
总结:initialize方法是在cls第一次接受消息的时候调用的,因为最后调用的时候走的是消息发送流程,所以查找方法进行调用的时候有个优先顺序。如果category实现了initialize,则不会再调用cls中的,子类如果实现了就只会调用子类的,不会调用父类了。