category简介
-
Objective-C 2.0之后添加的语言特性
-
使用场景
1> 为存在的类添加方法
2> 把类的实现分开在不同的文件中。好处:a)减少单个文件的体积, b)把不同的功能分开,c)可以多人开发同一个类,d)按需加载想要的分类
3> 声明私有方法
4> 模拟多继承 -
在https://opensource.apple.com/tarballs/objc4/源码的objc-runtime-new.h中的定义如下:
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);
};
即category的底层其实就是一个结构体,里边有类名、类、实例方法、类方法、协议信息、属性信息
等
- Category和Class Extension的区别
1> Class Extension属于类的一部分,在编译的时候,其信息就已经包含在了类信息中了
2> Category是在Runtime时,才将信息合并到类中
Category的加载处理过程
源码层面分析
- 先找到oc运行的入口方法
_objc_init
(objc_os.mm
文件中)
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
- category被附加到类上是在
map_images
的时候发生的,在objc_runtime_new.mm
文件中找到map_images
方法
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
rwlock_writer_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
-
接下来的跟踪路径:
map_images_nolock
->_read_images
->remethodizeClass
->attachCategories
->attachLists
->realloc
->memmove
->memcpy
-
总结:
1> Category是通过runtime进行加载的
2> 先编译的分类会存在在数组的后边(即,调用方法的时候,后编译的分类中的方法会先调用)
3> 合并后的Category方法、属性、协议会被放入数组中,插入到类原来数据的前面
4> 类与分类中相同的方法不会被相互覆盖,只是调用的时候,会调用分类中的该方法
#import <Foundation/Foundation.h>
@interface Person : NSObject
- (void)test1;
@end
#import "Person.h"
@interface Person (Test1)
- (void)test1;
@end
/// 在main.m文件中
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person+Test1.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test1]; /// 此句调用的是 Person+Test1 文件中的 test1 方法
}
return 0;
}
Category中的+load方法
-
+load
方法会在runtime加载类、分类的时候调用 - 每个类、分类的
+load
方法在运行中只调用一次 - 调用顺序:
1> 先调用类的+load
方法。按照先编译先调用的顺序,调用子类的+load
方法之前会先调用父类的+load
方法
2> 再调用分类的+load
方法。调用顺序按照编译的先后顺序
源码分析+load
方法的加载过程
- 类的加载
1> objc-os.mm 文件的_objc_init
->load_images
->prepare_load_methods
->call_load_methods
->call_class_loads
->(*load_method)(cls, SEL_load)
2> 其中prepare_load_methods
方法中的schedule_class_load
,会对拥有继承关系的类进行排序:父类优先于子类先加载
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
-
分类的加载
objc-os.mm 文件的_objc_init
->load_images
->prepare_load_methods
->call_load_methods
->call_category_loads
->(*load_method)(cls, SEL_load)
-
特别注意:不管是类还是分类,
+load
方法是根据方法地址直接调用,并不是通过objc_msgSend
函数调用
Category中的+initinitialize方法
-
在类第一次接到消息时调用
-
每个类只会initialize一次,父类的
+initialize
方法可能会调用多次 -
调用顺序:
先调用父类再调用子类 -
initialize的加载过程
objc-runtime-new.mm文件中class_getInstanceMethod
->lookUpImpOrNil
->lookUpImpOrForward
->_class_initialize
中的部分代码如下 ->callInitialize
->objc_msgSend
void _class_initialize(Class cls)
{
Class supercls;
bool reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
}
+load和+initialize的区别
-
调用方式
1> load是根据函数地址直接调用
2> initialize是通过objc_msgSend调用 -
调用时刻
1> load是runtime加载类、分类的时候调用(只会调用1次)
2> initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次) -
调用顺序
load
1> 先调用类的load
a) 先编译的类,优先调用load
b) 调用子类的load之前,会先调用父类的load
2> 再调用分类的load
a) 先编译的分类,优先调用load
initialize
1> 先初始化父类
2> 再初始化子类(可能最终调用的是父类的initialize方法)
关联对象
-
原理图
关联对象原理图.png -
相关API
1> 添加关联对象
void objc_setAssociatedObject(id object, const void * key,
id value, objc_AssociationPolicy policy)
2> 获得关联对象
id objc_getAssociatedObject(id object, const void * key)
1> 移除所有的关联对象
void objc_removeAssociatedObjects(id object)
网友评论