iOS:Category浅析

说明 时间
首次发布 2019年04月22日
最近更新 2019年09月07日
一、分类的加载处理过程

结论:runtime加载某个类的所有分类数据,将所有category的方法、属性、协议合并到一个大数组里(后面参与编译的分类数据,会插入到数组的前面),最后将合并后的分类数据插入到类原来位置的前面

#import 

NS_ASSUME_NONNULL_BEGIN
//本类
@interface SuperMan : NSObject
@end

//分类
@interface SuperMan (Fly)

- (void)fly;

+ (void)eat;

@end
NS_ASSUME_NONNULL_END
========================

//.m
#import "SuperMan.h"

@implementation SuperMan

- (void)fly {
    NSLog(@"fly");
}

+ (void)eat {
    NSLog(@"eat");
}

@end
========================

@implementation SuperMan (Fly)

- (void)fly {
    NSLog(@"fly");
}

+ (void)eat {
    NSLog(@"eat");
}

@end

打印本类里的方法

#import "ViewController.h"

#import "SuperMan.h"
#import 

void getAllMethodsFor(Class cls) {
    
    unsigned int count;
    Method *methods = class_copyMethodList(cls, &count);
    NSMutableArray *list = [NSMutableArray array];
    for (unsigned int i = 0; i < count; i++) {
        Method method = methods[i];
        [list addObject:NSStringFromSelector(method_getName(method))];
    }
    
    //释放内存
    free(methods);
    
    NSString *methodName = [list componentsJoinedByString:@", "];
    NSLog(@"%@", methodName);
}

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //通过类对象,获取到成员方法
    getAllMethodsFor(SuperMan.class);
    //通过元类,获取到本类的类方法
    getAllMethodsFor(object_getClass(SuperMan.class));
}

@end

输出:

fly, fly
eat, eat

从中,我们可以看到里面有两个flyeat方法,并没有发生我们所认为的方法覆盖。结合上面我们所说的将分类数据插入到类原来位置的前面,所以当真正调用的时候,找到前面的方法之后,就不会再接着往后便利,于是就发生了“分类的方法覆盖了本来的实现”。


二、系统调用+load方法的顺序探究

代码结构:Cat继承自Animal,并且二者皆有一个分类,每个文件都有个一个+load方法。

//Animal
@interface Animal : NSObject
@end

#import "Animal.h"

@implementation Animal

+ (void)load {
    NSLog(@"====Animal");
}

@end
========================

//Cat
@interface Cat : Animal
@end

@implementation Cat

+ (void)load {
    NSLog(@"====Cat");
}

@end
========================

//Animal+Test
@interface Animal (Test)
@end

@implementation Animal (Test)

+ (void)load {
    NSLog(@"====Animal+Test");
}

@end
========================

//Cat+Test
@interface Cat (Test)
@end

@implementation Cat (Test)

+ (void)load {
    NSLog(@"====Cat+Test");
}

@end

结论1:无论怎么调整Animal、Cat、Animal+Test和Cat+Test的顺序,始终先打印Animal、Cat,即:先加载本类,再加载分类。Animal+Test和Cat+Test的顺序,则是编译顺序决定打印顺序,谁在前,就先调用谁的+load方法。

如果再增加一个继承自NSObject的Dog类,如下:

//Dog
@interface Dog : NSObject
@end

#import "Dog.h"

@implementation Dog

+ (void)load {
    NSLog(@"====Dog");
}

@end
========================

//Dog+Test
@interface Dog (Test)
@end

@implementation Dog (Test)

+ (void)load {
    NSLog(@"====Dog+Test");
}

@end

结论2:如果Cat在最前,则本类的调用顺序就是Animal、Cat、Dog;如果Dog在最前,则为Dog、Animal、Cat;如果Animal在最前,则Animal的打印也在最前,Dog和Cat的顺序则根据编译顺序排序。由此可见,有依赖关系的,则先调用父类的+load,没有依赖关系的,则按照编译顺序调用+load方法,分类的+load方法的调用顺序依然遵循编译顺序调用。


三、分类不能直接添加属性
static const char MZNameKey;
static const void *MZAddressKey = &MZAddressKey;

补充:

Selector,Method,IMP 的关系:
一个类(Class)持有一个分发表,在运行期分发消息,表中的每一个实体代表一个方法(Method),它的名字叫做选择子(SEL),对应着一种方法实现(IMP)。

以下是Method的底层,实际上是一个method_t结构体。

struct method_t {
    SEL name; //函数名
    const char *types; //编码(返回值类型、参数类型)
    IMP imp;//指向函数的指针(函数地址)
};

你可能感兴趣的:(iOS:Category浅析)