一、类拓展
1.1、什么是类拓展
类拓展实际上就是一个匿名的分类。 在.m文件中, @interface @end之间就是类的拓展。 为了给当前的类增加属性与方法。
1.2、类拓展的加载
我们知道在数据很早的时候都会来到 read_images 里面来对类进行处理。 通过镜像文件来读取类的各种信息。那么我们就可以在 read_images 中来看一下类中是否有类拓展中相关的东西, 这样就可以知道类拓展是否是在编译器就加载的。
首先来定义一个类以及拓展
类 Person.m:
#import "Person.h"
#import "Person+PExtension.h"
@interface Person ()
@property (nonatomic, copy) NSString *mName;
@end
@implementation Person
+ (void)load{
NSLog(@"%s",__func__);
}
- (void)extM_method{
NSLog(@"%s",__func__);
}
@end
类拓展 Person+PExtension.h :
@interface Person ()
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, copy) NSString *ext_subject;
- (void)extH_method;
@end
接下来我们在read_images中等待加载Person类的到来:
此时我们就可以通过lldb来看一下当前类中的ro中是否存在类拓展中的东西
可以看到这个时候ro中就已经有了拓展中相关的属性方法。
这也就是说类拓展会在编译时作为类的一部分来编译。
注: 一定要引用类拓展,系统才会去加载这个类拓展,否则不会对类拓展进行处理。
二、分类的属性关联
在上篇介绍到,当加载分类的时候, 会把分类的属性加到类的rw中去。但是此时我们去使用这个属性, 是没办法正常去使用的。原因就是它缺少了set get方法。这个时候我们就需要对它进行重新关联set get方法
@interface Person (Ca)
@property (nonatomic, copy) NSString *caName;
@end
@implementation Person (Ca)
-(void)setCaName:(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 *)caName{
/**
参数一:id object : 获取哪个对象里面的关联的属性。
参数二:void * == id key : 什么属性,与objc_setAssociatedObject中的key相对应,即通过key值取出value。
*/
return objc_getAssociatedObject(self, @"name");
}
@end
那么它是如何让属性与setget进行关联的呢
进入objc_setAssociatedObject() 往下走来到 _object_set_associative_reference()方法:
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;
assert(object);
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
// retain the new value (if any) outside the lock.
// 在锁之外保留新值(如果有)。
ObjcAssociation old_association(0, nil);
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);
}
看一下是如何处理关联的:
- 首先是创建了一个管理者, 用来管理各个类的哈希表。
- 管理者会通过地址来遍历找到相应的类。假如遍历不到类,则创建一张类的哈希表。
- 我们存储的属性名就是一个key通过下标法用这个key去在相应的类的哈希表中寻找这个相应的属性。假如找不到这个属性, 则创建一个属性通过key把值存进去。
- 如果传入一个nil值,则直接移除这个关联对象。
三、load_images
前篇章中说道过程序在_objc_init中通过_dyld_objc_notify_register()调用map_images。 那么这里就来继续向下,研究一下load_images.
我们通过实现load方法来将类分为懒加载类与非懒加载类。 但是无论是分类还是类,只要是实现load方法都会进行调用,那么load方法是在什么时候调用怎么调用的?
进入load_images() -> call_load_methods() 方法:
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();
}
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
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);
objc_autoreleasePoolPop(pool);
loading = NO;
}
可以看到它进行了两个do-while循环, 分别来处理类的load方法 call_class_loads() 与分类的load方法call_category_loads()。也就是拿到所有的load方法后, 会有一个分支. 也就意味着前面会有处理用来区分类和分类的load.
唯一有额外处理的地方: prepare_load_methods((const headerType *)mh); 进入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);
const class_ro_t *ro = (const class_ro_t *)cls->data();
const char *cname = ro->name;
const char *oname = "LGPerson";
if (cname && (strcmp(cname, oname) == 0)) {
printf("_getObjc2NonlazyClassList 类名 :%s - %p 分类名: %s\n",cname,cls,cat->name);
}
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);
}
}
我们知道只有实现load方法它才是一个懒加载类, 所以它会在非懒加载中遍历的到实现load方法的类列表. 进入schedule_class_load() -> add_class_to_loadable_list() 方法:
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
大致可以得知该方法进行了一个存储操作, 讲费懒加载类存储到了一个loadable_classes[]数组中, 当我们需要非懒加载类的时候, 直接到这个数组中去读取就可以了.
拿到了非懒加载类后, 系统就需要去调用load方法, 接着call_load_methods向下走. 来到对类与分类分支的地方.
现在明白了call_class_loads()应该就是调用load方法的地方, 进入 call_class_loads() :
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// 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);
}
// Destroy the detached list.
if (classes) free(classes);
}
对used进行循环, 在非懒加载数组loadable_class中遍历所有的非懒加载类, 拿到每一个非懒加载类的method,也就是load方法. 然后通过SEL直接将消息发送出去.
当类方法调用完成后, call_load_methods()方法中调用call_category_loads()来对分类的load进行加载. 此过程几乎与类的load方法调用一致.
并且根据代码可以得知, 是先处理主类的父类的load方法, 再调用主类的load方法. 再调用分类的load方法.
四、initalize的方法调用
我们知道initalize的方法调用时机也是非常早的, 但是在load_images调用完成了后我们仍看不到initialize的影子.
在Person中加入initalize的方法, 断点调试发现, 无论是Person类进行alloc还是直接class, initalize都会进行调用. 那么initalize方法就应该是跟方法调用的本质有关.
方法的本质就是发送消息, 那么又要来到我们熟悉的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
}
进入initializeAndLeaveLocked()-> initializeAndMaybeRelock()->initializeNonMetaClass()->callInitialize中:
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
在里面直接通过objc_msgSend()调用了initialize()方法