Target-Action设计模式

本文深入探讨了Objective-C中的SEL(方法选择器)和IMP(方法实现)的概念及其内部工作机制,包括如何通过@selector获取方法编号,以及如何通过IMP执行方法。此外,还详细解释了performSelector的功能和应用场景。

一、SEL(@selector)

概念

SEL
方法名(编号)
IMP
一个函数指针,保存了方法的地址
@selector(方法名)
获取方法的编号,结果是SEL类型。他的行为基本可以等同于C语言中的函数指针
区别:

C语言中,可以直接把函数名赋值给一个函数指针,而且函数指针直接保存了函数地址
Objc中的类不能直接应用函数指针,只能使用@selector来获取,获取的是方法的编号

方法以@selector作为索引,@selector的数据类型是SEL,对应每个方法的编号,
当我们寻找方法的时候使用的是这个方法编号。类中存在一个methodLists专门用来存放方法实现IMP和SEL的映射。方法编号SEL通过Dispatch table表寻找到对应的IMP,IMP就是一个函数指针,然后执行这个方法。

struct objc_class {

    struct objc_class super_class;  /*父类*/

    const char *name;                 /*类名字*/

    long version;                   /*版本信息*/

    long info;                        /*类信息*/

    long instance_size;               /*实例大小*/

    struct objc_ivar_list *ivars;     /*实例参数链表*/

    struct objc_method_list **methodLists;  /*方法链表*/

    struct objc_cache *cache;               /*方法缓存*/

    struct objc_protocol_list *protocols;   /*协议链表*/

};

具体实现

metaClass和class的理解
1.metaClass其实就是一个class,只不过它的methodList是+类方法,而普通的class是-实例方法而已。
2.当获取类方法时,只是用的class_getInstanceMethod(class_getMeta(cls),sel)来获取的。
3.meta的rootClass等于本身也就是NSObject class的rootClass是nil。

在寻找IMP的地址时,runtime提供了两种方法:

IMP class_getMethodImplementation(Class cls, SEL name);
IMP method_getImplementation(Method m)

而根据官方描述,第一种方法可能会更快一些
(一)、对于第一种方法而言,类方法和实例方法实际上都是通过调用class_getMethodImplementation()来寻找IMP地址的,不同之处在于传入的第一个参数不同

/// 类方法(假设有一个类ClassA)
class_getMethodImplementation(objc_getMetaClass("ClassA"),@selector(methodName));

/// 实例方法
class_getMethodImplementation([ClassA class],@selector(methodName));

通过该传入的参数不同,找到不同的方法列表,方法列表中保存着下面方法的结构体,结构体中包含这方法的实现,selector本质就是方法的名称,通过该方法名称,即可在结构体中找到相应的实现。
(二)、对于第二种方法而言,传入的参数只有method,区分类方法和实例方法在于封装method的函数。

/// 类方法
Method class_getClassMethod(Class cls, SEL name)

/// 实例方法
Method class_getInstanceMethod(Class cls, SEL name)

/// 获取IMP地址
IMP method_getImplementation(Method m) 

测试代码如下:

- (instancetype)init {
    self = [super init];
    if (self) {
        [self getIMPFromSelector:@selector(test1)];
        [self getIMPFromSelector:@selector(test2)];
    }
    return self;
}

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

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

- (void)getIMPFromSelector:(SEL)aSelector {
    
    //第一种方式class_getMethodImplementation
    // 通过objc_getClass,获取实例方法编号为aSelector的指针
    IMP classInstanceIMP_1 = class_getMethodImplementation(objc_getClass("TestSelAndImp"), aSelector);
    NSLog(@"selectorName: %@, classInstanceIMP_1: %p",NSStringFromSelector(aSelector), classInstanceIMP_1);
    
    // 通过objc_getMetaClass,获取类方法编号为aSelector的指针
    IMP metaClaseIMP_1 = class_getMethodImplementation(objc_getMetaClass("TestSelAndImp"), aSelector);
    NSLog(@"selectorName: %@, metaClaseIMP_1: %p",NSStringFromSelector(aSelector),metaClaseIMP_1);

    //第二种方式 通过method_getImplementation方法,method_getImplementation(),如果找不到对应的实现,则返回0。

    //通过class_getInstanceMethod,获取实例方法编号为aSelector的指针
    Method classInstanceMethod_2 = class_getInstanceMethod(objc_getClass("TestSelAndImp"), aSelector);
    IMP classInstnceIMP_2 = method_getImplementation(classInstanceMethod_2);
    NSLog(@"selectorName: %@, classInstnceIMP_2: %p",NSStringFromSelector(aSelector),classInstnceIMP_2);
    
    //通过class_getClassMethod,获取类方法编号为aSelector的指针
    Method metaClassMethod_2 = class_getClassMethod(objc_getMetaClass("TestSelAndImp"), aSelector);
    IMP metaClassIMP_2 = method_getImplementation(metaClassMethod_2);
    NSLog(@"selectorName: %@, metaClassIMP_2: %p",NSStringFromSelector(aSelector),metaClassIMP_2);
    
}

