协议
为什么要使用协议?
因为Object-C是不支持多继承的,所以很多时候都是用Protocol(协议)来代替。Protocol(协议)只能定义公用的一套接口,但不能提供具体的实现方法。也就是说,它只告诉你要做什么,但具体怎么做,它不关心。
当一个类要使用某一个Protocol(协议)时,都必须要遵守协议。比如有些必要实现的方法,你没有去实现,那么编译器就会报警告,来提醒你没有遵守某某协议。
定义一套公用的接口(Public)
@required:必须实现的方法,默认在@protocol里的方法都要求实现。
@optional:可选实现的方法(可以全部都不实现)
代理
委托代理是指给一个对象提供一个机会,对另一个对象中的变化做出反应,或者响应另一个对象的行为。它的基本思想是两个对象协同解决问题。(Google说法)
我自己现在的理解就是你需要别的文件传一个参数(一个值,或者一个事件)而且为了代码的封装性等等很多东西(其实是我还没理解到就不好乱讲)你就需要做一个代理。(block也可以完成这些,区别和联系我会在最后列出)
下面就是讲一下代理的声明与实现方法。
举个例子,A页跳转到B页面返回时,A页面需要一个B面的参数·····
代码运行结果:
代码开始运行:
运行结果:
从上面的步骤可以看到,委托代理的使用,是从传值页面开始的。现在,来一个通俗的解释:
A(ViewController)页面是上一级页面,可以跳转到B(SecondViewController)页面,而传值方向是从B传递到A。代码中是B设置了委托,A实现了代理并实现委托方法。可以简单理解为,B想要将值传递给A,但是没有好的方法(不能直接传值),这个时候,B就想委托A自己取值,这个时候,B就设计了一套委托协议,A想要得到B的值,但是B不能直接传递给A,A就问啦:B啊,你怎么不给我传值啊,我很急啊。然后B就对A说,我这里没有通道,不能直接给你啊,我这有一套委托协议,你来遵守我的协议然后自己来取值吧。这样A就作为了B的代理方,通过实现代理方法得到了想要的值。
在使用委托代理传值的是,要传值的一方是委托方,要接收的一方是代理方。在委托方要声明协议,声明代理,声明协议方法;而在代理方要遵守协议,设置代理,实现协议方法。这样相互之间一一对应的关系就使得委托代理在定向传值的时候显得特别的重要和实用。
block:
同样有两个视图控制器 A 和 B,现在点击 A 上的按钮跳转到视图 B ,并在 B 中的textfield 输入字符串,点击 B 中的跳转按钮跳转回 A ,并将之前输入的字符串
显示在 A 中的 label 上。也就是说 A 视图中需要回调 B 视图中的数据。
基础用法我就不再赘述了(也是直接代码)
在这里,代码 1 用 typedef 定义了void(^) (NSString *text)的别名为 CallBackBlcok。这样我们就可以在代码 2 中,使用这个别名定义一个 Block 类型的变量callBackBlock。
在定义了callBackBlock之后,我们可以在 B 中的点击事件中添加callBackBlock的传参操作
这样我们就可以在想要获取数据回调的地方,也就 A 的视图中调用 block:
代码 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 接口, 反而容易出问题。
(这个我自己理解的比较基础,就把看到的一个感觉很贴地气的解释复制过来,希望对大家有帮助)