说明 | 时间 |
---|---|
首次发布 | 2019年04月22日 |
最近更新 | 2019年09月07日 |
一、分类的加载处理过程
结论:runtime加载某个类的所有分类数据,将所有category的方法、属性、协议合并到一个大数组里(后面参与编译的分类数据,会插入到数组的前面),最后将合并后的分类数据插入到类原来位置的前面。
#import <Foundation/Foundation.h>
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 <objc/runtime.h>
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
从中,我们可以看到里面有两个fly
和eat
方法,并没有发生我们所认为的方法覆盖。结合上面我们所说的将分类数据插入到类原来位置的前面,所以当真正调用的时候,找到前面的方法之后,就不会再接着往后便利,于是就发生了“分类的方法覆盖了本来的实现”。
二、系统调用+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;//指向函数的指针(函数地址)
};
网友评论