zoukankan      html  css  js  c++  java
  • 协议和代理 (protocol 和 delegate)

    协议(protocol)

    协议就是定义一个需要完成任务(函数)的公用接口,因为Objective - C语言没有多继承,所以很多时候都是用Protocol(协议)来代替。

    比如:你要写一个程序里面包括优秀学生,优秀三好学生,普通学生三个类。他们都应该继承学生类,但是优秀学生和优秀三好学生 都有一个相似的部分都是优秀学生,但是因为Objective - C 没有多继承,所以就会用到Protocol(协议)来定义一套公用的接口,代替多继承。当然协议还有另一种用法,接下来我们将会讲到

    定义一套公用的接口(Public)

    @required:必须实现的方法,默认在@protocol里的方法都要求实现。

    @optional:可选实现的方法(可以全部都不实现)

    比如常用的 UITableViewDelegate 就是一个协议,遵守这个协议必须实现UI TabViewDelegate的方法

    代理
    委托代理是指给一个对象提供一个机会,对另一个对象中的变化做出反应,或者响应另一个对象的行为。它的基本思想是两个对象协同解决问题。
    一般在代码开发过程中,View层一本不会直接操作函数,但会接受一些时间,比如点击事件,这种事件需要拿到controller里去处理,而完成这个让Controller响应View事件的操作就可以用代理来完成。

    一般代理和协议都是联合使用的:代理让‘别人去替你做事情 ’,‘事情’用协议声明

    接下来讲解一下代理的用法:

    1、先声明一个协议(事情)

    @protocol ProtocolDelegate <NSObject>
    
    // 必须实现的方法
    @required
    - (void)error;
    
    // 可选实现的方法
    @optional
    - (void)other;
    - (void)other2;
    - (void)other3;
    
    @end
    
    @interface ViewControllerB : UIViewController
    
    // 委托代理人,代理一般需使用弱引用(weak)
    @property (weak, nonatomic) id<ProtocolDelegate> delegate;
    
    @end

    2、接下来,作为当事人,在让别人去替你做事的时候,你需要告诉别人什么时候做:

    // 在ViewControllerB的.m文件里
    - (void)backAction:(id)sender
    {
    // 协议是否响应了error方法 (看看代理人是不是准备好要做事情了)
        if ([_delegate respondsToSelector:@selector(error)]) { 
            [_delegate error]; // 告诉别人 你可以做事情了
        }}
    
    @end

    3、然后代理人需要做什么准备呢?

    #import "ViewController.h"
    #import "ViewControllerB.h"
    
    @interface ViewController () <ProtocolDelegate> //  声明协议(如果你想替别人做事,需要先遵守这个合同)
    
    @end
    
    @implementation ViewController
    
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        ViewControllerB *vc = segue.destinationViewController;
        [vc setDelegate:self]; // 确定自己(ViewControllerB)的代理人(self / ViewController);
    }
    
    // 当自己自己(ViewControllerB)需要实现error方法时 调用此方法
    - (void)error
    {
    }
    @end

    二、block:

    block的代理的要完成目标相似,都是让别人去替完成任务。那就直接上代码

    //BViewController.h
    #import <UIKit/UIKit.h>
    
    typedef void(^CallBackBlcok) (NSString *text);//1
    
    @interface BViewController : UIViewController
    
    // 在自己页面声明一个block
    @property (nonatomic,copy) CallBackBlcok callBackBlock;//2
    @end

    在这里,代码 1 用 typedef 定义了void(^) (NSString *text)的别名为 CallBackBlcok。这样我们就可以在代码 2 中,使用这个别名定义一个 Block 类型的变量callBackBlock。

    在定义了callBackBlock之后,我们可以在 B 中的点击事件中添加callBackBlock的传参操作

    //BViewController.m
    
    // 然后在.m 决定什么时候替你完成‘事’
    - (IBAction)click:(id)sender {
        self.callBackBlock(_textField.text); //1
    }

    这样我们就可以在想要获取数据回调的地方,也就 A 的视图中调用 block:

    // AViewController.m
    - (IBAction)push:(id)sender {
        BViewController *bVC = [self.storyboard instantiateViewControllerWithIdentifier:@"BViewController"];
        
        bVC.callBackBlock = ^(NSString *text){   // 1
            
            NSLog(@"text is %@",text);
            
            self.label.text = text;
            
        };
        [self.navigationController pushViewController:bVC animated:YES];
    }

    代码 1 中,通过对回调将 B 中的数据传递到代码块中,并赋值给 A中的 label,实现了整个回调过程。

    上例是通过将 block 直接赋值给 block 属性,也可以通过方法参数的方式传递 block 块。

    不过上面的代码有问题:

    有人问了,兄弟,有错误的代码还粘?我要回答了,兄弟 我之前也是被这个代码骗惨了,拿出来就是让大家以你想让大家深刻记住block的缺点,就是block会循环引用。

    把代码中的self改成弱引用就可以了:

    __weak AViewController *weakSelf = self;
    bVC.callBackBlock = ^(NSString *text){
    
           NSLog(@"text is %@",text);
          // self.label.text = text;
          weakSelf.label.text = text;
    
    };

    因为上面的代码 self.label.text = text;,在 Block 中引用 self ,也就是 A ,而 A 创建并引用了 B ,而 B 引用callBackBlock,此时就形成了一个循环引用。改成弱引用就好了原因

    协议、代理与block基本就到这了

    三、区别

    很多同学可能还会迷糊代理与block,两者实现的功能差不多那么区别是什么呢?接下来我给把两者区别列出来一下:

    block 和 delegate 都可以通知外面。block 更轻型,使用更简单,能够直接访问上下文,这样类中不需要存储临时数据,使用 block 的代码通常会在同一个地方,这样读代码也连贯。delegate 更重一些,需要实现接口,它的方法分离开来,很多时候需要存储一些临时数据,另外相关的代码会被分离到各处,没有 block 好读。

    应该优先使用 block。而有两个情况可以考虑 delegate。

    1.有多个相关方法。假如每个方法都设置一个 block, 这样会更麻烦。而 delegate 让多个方法分成一组,只需要设置一次,就可以多次回调。当多于 3 个方法时就应该优先采用 delegate。

    比如一个网络类,假如只有成功和失败两种情况,每个方法可以设计成单独 block。但假如存在多个方法,比如有成功、失败、缓存、https 验证,网络进度等等,这种情况下,delegate 就要比 block 要好。

    在 swift 中,利用 enum, 多个方法也可以合并成一个 block 接口。swift 中的枚举根据情况不同,可以关联不同数据类型。而在 objc 就不建议这样做,objc 这种情况下,额外数据需要使用 NSObject 或者 字典进行强转,接口就不够安全。

    2.为了避免循环引用,也可以使用 delegate。使用 block 时稍微不注意就形成循环引用,导致对象释放不了。这种循环引用,一旦出现就比较难检查出来。而 delegate 的方法是分离开的,并不会引用上下文,因此会更安全些。

    假如写一个库供他人使用,不清楚使用者的水平如何。这时为防止误用,宁愿麻烦一些,笨一些,使用 delegate 来替代 block。

    将 block 简单分类,有三种情形。

    • 临时性的,只用在栈当中,不会存储起来。

    比如数组的 foreach 遍历,这个遍历用到的 block 是临时的,不会存储起来。

    • 需要存储起来,但只会调用一次,或者有一个完成时期。

    比如一个 UIView 的动画,动画完成之后,需要使用 block 通知外面,一旦调用 block 之后,这个 block 就可以删掉。

    • 需要存储起来,可能会调用多次。

    比如按钮的点击事件,假如采用 block 实现,这种 block 就需要长期存储,并且会调用多次。调用之后,block 也不可以删除,可能还有下一次按钮的点击。

    对于临时性的,只在栈中使用的 block, 没有循环引用问题,block 会自动释放。而只调用一次的 block,需要看内部的实现,正确的实现应该是 block 调用之后,马上赋值为空,这样 block 也会释放,同样不会循环引用。

    而多次调用时,block 需要长期存储,就很容易出现循环引用问题。

    Cocoa 中的 API 设计也是这样的,临时性的,只会调用一次的,采用 block。而多次调用的,并不会使用 block。比如按钮事件,就使用 target-action。有些库将按钮事件从 target-action 封装成 block 接口, 反而容易出问题。

  • 相关阅读:
    OnEraseBkgnd、OnPaint与画面重绘
    .编译ADO类DLL时报错的解决方案
    VC列表框样式
    Codeforces 131D. Subway 寻找环树的最短路径
    Codeforces 103B. Cthulhu 寻找奈亚子
    Codeforces 246D. Colorful Graph
    Codeforces 278C. Learning Languages 图的遍历
    Codeforces 217A. Ice Skating 搜索
    Codeforces 107A. Dorm Water Supply 搜图
    Codeforces 263 D. Cycle in Graph 环
  • 原文地址:https://www.cnblogs.com/xujinzhong/p/13948412.html
Copyright © 2011-2022 走看看