基础知识- runtime
Runtime的应用场景
1.按键防重复点击【交换方法】
UIControl控件在发生点击操作时候,最后都会回调到sendAction:to:forEvent: 利用runtime交互方法的功能,把该方法替换成我们自定义的方法,并添加一个间隔时间属性,这个时间默认为0,如果设置了时间,每次点击后都会去判断两次点击的时间间隔 如果时间间隔在设定的间隔时间范围内,可认定为重复点击,此时忽略此次点击
2.数组防越界/数组/字典崩溃/
objectAtIndex可能会导致越界的情况,查看报错日志信息会有某个方法越界的提示,利用runtime交换该方法,并添异常处理
3.uiview ,uicontrol添加block方法,快速添加点击事件【添加方法】
通过关联对象保存block,并在方法中添加addTarget事件,在事件回调通过,通过关联对象获取到之前保存的block,通过block把事件回调出去
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
NS_ASSUME_NONNULL_BEGIN
typedef void(^FKControlBlock)(UIControl* control);
@interface UIControl (WW)
- (void)fk_addAction:(FKControlBlock)block;
- (void)fk_addAction:(FKControlBlock)block forControlEvents:(UIControlEvents)controlEvents;
@end
NS_ASSUME_NONNULL_END
#import "UIControl+WW.h"
@implementation UIControl (WW)
- (void)fk_addAction:(FKControlBlock)block
{
objc_setAssociatedObject(self, @selector(fk_addAction:), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self addTarget:self action:@selector(action:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)fk_addAction:(FKControlBlock)block forControlEvents:(UIControlEvents)controlEvents
{
objc_setAssociatedObject(self, @selector(fk_addAction:), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self addTarget:self action:@selector(action:) forControlEvents:controlEvents];
}
- (void)action:(id)sender
{
FKControlBlock blockAction = (FKControlBlock)objc_getAssociatedObject(self, @selector(fk_addAction:));
if (blockAction)
{
blockAction(self);
}
}
@end
消息转发
void objc_msgSend(id self,SEL op,...)
消息传递实际上转换成了函数的调用 发生在编译器层面
[self class] ==> objc_msgSend(self,@select(class))
void objc_msgSendSuper(id self,SEL op,...)
[super class] ==> objc_msgSendSuper(super,@select(class))

消息传递的过程 我们在调用一个方法时,先去查找缓存,看看缓存中是否有对应方法选择器的实现,如果命中通过函数指针调用函数,就完成了一次消息传递
如果没有命中,会根据当前类的isa指针去查当前类对象的方法类表,看是否有同样名称的方法,如果找到的话,通过函数指针进行方法的调用,就结束了消息调用
如果当前类方法列表中没有对应方法,则逐级父类的方法列表中去查找,通过当前类对象的[super class]指针查找父类的方法列表,
runtime会为每个类(不是类的实例)添加一个方法列表,该列表采用hash表实现,方便使用时快捷查找
缓存查找是通过hash查找
当前类查找 已排序二分查找 未排序的遍历查找
父类逐级查找
3次机会
通过objc对象的isa指针找到对应的class,通过遍历class的method_list查找对应的方法
如果class中未找到则查询其父类 父类的父类 方法列表,如果还是没有找到则走方法转发流程
运行时决议是否有动态添加的方法 通过class_addMethod动态添加方法
-(bool)resolveClassMethod:(SEL)sel
-(bool)resolveInstanceMethod:(SEL)sel
#import "ViewController.h"
#import "objc/runtime.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(foo:)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(foo:)) {
class_addMethod([self class], sel, (IMP)fooMethod, "v@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void fooMethod(id obj, SEL _cmd){
NSLog(@"Doing foo");
}
快速转发流程 返回其他对象,让其他对象来实现该方法
-(id)forwardingTargetForSelector:(SEL)aSelector
#import "ViewController.h"
#import "objc/runtime.h"
@interface Person : NSObject
@end
@implementation Person
- (void)foo{
NSLog(@"Doing foo");
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(foo)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
return YES;// 返回YES进入下一步转发
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(foo)) {
return [Person new]; // 返回Person对象,让Person对象接受这个消息
}
return [super forwardingTargetForSelector:aSelector];
}
@end
标准转发流程 查找是否有其他类来实现该方法
调用对应的方法查询是否有某些方法的实现 当前类无法实现,查找是否有其他类可以实现对应方法
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
-(void)forwardInvocation:(NSInvocation *)anInvocation
如果最终仍然未找到则返回方法无法找到并报错
#import "ViewController.h"
#import "objc/runtime.h"
@interface Person : NSObject
@end
@implementation Person
- (void)foo{
NSLog(@"Doing foo");
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(foo)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
return YES;// 返回YES进入下一步转发
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
return nil;// 返回nil进入下一步转发
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
SEL sel = anInvocation.selector;
Person *p = [Person new];
if ([p respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p];
}else{
[self doesNotRecognizeSelector:sel];
}
}
@end
Invocation方法调用
// NSInvocation 调用
NSMethodSignature *sig = [[People class] instanceMethodSignatureForSelector:sel_registerName("helloWorld")];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
invocation.target = people;
invocation.selector = sel_registerName("helloWorld");
[invocation invoke];
load与initialize
load与initialize调用时机
- +load在main函数之前被Runtime调用,+initialize 方法是在类或它的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用。
load方法调用顺序
父类->主类->分类 - 主类的 +load 方法会在它的所有父类的 +load 方法之后执行。如果主类没有实现 +load 方法,当它被runtime加载时 是不会去调用父类的 +load 方法的。
- 分类的 +load 方法会在它的主类的 +load 方法之后执行,当一个类和它的分类都实现了 +load 方法时,两个方法都会被调用。当有多个分类时,根据编译顺序(Build Phases->Complie Sources中的顺序)依次执行。
- 在类的+load方法调用的时候,可以调用category中声明的方法么? 可以调用,因为附加category到类的工作会先于+load方法的执行
当父类和子类都实现load方法,则先执行父类的load,在执行子类的load,父类的load优先级要高一些,类的优先级又比分类的优先级要高
当子类未实现load方法,则不会调用父类的load方法
确保在load和initialize的调用只执行一次
- 由于initialize可能会调用多次,所以在这两个方法里面做的初始化操作需要保证只初始化一次,用dispatch_once来控制
分类和拓展的区别
分类:运行时决议,可以为现有类添加实例方法,类方法,属性(只会重写set get方法,不会添加成员变量,需要配好关联对象)
拓展:编译时决议的,可以属性,只能是私有属性

浙公网安备 33010602011771号