基础知识- 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方法,不会添加成员变量,需要配好关联对象)
拓展:编译时决议的,可以属性,只能是私有属性

posted @ 2022-07-25 17:29  qqcc1388  阅读(3)  评论(0)    收藏  举报