聊聊 iOS 开发中的协议
前言
何为协议,简单来说在OC中我们使用关键字@protocol可以声明一个协议,并在协议中添加多个属性、方法供于遵循者实现,从某个角度上来说,这是一种不同于category机制的category。在日常开发中,协议可谓无处不在,最为核心的UITableView通过协议来获取数据、完成事件处理等。下面就是一个最粗浅的协议
@protocol CustomProtocol
- (void)doSomething;
@end
对于协议的理解,很多的开发者依旧保留在委托-代理等于协议等认知上。然而前者依赖于后者的实现,而后者即便不通过前者也能完成抽象解耦的工作。在继续谈协议可以完成的工作之前,有必要来理解一下何为协议:
协议指定了一套行为规范,遵循协议的类必须实现对应的行为
协议应用
代理回调
开发中我们几乎都会写的代码一定是UITableView系列的代理和数据源方法。毫无疑问,苹果提供的这个视图是如此的优雅而强大,即便在现在这个数据源方法因代码过多被疯狂吐槽的年代,你依然无法想到其他实现UITableView的更佳实践,这个控件充分向我们展示了委托-代理的强大。
协议最简单直观的应用是委托-代理设计模式,在封装自定义控件的时候,我喜欢使用自定义的协议来完成用户点击等业务处理。个人认为,如果你想要了解代理这一模式,起码要自定义过自己的代理协议:
@protocol SegmentControlDelegate
@optional
- (void)segmentControl: (SegmentControl *)segmentControl didSelectItemAtIndex: (NSUInteger)index;
@end
@interface SegmentControl: UIView
@property (nonatomic, weak) id delegate;
@end
@implementation SegmentControl
- (voice)clickItem: (UIButton *)item
{
if ([_delegate respondsToSelector: @selector(segmentControl:didSelectItemAtInex:)]) {
[_delegate segmentControl: self didSelectItemAtIndex: item.tag];
}
}
@end
上面是我曾经自定义过的分段控制器的代理伪实现代码,对于项目开发而言,代理这种回调机制的好处包括不仅于接口目的性强、易于追溯调试等。
什么时候用代理
这里不免就要提到另一个跟Delegate同样实用受欢迎的机制Block。比如常用的分享功能通常使用block进行结果回调:
typedef NS_ENUM(NSInteger, ShareResult) {
ShareResultSuccess,
ShareResultFailed,
ShareResultCancel
};
- (void)share {
[shareManager shareWithResult: ^(ShareResult result) {
switch (result) {
case ShareResultSuccess:
//....
case ShareResultFailed:
//....
case ShareResultCancel:
//....
}
}];
}
这样的代码看着很紧凑简洁,如果用Delegate来完成结果回调又是另一种感觉了:
- (void)share
{
ShareManager * shareManager = [[ShareManager alloc] initWithUrl: @"http://sindrilin.com" title: @"" content: @""];
shareManager.delegate = self;
[shareManager share];
}
- (void)shareManager: (ShareManager *)shareManager didCompleteShare: (ShareResult)result
{
switch (result) {
case ShareResultSuccess:
//....
case ShareResultFailed:
//....
case ShareResultCancel:
//....
}
}
同样的代码在Delegate看着并不nice。这是什么原因呢?个人认为原因如下:
由于代理没有Block的捕获上下文特性,需要传入调用方参数。但在方法中,ShareManager并没有任何作用
相较于block,代理将分享步骤和结果处理分到两个地方,逻辑不够紧凑
同样的,假如上面的分享换成网络请求,并且要求在请求中同步下载进度,单纯的使用Block可能就会变成这样:
- (void)requestData {
[manager requestWithProgress: ^(CGFloat progress) {
NSLog(@"download progress: %g", progress);
self.progressView.progress = progress;
} complete: ^(id receiveData, NSError * error) {
if (error) {
[self showErrorWithMsg: error.description];
} else {
// handle receive data
}
}];
}
这个时候的Block就会有些杂乱,看着头疼。通过这种对比,我们不难看出如果你需要执行某个任务,并且在任务完成的时候执行额外的操作时,Block的使用效果优于代理。而如果某个事件是存在多种状态,在每个状态发生或者改变时需要回调的,定义一个协议来回调是更好的选择。
数据源
虽然苹果将代理分为了数据源和代理两种类型,但是本质上都归属于委托-代理机制。苹果没有介绍过两者应当怎么划分,单从字面上的意思来划分这两者大概是这样的:
数据源为控件提供了数据展示的接口
代理为控件提供了事件响应的接口
这里要说的大概就是假如我们自定义了控件的数据源,那么应该什么时候调用数据源?按照UITableView的使用来说,当存在下面几种情况的时候,数据源方法是不调用的:
UITableView未添加到视图上
UITableView的宽高其中一个值为0
UITableView没设置代理
首先是前两个问题,作为所有视图父类的UIView中开放了一个方法didMoveToSuperview,这个方法会在当前视图被addSubview:之后回调。因此我们需要重写这个方法并且从数据源获取数据:
@protocol CustomViewDataSource
- (CustomViewCell *)customView: (CustomView *)customView cellForRowAtIndexPath: (IndexPath *)indexPath;
@end
@interface CustomView
@property (nonatomic, weak) id dataSource;
@end
@implementation CustomView
- (void)didMoveToSuperview
{
[super didMoveToSuperview];
if (!_delegate) { return; }
if ( CGRectGetWidth(self.frame) == 0 || CGRectGetHeight(self.frame) == 0 ) { return; }
for (IndexPath * index in _visableIndexPaths) {
id cell = [_dataSource customView: self cellForRowAtIndexPath: index];
// configure cell
[self addSubview: cell];
}
}
@end
与didMoveToSuperview类似的还有didMoveToWindow,这个方法会在前一个方法调用的前后分别调用一次,适当的重写这两个方法可以尽量让一些数据源方法调用延后,从而减少了同一时间大量方法调用降低帧数的可能性。
同样是委托-代理机制,数据源的使用要比单纯的代理考虑的因素多了许多。因此,数据源的使用算是协议应用的进一步。
协议抽象
在这里我们要聊聊面向对象编程,几乎有经验的开发者都能随口说出面向对象编程的特性:多态、抽象、封装、这些特性大大的提高了猿们的生产力,但这种便利并不是没有代价的,在Casa大神跳出面向思想系列就提出了继承的缺陷。
然而,这和本文有什么关系?
在iPad开发中有一个特殊的控件UISplitViewController,它将屏幕分割成大小两部分并同时显示两个控制器视图。其中左侧作为管理视图,右侧为详细信息视图。如果在右侧详情视图存在多个的情况下,左侧点击发生事件时,可能需要一堆的配置创建控制器的代码:
#import "AppDelegate.h"
#import "AddContactViewController.h"
#import "ContactDetailViewController.h"
@interface MasterViewController ()
@property (nonatomic, weak) UISplitViewController * splitViewController;
@property (nonatomic, strong) NSArray * contacts;
@end
@implementation MasterViewController
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate * appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
self.splitViewController = appDelegate.splitViewController;
}
- (void)contactDetail: (NSInteger)index
{
ContactDetailViewController * contactDetailVC = [[ContactDetailViewController alloc] init];
contactDetailVC.contact = _contacts[index];
[_splitViewController showDetailViewController: contactDetailVC sender: self];
}
- (void)addContact
{
AddContactViewController * addContactVC = [[AddContactViewController alloc] initWithNibName: @"AddContactViewController" bundle: nil];
[_splitViewController showDetailViewController: addContactVC sender: self];
}
@end
实际上需求当中右侧包含的界面包括何止十来个,还不包括复杂的nib控制器,这样就导致了左侧视图MasterViewController非常的乱。一方面,虽然只是弱指针引用,但是MasterViewController依旧需要获取所在的splitViewController。另一方面,太多的控制器创建配置代码雷同而多余。通过使用协议让这些坏毛病变得不那么坏。对于右侧的显示控制器,定义一个用于初始化的类构造方法,将右侧控制器的创建统一化;以及一个配置数据的方法:
@protocol DetailViewControllerProtocol
+ (instancetype)detailViewController;
- (void)configurateWithData: (id)data;
@end
另一方面,为左侧的控制器定义一个回调代理,传入将要显示的右侧控制器类名以及配置数据,让splitViewController自己来完成界面显示:
/// MasterViewController.h
@protocol MasterViewControllerDelegate
- (void)showDetailWithClass: (Class)aClass data: (id)data;
@end
@interface MasterViewController: NSObject
@property (nonatomic, weak) id delegate;
@end
/// MasterViewController.m
@implementation MasterViewController- (void)contactDetail: (NSInteger)index
- (void)contactDetail: (NSInteger)index
{
[self showDetail: NSClassFromString(@"ContactDetailViewController") data: _contacts[index]];
}
- (void)addContact
{
[self showDetail: NSClassFromString(@"AddContactViewController") data: nil];
}
- (void)showDetail: (Class)aClass data: (id)data
{
if ([_delegate respondsToSelector: @selector(showDetailWithClass:data:)]) {
[_delegate showDetailWithClass: aClass data: data];
}
}
@end
改动之后MasterViewController只保留了数据以及右侧控制器的名称,其余的工作交给代理人也就是UISplitViewController来完成:
#import "MasterViewController.h"
#import "DetailViewControllerProtocol.h"
@interface SplitViewController ()
@end
@implementation SplitViewController
- (void)viewDidLoad
{
[super viewDidLoad];
for (UIViewController * viewController in self.viewControllers) {
if ([viewController isKindOfClass: [MasterViewController class]]) {
MasterViewController * masterVC = (MasterViewController *)viewController;
masterVC.delegate = self;
break;
}
}
}
- (void)showDetailWithClass: (Class)aClass data: (id)data
{
UIViewController * detailVC = [aClass detailViewController];
[detailVC configurateWithData: data];
[self showDetailViewController: detailVC sender: self];
}
@end
最后就是右侧不同的控制器实现协议方法,完成控制器的创建和配置:
@implementation ContactDetailViewController
+ (instancetype)detailViewController
{
return [[[self class] alloc] init];
}
- (void)configurateWithData: (Contact *)data
{
self.phone = data.phone;
self.fullName = data.fullName;
}
@end
@implementation AddContactViewController
+ (instancetype)detailViewController
{
return [[[self class] alloc] initWithNibName: NSStringFromClass([self class) bundle: nil];
}
- (void)configurateWithData: (id)data { }
/// More. Load from storyboard etc
@end
实际上上面的例子是通过协议将不同控制器统一的行为(创建、配置)抽象成协议,从而减少了重复代码的使用。这种抽象统一的好处在于耦合度低,任何控制器只要实现了协议就能很好的在这个splitViewController上使用展示。
结束语
曾几何时,在懵懂中自学iOS时,总是不能理解协议的用处。甚至在接触了Block的时候,觉得这tm的太好用了,代理有个毛卵用。然后一直滥用Block直到发现并没有那么的神,往往还会带来隐晦的bug,于是才重新捡起了Delegate。从那之后逐渐的开始尝试包括通知、代理、Block多种回调方式的自定义控件,因此,本文严格来说算是纪录曾经的一次成长。
聊聊 iOS 开发中的协议的更多相关文章
- 聊聊iOS开发中耳机的那点事(监听耳机拔插、耳机线控)-b
如果说一个项目出现的最重大的事故,那无疑就是开发人员使用了不可控的元素. 前言 iOS开发当中有关于视音频播放的开发不在少数,用户时常会使用到一种输出设备,那就是"耳机",这一篇博 ...
- 总结iOS开发中的断点续传那些事儿
前言 断点续传概述 断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始.当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很 ...
- ios开发中的小技巧
在这里总结一些iOS开发中的小技巧,能大大方便我们的开发,持续更新. UITableView的Group样式下顶部空白处理 //分组列表头部空白处理 UIView *view = [[UIViewal ...
- iOS开发中设置UITextField的占位文字的颜色,和光标的颜色
在iOS开发中,对于很多初学者而言,很有可能碰到需要修改UITextField的占位文字的颜色,以及当UITextField成为第一响应者后光标的颜色,那么下面小编就介绍一下修改占位文字和光标的颜色. ...
- iOS开发中的4种数据持久化方式【二、数据库 SQLite3、Core Data 的运用】
在上文,我们介绍了ios开发中的其中2种数据持久化方式:属性列表.归档解档.本节将继续介绍另外2种iOS持久化数据的方法:数据库 SQLite3.Core Data 的运 ...
- iOS开发中的4种数据持久化方式【一、属性列表与归档解档】
iOS中的永久存储,也就是在关机重新启动设备,或者关闭应用时,不会丢失数据.在实际开发应用时,往往需要持久存储数据的,这样用户才能在对应用进行操作后,再次启动能看到自己更改的结果与痕迹.ios开发中, ...
- 简述 Ruby 与 DSL 在 iOS 开发中的运用
阅读本文不需要预先掌握 Ruby 与 DSL 相关的知识 何为 DSL DSL(Domain Specific Language) 翻译成中文就是:"领域特定语言".首先,从定义就 ...
- iOS开发 中的代理实现
iOS开发 中的代理实现 关于今天为什么要发这篇文字的原因:今天在和同事聊天的时候他跟我说项目中给他的block有时候不太能看的懂,让我尽量用代理写,好吧心累了,那就先从写个代理demo,防止以后他看 ...
- IOS开发中单例模式使用详解
第一.基本概念 单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例类的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问. 第二.在IOS中使用单例模式的情 ...
随机推荐
- 从ramdisk根文件系统启动Linux成功
这几天参考国嵌的实验手册和网上的资料完成了u-boot定制.内核定制.ramdisk根文件系统的制作,并成功.趁热打铁,总结一下.本文引用了很多网络上的文章,就不一一注明了.感谢各大侠的帮助,如有雷同 ...
- HDU 5617 Jam's maze 巧妙DP
题意:给你一个字符矩阵,从(1,1)到(n,n)有很多种走法,每一种走法形成一个字符串,问有多少种走法形成的字符串是回文的 分析:(粘贴BC题解) 的是回文串,有人会想到后缀数组自动机马拉车什么的,其 ...
- 解决js获取数据跨域问题,jsonP
网上说了一些jsonp的示例,感觉都没用,最后研究了一下,调用腾讯的一个api.最后要加output=jsonp&callback=?这个,比较适用. var url = "http ...
- vector(相对线程安全) arryList(线程不安全)
1.什么是线程安全? 如果说某个集合是线程安全的,那么我们就不用考虑并发访问这个集合?(需要定义自己百度,但是很难懂) 2.深入jvm中的线程安全的级别. a不变模式(String等基本类型) b.绝 ...
- JSF session的用法
http://blog.csdn.net/finelife/article/details/1608632 1.写入sessionObject sessionName = "name&quo ...
- debian 显示器使用自定义分辨率
比如你要使用 1440x900 的自定义分辨率,先利用 cvt 计算一个新的 modeline $ cvt 1440 900 输出为: # 1440x900 59.89 Hz (CVT 1.30MA ...
- rsync 的安装
Server setup 0)yum -y install xinetd vi /etc/xinetd.d/rsync and ensure following: disable = n ...
- POJ2778&HDU2243&POJ1625(AC自动机+矩阵/DP)
POJ2778 题意:只有四种字符的字符串(A, C, T and G),有M中字符串不能出现,为长度为n的字符串可以有多少种. 题解:在字符串上有L中状态,所以就有L*A(字符个数)中状态转移.这里 ...
- Android实例-MotionSensor加速度(XE8+小米2)
结果: 1. 实例代码: unit Unit1; interface uses System.SysUtils, System.Types, System.UITypes, System.Classe ...
- Modbus调试利器 Modbus Poll
Modbus Poll是一个非常不错的工具,支持TCP/串口 下载地址:http://pan.baidu.com/share/link?shareid=2880213929&uk=248325 ...