category
category中的方法是通过runtime动态的将分类的方法合并到类对象、元类对象中
最后面参与编译的分类,方法的实现在最前面
category 的底层CPP结构是 _category_t,一个分类对应一个结构体对象,然后通过runtime将方法合并到类对象中
// .h
#import "MJPerson.h"
@interface MJPerson (Eat) <NSCopying, NSCoding>
- (void)eat;
@property (assign, nonatomic) int weight;
@property (assign, nonatomic) double height;
@end
// .m
#import "MJPerson+Eat.h"
@implementation MJPerson (Eat)
- (void)run
{
NSLog(@"MJPerson (Eat) - run");
}
- (void)eat
{
NSLog(@"eat");
}
- (void)eat1
{
NSLog(@"eat1");
}
+ (void)eat2
{
}
+ (void)eat3
{
}
@end
转换后
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; // 属性
};
利用结构体创建出一个对象 OBJC_$_CATEGORY_MJPerson_$_Eat
static struct _category_t _OBJC_$_CATEGORY_MJPerson_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"MJPerson",
0, // &OBJC_CLASS_$_MJPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MJPerson_$_Eat,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_MJPerson_$_Eat,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_MJPerson_$_Eat,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MJPerson_$_Eat,
};
// 这些是不同的结构体,去转换后的代码里看
_OBJC_$_CATEGORY_INSTANCE_METHODS_MJPerson_$_Eat
_OBJC_$_CATEGORY_CLASS_METHODS_MJPerson_$_Eat
…… …… ……
这里要通过转换后的CPP代码去看,编译完成后,类名、对象方法、类方法、协议、属性 存放到了结构体 _category_t 底层结构中,并没有合并到类对象中,在程序运行时,runtime会将category的数据,合并到类对象(元类对象)中
分类的加载处理过程
-
后面参与
编译的Category数据,会在数组的前面(优先调用) -
使用
msg_send发送消息的调用顺序1.分类 2.类
+load方法
/***********************************************************************
* call_load_methods
* Call all pending class and category +load methods.
* Class +load methods are called superclass-first.
* Category +load methods are not called until after the parent class's +load.
*
* This method must be RE-ENTRANT, because a +load could trigger
* more image mapping. In addition, the superclass-first ordering
* must be preserved in the face of re-entrant calls. Therefore,
* only the OUTERMOST call of this function will do anything, and
* that call will handle all loadable classes, even those generated
* while it was running.
*
* The sequence below preserves +load ordering in the face of
* image loading during a +load, and make sure that no
* +load method is forgotten because it was added during
* a +load call.
* Sequence:
* 1. Repeatedly call class +loads until there aren't any more
* 2. Call category +loads ONCE.
* 3. Run more +loads if:
* (a) there are more classes to load, OR
* (b) there are some potential category +loads that have
* still never been attempted.
* Category +loads are only run once to ensure "parent class first"
* ordering, even if a category +load triggers a new loadable class
* and a new loadable category attached to that class.
*
* Locking: loadMethodLock must be held by the caller
* All other locks must not be held.
**********************************************************************/
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.重复调用class +loads,直到没有其他类
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;
}
/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
static void call_class_loads(void)
{
...................
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
// typedef void(*load_method_t)(id, SEL); 函数指针
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, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
/***********************************************************************
* call_category_loads
* Call some pending category +load methods.
* The parent class of the +load-implementing categories has all of
* its categories attached, in case some are lazily waiting for +initalize.
* Don't call +load unless the parent class is connected.
* If new categories become loadable, +load is NOT called, and they
* are added to the end of the loadable list, and we return TRUE.
* Return FALSE if no new categories became loadable.
*
* Called only by call_load_methods().
**********************************************************************/
static bool call_category_loads(void)
{
...................
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
// 直接找到load函数指针
// typedef void(*load_method_t)(id, SEL); 函数指针
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
...................
// 调用load函数
(*load_method)(cls, @selector(load));
}
}
...................
}
call_class_loads();call_category_loads();- 从这两处可以看出,先调用类的
load方法,再调用分类的load方法
load_method_t load_method = (load_method_t)cats[i].method;(*load_method)(cls, @selector(load));- 通过这两句代码可以看出,
load方法是直接找到load函数(方法)的指针,通过指针调用的load函数,不是通过OBJC的消息机制
+load方法会在runtime加载类、分类时调用,每个类、分类的+load,在程序运行过程中只调用一次
load调用顺序
void call_load_methods(void)
{
....................
void *pool = objc_autoreleasePoolPush();
do {
// 1.重复调用class +loads,直到没有其他类
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;
}
void prepare_load_methods(const headerType *mhdr)
{
.........
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 按照编译顺序,将类的数据加进去
schedule_class_load(remapClass(classlist[i]));
}
...........
}
/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
..........
// Ensure superclass-first ordering(递归调用,始终先添加父类方法,父类都放入后,开始放入子类对象)
schedule_class_load(cls->getSuperclass());
// 将cls添加到loadable_classes数组的最后面
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
schedule_class_load(cls->getSuperclass());通过这里可以看出是先调用父类的load方法,再调用子类的load方法
-
先调用类的
+load- 按照编译先后顺序调用(先编译,先调用)
- 调用子类的
+load之前会先调用父类的+load
-
再调用分类的
+load- 按照编译先后顺序调用(先编译,先调用)
子类不实现
load的,不会去重复调用父类的load方法,因为不是按照objc_msgSend方式寻址的,不会通过superClass指针向上寻找
+ initialize (初始化)
源码
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void initializeNonMetaClass(Class cls)
{
/****************优先调用父类的initialize方法**********************/
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->getSuperclass();
if (supercls && !supercls->isInitialized()) {
// 递归
initializeNonMetaClass(supercls);
}
@try
{
/***********************************************************************
...................调用initialize方法.....................
**********************************************************************/
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
objc_thread_self(), cls->nameForLogging());
}
}
}
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
initializeNonMetaClass(supercls);先调用父类的initialize方法,同时这里进行了判断
if (supercls && !supercls->isInitialized()) {如果父类已经调用过
initialize,不会重复调用如果两个类,是同一个父类,并且这两个类的都没有实现
initialize,父类的initialize会实现两遍(父类的initialize可能会调用多次),因为initialize是通过OBJC的消息机制调用的如果类A自己没有实现
initialize,但是父类实现了initialize,父类的initialize会调用两次,因为initialize是通过OBJC的消息机制调用的
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));说明initialize是走的OBJC的消息发送机制
+initialize方法会在类第一次接收到消息时调用
+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点
- 如果子类没有实现
+initialize,会调用父类的+initialize - 如果分类实现了
+initialize,就覆盖类本身的+initialize调用
+initialize和+load的区别
1.调用方式
- load是根据函数地址直接调用
- initialize是通过objc_msgSend调用
2.调用时刻
- load是runtime加载类、分类的时候调用(只会调用1次)
- initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
load、initialize的调用顺序?
1.load
1> 先调用类的load
a) 先编译的类,优先调用load
b) 调用子类的load之前,会先调用父类的load
2> 再调用分类的load
a) 先编译的分类,优先调用load
2.initialize
1> 先初始化父类
2> 再初始化子类(可能最终调用的是父类的initialize方法)
通常情况下两者都是只实现一次,不会重复实现。但是不排除重复实现的可能,譬如误操作直接调用这种,建议使用的时候加上
dispatch_once来使用
关联对象(给分类添加属性)
在类中声明一个属性,会做三件事情
- 生成一个成员变量
- 生成get和set方法的声明
- 实现get和set方法的实现
在分类中写一个属性
只会自动生成
get和set方法的声明,不会生成方法实现,不会生成成员变量
默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现
对比看的话类的结构中,有成员变量列表
ivars(只读),用来存储成员变量,通过关联对象添加的成员变量,并不是放到类的ivars中
-
类的结构:
-
分类的结构
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; // 属性
};
关联对象提供了以下API
1. 添加关联对象
void objc_setAssociatedObject(id object, const void * key,
id value, objc_AssociationPolicy policy)
2. 获得关联对象
id objc_getAssociatedObject(id object, const void * key)
3. 移除所有的关联对象
void objc_removeAssociatedObjects(id object)
const修饰的是全局变量,外部可以通过extern关键字进行访问,给全局变量加上static意味着只有当前文件内部可以访问
// .h
extern const char outKey(编译成功)
extern const char privateKey (编译报错)
// .m
const char outKey (外部可以通过extern访问)
static const char privateKey (因为有static,外部无法访问)
key的常见用法
- 使用变量地址值,赋值比较啰嗦,切占8个字节(指针变量)
static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
- 还是使用变量地址,但是只占一个字节(char变量)
static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)
- 使用属性名作为key(@"property"在常量区,内存地址一致)
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");
- 使用get方法的@selecor作为key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, _cmd) // _cmd == @selector(getter)
API
/**
* Sets an associated value for a given object using a given key and association policy.
*
* @param object 关联对象(一般是self)
* @param key 关联的关键词
* @param value 关联的数值,传nil移除关联对象
* @param policy 关联策略
*
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
/**
* Returns the value associated with a given object for a given key.
*
* @param object 关联对象
* @param key 关联的关键词
*
* @return The value associated with the key \e key for \e object.
*
* @see objc_setAssociatedObject
*/
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
实际使用
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
// 隐式参数
// _cmd == @selector(name)
return objc_getAssociatedObject(self, _cmd);
}
objc_AssociationPolicy (关联策略)
没有
weak效果,使用要注意,离开赋值范围要手动设置nil
关了对象实现weak效果
| objc_AssociationPolicy | 对应的修饰符 |
|---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | strong, nonatomic |
OBJC_ASSOCIATION_COPY_NONATOMIC | copy, nonatomic |
OBJC_ASSOCIATION_RETAIN | strong, atomic |
OBJC_ASSOCIATION_COPY | copy, atomic |
关联对象原理(底层实现)
全局有一个
AssociationsManager,里面保存一个成员变量
AssociationsHashMap *map{关联对象(object): ObjectAssociationMap}根据
关联对象,可以得到一个
AssociationMap {key:ObjcAssociation}对象,
ObjcAssociation有两个属性 {value,policy}
关联对象不会增加成员变量,不能通过person -> age 形式进行访问