1、Category的实现结构体
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);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
每添加一个分类,编译器就会帮我们生成一个category_t
的结构体。App
在启动的时候,
调用_objc_init
方法进行runtime
初始化,并调用_read_images()
方法进行加载所有的类和分类。大意源码如下:
void _objc_init(void)
{
// 加载所有类、分类等
_read_images()
// 初始化所有类、分类等
load_images()
}
_read_images源码实现
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
// ...
// 加载所有的分类
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
// ...
}
// 将分类的信息附加到类
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags) {
// 将分类的方法、协议、属性等信息附加给类,并通过memmove、memcpy方法附加到类原本信息的前面
// 所以分类的方法调用优先于类本身的方法
}
load_images源码实现
void load_images(const char *path __unused, const struct mach_header *mh)
{
// 初始化所有的类以及它的父类
prepare_load_methods((const headerType *)mh);
// 调用
call_load_methods();
}
// 初始化所有的类以及它的父类
void prepare_load_methods(const headerType *mhdr)
{
// ...
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 递归调用类和所有未加载的父类的+load方法
schedule_class_load(remapClass(classlist[i]));
}
// ...
}
static void schedule_class_load(Class cls)
{
// ...
// 如果有父类,则递归调用加载父类
schedule_class_load(cls->superclass);
// 加载当前类
add_class_to_loadable_list(cls);
// ...
}
void add_class_to_loadable_list(Class cls)
{
// ...
// 将类和类的load方法存储到已加载的数组中
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
Category、load小结
category
分类底层实现是一个category_t
结构体,在运行时才会将分类信息合并到类中
在类和分类结构初始化后,会调用所有的类和分类load
方法。调用顺序如下:
- 先调用类本身的
load
方法,如果有父类,则递归调用父类的load
方法 - 接下来调用分类的
load
方法,分类的load
方法按照编译顺序进行调用
load
方法调用是直接使用函数地址进行调用
Category添加属性
@interface Person : NSObject
// 在类中添加一个实例变量,等同于给类加了一个_age的成员变量
// 以及age的get、set方法声明和实现
@property (nonatomic, assign) NSInteger age;
@end
@interface Person (Eat)
// 在分类中声明一个实例变量,等同于给类多加了count的get和set方法声明
// 但是没有添加_count的成员变量和get、set方法的实现
@property (nonatomic, assign) NSInteger count;
@end
// =================================================================
#import <objc/runtime.h>
@implementation Person (Eat)
static const char kPersonCountKey;
// 使用runtime设置关联属性
// key可以使用
// ① _cmd (表示当前方法的selector)
// ② selector ==> @selector(name)
// ③ 常量字符串 ==> @"name"
// ④ 静态char常量 static const char
- (void)setCount:(NSInteger)count {
objc_setAssociatedObject(self, &kPersonCountKey, @(count), OBJC_ASSOCIATION_ASSIGN);
}
- (NSInteger)count {
return [objc_getAssociatedObject(self, &kPersonCountKey) integerValue];
}
- (void)removeAll {
// 移除跟当前对象的所有关联属性
objc_removeAssociatedObjects(self);
}
@end
2、关联对象
实现关联对象的核心类
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
class AssociationsManager {
static AssociationsHashMap *map;
}
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
class ObjcAssociation {
uintptr_t _policy;
id _value;
}
- 关联对象不是存储在被关联对象的内存中,而是存储在全局统一的一个
AssociationsHashMap
中。所有的关联对象都由AssociationsManager
统一管理 - 在设置关联对象时,以当前对象的内存地址作为
key
,找到当前对象对应的AssociationsHashMap
。不同的对象对应不同的AssociationsHashMap
- 然后在
AssociationsHashMap
中,根据关联对象的key
,找到对应的ObjectAssociationMap
。不同的关联对象,对应不同的ObjectAssociationMap
ObjectAssociationMap
中存储的是关联对象的内存管理策略和值
问:为什么关联对象的内存策略中没有weak对象呢?
因为`weak`修饰的属性,当没有拥有对象之后就会被销毁,并且指针置为`nil`,那么在对象销毁之后,虽然在`map`中既然存在值`object`对应的`AssociationsHashMap`,但是因为`object`地址已经被置位`nil`,会造成坏地址访问而无法根据`object`对象的地址转化为`disguised_object`了。
问:关联对象什么时候释放?
关联对象在类执行dealloc
方法时,会移除跟当前类有关的全部关联对象。
3、initialize
initialize
是在第一个给类发送消息时,才会调用。
因为initialize
是通过objc_msgSend
方式进行调用的。
查找流程:
class_getInstanceMethod
👇
lookUpImpOrForward
👇
initializeNonMetaClass
void initializeNonMetaClass(Class cls)
{
Class supercls;
bool reallyInitialize = NO;
// 递归调用父类的initialize方法
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
// ...
callInitialize(cls);
// ...
}
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
initialize总结
- 每个类只会
initialize
一次,但是initialize是使用objc_msgSend
方式进行调用的, - 所以如果子类没有实现
+ (void)initialize
,会调用父类的+ (void)initialize
方法,所以父类的+ (void)initialize
会被调用多次。 - 父类的
+ (void)initialize
先于子类调用。