在这里插入图片描述不难看出:
第一种方式class_getMethodImplementation:如果实例方法和类方法都实现,则可以成功的找到类方法的实现,如果未实现实例方法和类方法,返回的地址都是相同的,但是每次运行该程序时返回的地址并不相同。
第二种方式:method_getImplementation:如果实例方法和类方法都实现,则可以成功的找到类方法的实现,如果未实现实例方法和类方法,则返回空。

二、performSelector的原理及应用场景分析

2.1.performSelector和直接调用方法的区别:
performSelector: withObject:是在iOS中的一种方法调用方式。他可以向一个对象传递任何消息,而不需要在编译的时候声明这些方法。所以这也是runtime的一种应用方式。所以performSelector和直接调用方法的区别就在与runtime。
直接调用编译是会自动校验。如果方法不存在,那么直接调用 在编译时候就能够发现,编译器会直接报错。
使用performSelector的话一定是在运行时候才能发现,如果此方法不存在就会崩溃。所以如果使用performSelector的话他就会有个最佳伴侣- (BOOL)respondsToSelector:(SEL)aSelector;来在运行时判断对象是否响应此方法。
2.2延迟执行
performSelector:withObject:afterDelay:其实就是在内部创建了一个NSTimer,然后会添加到当前线程的Runloop中。所以当该方法添加到子线程中时,需要格外的注意两个地方:
1.在子线程中直接执行会不会调用test方法,因为子线程中的runloop默认是没有启动的状态。使用run方法开启当前线程的runloop,但是一定要注意run方法和执行该延迟方法的顺序,在子线程中两者的顺序必须是先执行performSelector延迟方法之后再执行run方法。因为run方法只是尝试想要开启当前线程中的runloop,但是如果该线程中并没有任何事件(source、timer、observer)的话,并不会成功的开启。

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
         [self performSelector:@selector(test) withObject:nil afterDelay:2];
        [[NSRunLoop currentRunLoop] run];
});

2.test方法中执行的线程:

 [self performSelector:@selector(test) withObject:nil afterDelay:2];

对于该performSelector延迟方法而言,如果在主线程中调用,那么test方法也是在主线程中执行;如果是在子线程中调用,那么test也会在该子线程中执行。
注意:performSelector:withObject:只是一个单纯的消息发送,和时间没有一点关系。所以不需要添加到子线程的Runloop中也能执行。
2.3异步执行
如何在不使用NSThread、GCD和NSOperation的情况下,实现异步线程?
1.performSelectorInBackground 后台执行

 [self performSelectorInBackground:@selector(test) withObject:nil];

2.performSelector:onThread:在指定线程执行

[self performSelector:@selector(test) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES];

2.4performSelector取消延迟
此需求我们可以通过cancelPreviousPerformRequestsWithTarget来进行实现。
2.5Object传参
方法中一个重要参数:anArgument,发现如果参数不为空,那取消时的参数也要一致,否则不能取消成功。
2.5.1.参数包装成字典
2.5.2自己实现对应方法 使用NSMethodSignure,NSInvoation

Target-Action设计模式
view层与control层之间的交互主要用到代理模式和target-action模式

具体如下代码:

@interface TouchView : UIView

//模拟的Button的写法
//实现方法的独享
@property (nonatomic,assign) id target;
//实现的方法
@property (nonatomic,assign) SEL action;

@end
#import "TouchView.h"

@implementation TouchView

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    //self.target 实现了self.action的方法
    //withObject:一般用来实现多线程之间的通信
    [self.target performSelector:self.action withObject:nil];
    
}



@end
- (void)viewDidLoad {
    [super viewDidLoad];
    /**
     面向对象编程的核心思想:高内聚,低耦合
     target/action:可以用来解耦
     */
    TouchView *touchView1 = [[TouchView alloc]initWithFrame:CGRectMake(100, 100, 175, 100)];
    touchView1.backgroundColor = [UIColor magentaColor];
    //对target赋值
    touchView1.target = self ;
    //指定要实现的方法,对action赋值
    touchView1.action = @selector(touchAction1:);
    touchView1.tag = 111 ;
    [self.view addSubview:touchView1];
    
}


-(void)touchAction1:(TouchView *)touchView {
    TouchView *tempTouchView = (TouchView *)[self.view viewWithTag:111];
    tempTouchView.backgroundColor = [UIColor colorWithRed:arc4random()%256/255.0 green:arc4random()%256/255.0 blue:arc4random()%256/255.0 alpha:arc4random()%256/255.0];
}
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值