㉓ 通过委托与数据源协议进行对象间通信

代理模式/委托模式

对象之间经常需要相互通信, 而通信方式有很多种。 Objective-C开发者广泛使用一种名叫委托模式(Delegate pattern)的编程设计模式来实现对象间的通信。
此模式可将数据与业务逻辑解耦。委托协议名通常是在相关类名后面加上 Delegate 一词,整个类名采用驼峰法来写。这个属性需定义成weak,而非 strong, 因为两者之间必须为非拥有关系(nonowning relationship)。本类中存放委托对象的这个属性要定义为weak, 在相关对象销毁时会自动清空。

@protocol SubDogDelegate<NSObject>
@optional
- (void)makeNoise;
@end@interface SubDog : SuperDog
@property (nonatomic, weak) id<SubDogDelegate> delegate;
@end

如果要在委托对象上调用可选方法, 那么必须提前使用类型信息查询方法(参见第14条)判断这个委托对象能否响应相关选择子, 以及委托是否遵循了当前的委托方法。所以应该这样写:

    if ([_delegate conformsToProtocol:@protocol(SubDogDelegate)] && [_delegate respondsToSelector:@selector(makeNoise)]) {[_delegate makeNoise];}

也可以用协议定义一套接口,令某类经由该接口获取其所需的数据。委托模式的这用法旨在向类提供数据,故而又称数据源模式(Data Source pattern)。在此模式中,信息从数据源(Data Source)流向类(Class);而在常规的委托模式中,信息则从类流向受委托者(Delegate)。

将数据源协议与委托协议分离, 能使接口更加清晰,因为这两部分的逻辑代码也分开了。另外,数据源受委托者可以是两个不同的对象。然而一般情况下, 都用同一个对象来扮演这两种角色。

效率优化

虽然上面方法中的判断很简单, 但在某些情况下可能会多次调用, 如果每次都检查委托对象是否能响应此选择子, 那就显得多余了。鉴于此, 我们可以把委托对象能否响应某个协议方法这一信息缓存起来, 以优化程序效率。

将方法响应能力缓存起来的最佳途径是使用位段(bitfield)数据类型, 而新增的这个实例变量是个C语言结构体, 其中含有三个位段, 每个位段都与 delegate所遵从的协议中某个可选方法相对应。在下面例子中, 可以像下面这样查询并设置结构体中的位段:

@protocol SubDogDelegate<NSObject>
@optional
- (void)didReceiveData;
- (void)didFilwithError;
- (void)didUpdateProgress;
@end@interface SubDog : SuperDog
@property (nonatomic, weak) id<SubDogDelegate> delegate;
@end
@interface SubDog () {struct {unsigned int didReceiveData : 1;unsigned int didFilwithError : 1;unsigned int didUpdateProgress : 1;} _delegateFlags;
}@end@implementation SubDog- (void)setDelegate:(id<SubDogDelegate>)delegate {_delegate = delegate;// 设置delegate时, 设置不同位段的flag缓存_delegateFlags.didReceiveData = [_delegate conformsToProtocol:@protocol(SubDogDelegate)] && [_delegate respondsToSelector:@selector(didReceiveData)];_delegateFlags.didFilwithError = [_delegate conformsToProtocol:@protocol(SubDogDelegate)] && [_delegate respondsToSelector:@selector(didFilwithError)];_delegateFlags.didUpdateProgress = [_delegate conformsToProtocol:@protocol(SubDogDelegate)] && [_delegate respondsToSelector:@selector(didUpdateProgress)];
}- (void)requestResultMethod {// 不同的代理方法, 使用不同位段的flag缓存if (_delegateFlags.didReceiveData) {[_delegate didReceiveData];} else if (_delegateFlags.didFilwithError) {[_delegate didFilwithError];} else if (_delegateFlags.didUpdateProgress) {[_delegate didUpdateProgress];}
}@end

在相关方法要调用很多次时, 值得进行这种优化。而是否需要优化, 则应依照具体代码来定。这就需要分析代码性能, 并找出瓶颈, 若发现执行速度需要改进, 则可使用此技巧。如果要频繁通过数据源协议从数据源中获取多份相互独立的数据, 那么这项优化技术极有可能会提高程序效率。

总结

1.委托模式为对象提供了一套接口, 使其可由此将相关事件告知其他对象。
2.将委托对象应该支持的接口定义成协议, 在协议中把可能需要处理的事件定义成方法。当某对象需要从另外一个对象中获取数据时, 可以使用委托模式。这种情境下, 该模式亦称数据源协议(data source protocal)。
3.若有必要, 可实现含有位段的结构体, 将委托对象是否能响应相关协议方法这一信息缓存至其中。

㉔ 将类的实现代码分散到便于管理的数个分类之中

