JHHK

欢迎来到我的个人网站
行者常至 为者常成

Category

目录

实现原理

Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息,在程序运行的时候,runtime将Category的数据,合并到类信息中(类对象、元类对象中)。

定义在objc-runtime-new.h中的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);
};

通过runtime加载某个类的所有Category数据,把所有Category的方法、属性、协议数据,合并到一个大数组中,后边参与编译的Category数据,会在数组的前面。将合并后的分类数据(方法、属性、协议),插入到类原来的数据前面。所以分类里边的方法会覆盖类原来的方法,分类里边后编译的文件里的方法会覆盖前边编译的方法

文件的编译顺序 img

在三个文件里同时实现下面的方法:

-(void)test{
    NSLog(@"%s",__func__);
}

查看调用日志

-[Person(eat) test]

+load方法

+load方法会在runtime加载类、分类时调用,每个类、分类的+load方法,在程序运行过程中只调用一次。
调用顺序:
1.先调用类的+load方法,类的+load方法的调用顺序按类的编译顺序调用,先编译先调用。但在调用之前会先调用父类的+load方法。
2.再调用分类的+load方法,分类的+load方法的调用顺序按分类的编译顺序调用,先编译先调用。

文件的编译顺序 img

在五个文件里同时实现下面的方法:

+(void)load{
    NSLog(@"%s",__func__);
}

查看日志调用

+[Person load]
+[Student load]
+[Person(run) load]
+[Person(eat) load]
+[Student(study) load]

+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用

+initialize方法

+initialize方法会在类第一次接收到消息时调用
调用顺序:
先调用父类的+initialize方法,再调用子类的+initialize方法。(先初始化父类,再初始化子类,每个类只初始化一次)。
分类的+initialize方法会覆盖类的+initialize方法。
后编译的分类的+initialize方法会覆盖先编译的分类的+initialize方法。

文件的编译顺序 img

在五个文件里同时实现下面的方法:

+(void)initialize{
    NSLog(@"%s",__func__);
}

书写下面代码

[Person alloc];
[Student alloc];

查看日志调用

+[Person(eat) initialize]
+[Student(study) initialize]

如果将Student及Student+study文件内的+initialize方法注释掉
查看日志调用

+[Person(eat) initialize]
+[Person(eat) initialize]

+initialize方法是在类第一次接收到消息时调用,调用机制是通过objc_msgSend实现的,所以子类没有实现+initialize方法时,会调用到父类的+initialize方法。

+initialize和+load的区别

1.+load只调用一次,+initialize至少调用一次。
2.+load是在类、分类加载时直接通过函数地址调用,+initialize方法是在第一次给类对象发消息时调用,通过objc_msgSend调用。

关联对象

一、默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。
如下Person+run分类

@interface Person (run)
@property (nonatomic, assign) int height;
@end

如下调用时,报错,找不到setHeight:方法,即没有自动生成setHeight:方法

Person * person = [[Person alloc] init];
person.height = 180;

*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[Person setHeight:]: unrecognized selector sent to instance 0x2815d9220’

那我们自己实现下看能不能行。

-(void)setHeight:(int)height{
    NSLog(@"%s",__func__);
}

查看日志调用,证明方法已经调用

 -[Person(run) setHeight:]

但当我们将成员变量赋值时 img

或者直接声明成员变量时 img

二、替代思路
Person+run.h文件

#import <Foundation/Foundation.h>
#import "Person.h"

NS_ASSUME_NONNULL_BEGIN

@interface Person (run)
@property (nonatomic, assign) int height;
@end

NS_ASSUME_NONNULL_END

Person+run.m

#import "Person+run.h"

#define LCKey [NSString stringWithFormat:@"%p", self]

NSMutableDictionary * heights_;
@implementation Person (run)

+(void)load{
    heights_ = [NSMutableDictionary new];
}

-(void)setHeight:(int)height{
    [heights_ setObject:@(height) forKey:LCKey];
}

-(int)height{
    return [[heights_ objectForKey:LCKey] intValue];
}

@end

调用

Person * person = [[Person alloc] init];
person.height = 180;
NSLog(@"person.height = %d",person.height);

Person * person1 = [[Person alloc] init];
person1.height = 181;
NSLog(@"person1.height = %d",person1.height);

调用日志

person.height = 180
person1.height = 181

三、关联对象
关联对象的实现方式
Person+run.m

#import <objc/runtime.h>
- (void)setWeight:(int)weight{
    objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (int)weight{
  // _cmd == @selector(weight)
  return [objc_getAssociatedObject(self, _cmd) intValue];
}
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

关联对象的原理

img


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.