在实现某些类时, 假如类中的方法代码非常多, 那么该类的实现文件就十分臃肿。如果还向类中继续添加方法的话, 那么源代码文件就会越来越大, 变得难于管理。在此情况下,可以通过 Objective-C的分类机制, 把类代码按逻辑划入几个分区中, 把类分成几个不同的部分, 这对开发与调试都有好处。如下:

@interface Person : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,readonly) NSArray *friends;
@property (nonatomic,assign) int age;
@end@interface Person (FriendShip)
- (void)addFriend:(Person *)person;
- (void)removeFriend:(Person *)person;
@end@interface Person (Work)
- (void)performDaysWork;
- (void)takeVacationFromWork;
@end@interface Person (Play)
- (void)playPingPong;
- (void)playFootball;
- (void)sing;
- (void)run;
@end

使用分类机制之后, 依然可以把整个类都定义在一个接口文件中, 并将其代码写在一个实现文件里。可是, 随着分类数量增加, 当前这份实现文件很快就膨胀得无法管理了。此时可以把每个分类提取到各自的文件中去。以 Person为例, 可以按照其分类将代码拆分成下列几个文件:

1.Person+Friendship(h/m)
2.Person+Work(h/m)
3.Person+Play(h/m)

使用分类机制之后, 如果想用分类中的方法, 那么要记得在引入Person.h时一并引入分类的头文件。虽然稍微有点麻烦, 不过分类仍然是一种管理代码的好办法。而且, 这样做还有一个好处, 就是便于调试。根据回溯信息中的分类名称, 很容易就能精确定位到类中的方法所属的功能区。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ttKDHgKg-1574237430288)(https://img-blog.csdn.net/20180207110456491?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2FuZ3lhbmNoYW5nMjE=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)]

这对于某些私有方法来说更是极为有用, 可以创建名为 Private 的分类, 把这种方法全都放在里面。这个分类里的方法一般只会在类或框架内部使用, 而无须对外公布。在编写准备分享给其他开发者使用的程序库时, 可以考虑创建 Private分类。可以非常灵活的选择随不随程序库一并公开。

总结

1.使用分类机制把类的实现代码划分成易于管理的小块, 且易于调试。
2.将应该视为私有方法归入名叫 Private的分类中,以隐藏实现细节。

㉕ 总是为第三方类的分类名称加前缀

分类机制通常用于向无源码的既有类中新增功能。将分类方法加入类中这一操作是在运行期系统加载分类时完成的。如果类中本来就有此方法,而分类又实现了一次,那么分类中的方法会覆盖原来那一份实现代码。多次覆盖的结果以最后一个分类为准。

以命名空间来区别各个分类的名称与其中所定义的方法。给相关名称都加上某个共用的前缀。也与给分类所加的前缀, 当然这与给类名加前缀(参见第15条)时所应考虑的因素相似。

@interface Nsstring (ABC_HTTP)// Encode a string with URL encoding
-(Nsstring*)abc_urlEncodedstring:// Decode a URL encoded string
-(Nsstring*)abc_urlDecodedstring;@end

这样做也能避免类的开发者以后在更新该类时所添加的方法与你在分类中添加的方法重名。否则, 很可能当你所写的代码所覆盖系统或者其他三方库的原犯法后, 则会令对象内的数据互不一致,从而造成难于查找的bug。

总结

1.向第三方类中添加分类时,总应给其名称加上你专用的前缀
2.向第三方类中添加分类时,总应给其中的方法名加上你专用的前缀。

㉖ 勿在分类中声明属性

属性是封装数据的方式(参见第6条)。尽管从技术上说,分类里也可以声明属性,但这种做法还是要尽量避免。原因在于, 除Extension扩展之外的其他分类无法把实现属性所需的实例变量合成出来。例如, 你在分类中添加一个name 的属性时, 编译器应该会提示你:

Property ‘name’ requires method ‘name’ to be defined - use @dynamic or provide a method implementation in this category

意思是说此分类无法合成与 name属性相关的实例变量, 所以开发者需要在分类中为该属性实现存取方法。当然你可以把存取方法声明为@dynamic, 等到运行期再提供, 使用消息转发机制(参见第12条)在运行期拦截方法调用, 并提供其实现。但是一般我们通过关联对象(参见第10条)就能解决这个问题:

// .h
@property (nonamic, assign) BOOL canShowToast;// .m
- (BOOL)canShowToast {BOOL canShowToast = [objc_getAssociatedObject(self, kSomeKey) boolValue];return canShowToast;
}- (void)setCanShowToast:(BOOL)canShowToast {objc_setAssociatedObject(self, @selector(canShowToast), kSomeKey, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

这样做可行,但不太理想。要把相似的代码写很多遍, 而且在内存管理问题上容易出错, 因为在为属性实现存取方法时, 经常会忘记遵从其内存管理语义。比方说, 你可能通过属性特质(attribute)修改了某个属性的内存管理语义。而此时还要记得, 在设置方法中也得修改设置关联对象时所用的内存管理语义才行。

所以, 正确做法是把所有属性都定义在主接口里, 而且这样要比定义在分类里清晰得多。因为对于分类机制, 其目的在于扩展类的功能, 而非封装数据。

总结

1.把封装数据所用的全部属性都定义在主接口里。
2.在Extension扩展之外的其他分类中, 可以定义存取方法, 但尽量不要定义属性。

㉗ 使用“Extension扩展”隐藏实现细节

Objective-C动态消息系统(参见第11条)的工作方式决定了其不可能实现真正的私有方法或私有实例变量。
OC中的Extension, 即扩展, 也被称为class-continuation分类。这是唯一能声明实例变量的分类, 而且此分类没有特定的实现文件, 其中的方法都应该定义在类的主实现文件里。

公共接口里本来就能定义实例变量。不过, 把它们定义在Extension扩展实现块中可以将其隐藏起来, 只供本类使用。而且还可以在Extension扩展中服从协议, 这样也不会对外界暴露。

// .m
@interface ViewController () {NSInteger _age;
}
@property (nonatomic, copy) NSString *str;
@end@implementation ViewController
- (void)viewDidLoad {[super viewDidLoad];}
@end

Extension扩展 还有一种合理用法, 就是将 public接口中声明为只读的属性扩展为可读写, 以便在类的内部设置其值。这样, 封装在类中的数据就由实例本身来控制, 而外部代码则无法修改其值。

// .h
@interface ViewController : UIViewController
@property (nonatomic, copy, readonly) NSString *string;
@end// .m
@interface ViewController ()
@property (nonatomic, copy, readwrite) NSString *string;
@end@implementation ViewController
@end

只会在类的实现代码中用到的私有方法也可以声明在Extension扩展中。

@interface ViewController ()
- (void)private_someMethod;
@end

这样做可以把类里所含的相关方法都统一描述于此, 使类的代码更易读懂。若对象所遵从的协议只应视为私有,则可在Extension扩展中声明。

总结

1.通过Extension扩展向类中新增实例变量。
2.如果某属性在主接口中声明为只读, 而类的内部又要用设置方法修改此属性, 那么就在Extension扩展中将其扩展为可读写
3.把私有方法的原型声明在Extension扩展里面。
4.若想使类所遵循的协议不为人所知, 则可于Extension扩展中声明。

㉘ 通过协议提供匿名对象

我们可以用协议把自己所写的API之中的实现细节隐藏起来, 将返回的对象设计为遵从此协议的纯id类型。这样的话, 想要隐藏的类名就不会出现在API之中了。若是接口背后有多个不同的实现类,而你又不想指明具体使用哪个类, 那么可以考虑使用这个方法。

数据库连接(database connection)的程序也用这个思路,以匿名对象来表示从另一个库中返回的对象。对于处理连接哪个类,你也许不想让别人知道。如果没有办法令其继承字同一个基类,那么就得返回对下你跟遵从此协议:

@protocol EOCDatabaseConnection
- (void)connect;
- (void)disconnect;
- (void)isConnected;
- (NSArray*)performQuery:(NSString *)query;
@end;

然后,就可以用数据库处理器单例来提供数据库连接了。这个单例的接口可以写成:

@protocol EOCDatabaseConnection
@interface EOCDatabaseManger:NSObject
+ (id)sharedInstance;
- (id<EOCDatabaseConnection>) connectionWithIdentifier:(NSString *)identifier;
@end;

总结

1.协议可在某种程度上提供匿名类型。具体的对象类型可以淡化成遵从某协议的id类型,协议里规定了对象所应实现的方法。
2.使用匿名对象来隐藏类型名称(或类名)
3.如果具体类型不重要,重要的是对象能够响应特定方法,那么可以是使用匿名对象来表示。

高效 OC开发之协议与分类相关推荐

  1. 高效 OC开发之对象、消息、运行时

    ⑥ 理解属性的概念 想必你曾经也这样为某个类添加成员变量: @interface Person : NSObject { @publicNSString *_firstName;NSString *_ ...

  2. 高效 OC开发之熟悉Objective-C

    ① OC起源 Objective-C是C语言添加了面向对象特性, 是其超集(superset). OC语言使用的是消息机制, 并不是通过函数调用方法, 而是通过发送消息.Objective-C 使用动 ...

  3. IT:后端进阶技术路线图(初级→中级→高级)、后端开发工程师(技术方向分类之后台业务开发/中间件/内核/分布式架构)基础知识简介、技术路线/技术趋势指南(如何选择自己的技术方向)之详细攻略

    IT:后端进阶技术路线图(初级→中级→高级).后端开发工程师(技术方向分类之后台业务开发/中间件/内核/分布式架构)基础知识简介.技术路线/技术趋势指南(如何选择自己的技术方向)之详细攻略 目录 后端 ...

  4. 在闲鱼,我们如何用Dart做高效后端开发?

    背景 像阿里其他技术团队以及业界的做法一样,闲鱼的大多数后端应用都是全部使用java来实现的.java易用.丰富的库.结构容易设计的特性决定了它是进行业务开发的最好语言之一.后端应用中数据的存储.访问 ...

  5. 个人微信号二次开发sdk协议,微信个人号开发API接口

    个人微信号二次开发sdk协议,微信个人号开发API接口 微信SDK程序概要说明 个人微信号开发sdk非微信ipad协议.非mac协议,非安卓协议,api可实现微信99%功能: 无需扫码登录.可收发朋友 ...

  6. Flutter App开发蓝牙协议

    Flutter App开发蓝牙协议 Summary BLE低功耗蓝牙,是我们常说的蓝牙4.0, 该技术有极低的运行待机功耗,本文记录使用Flutter开发安卓App的过程,使用蓝牙模块的配置和一些细节 ...

  7. 码出高效:java开发手册_Java 11手册:最聪明的技巧来简化Java 11导航

    码出高效:java开发手册 Java 11:提示和技巧,日常陷阱及更多 为了庆祝Java 11的发布,我们邀请了八位Java专家与他们分享最新版本的最佳和最差体验. 由于本系列旨在作为Java 11的 ...

  8. 码出高效:java开发手册_Java 11手册:Java专家分享他们在Java 11方面的最佳和最差的经验

    码出高效:java开发手册 Java 10标志着Java生态系统新时代的开始,但最新版本证明仍有一些里程碑可言. Java 11是Oracle新的六个月周期中的第一个LTS版本. 您可以在此处下载Ja ...

  9. 《高效团队开发工具与方法》

    一直以来都在用所谓的敏捷开发,但是也只是简单的说,用什么用什么,从开始感觉不方便,到后来感觉习惯了,可没有考虑过到底能带来什么改变,所以最近在上下班的地铁上在看完了<高效团队开发工具与方法> ...

最新文章

  1. android 2.2.3,升还是不升 Android2.2与2.3性能测试对比
  2. 【Android 逆向】substrate 框架 ( substrate 简介 | substrate 相关文档资料 )
  3. ensp启动设备蓝屏_Windows 10系统遇到蓝屏怎么解决?
  4. vue-resource jsonp跨域问题解决方法
  5. 漫反射 高光反射_如何有效地使用反射
  6. java 双因素认证(2FA)TOTP demo
  7. 深度测试与alpha混合(1)
  8. RouterOS 5.22固定公网IP共享上网设置
  9. web 前端常用组件【04】Datetimepicker 和 Lodop
  10. win7系统两台电脑之间利用Socket实现文件传输---C++实现
  11. CCF NOI1047 寻找鞍点
  12. 用python画八卦图-用Python中的画图工具turtle绘制八卦图
  13. jQuery实现清空table表格除首行外的所有数据
  14. 最新ThinkPHP仿华为商城源码+带支付宝接口/在线支付
  15. 毛星云OpenCV3编程入门之python实现
  16. 关于IRR的一些总结
  17. #023单词接龙1(字符串)(女友)
  18. 对国内基金行业的一些思考 【投资干货】
  19. 2022年危险化学品经营单位主要负责人最新解析及危险化学品经营单位主要负责人考试资料
  20. java ygc逐步增加,【jvm学习笔记五】G1-YGC分析

热门文章

  1. 信息化对就业的影响与应对
  2. [原创]Fashion汽车定位器拆解
  3. 潭州学院html学习(day10)
  4. C语言 一元二次方程
  5. 视图的重命名mysql语句_sql语句重命名字段-视图重命名sql语句-数据库重命名sql语句...
  6. 台式电脑没鼠标怎么移动光标_没有鼠标怎么移动光标【设置措施】
  7. Atitit mis 管理信息系统概论 艾提拉著 目录 1. 互联网三大定律 2 1.1. 摩尔定律和 2 1.2. 吉尔德定律 电脑及网络宽带资源成为重要免费资源 2 1.3. 梅特卡夫定律 用户
  8. [转载]2017 中国电信(美洲)公司CTExcel US电话卡使用攻略_拔剑-浆糊的传说_新浪博客
  9. sin72度用计算机怎么算,三角函数值
  10. 程序员身体自测健康5大标准