Chapter 3 接口与 API 设计

  • Tips 15 使用前缀避免明明空间冲突

    • Objective-C 没有命名空间,所以我们在起名时要设法避免命名冲突
    • 避免命名冲突的方法就是使用前缀
    • 应用中的所有名称都需要加前缀(包括实现文件中的全局变量和纯 C 函数)
  • Tips 16 提供“全能(designated)初始化方法”
    • 一个会被所有初始化方法调用到的初始化方法
    • 当底层数据存储机制变化时,只需要修改这个方法就可以了,不需要改动其他初始化方法
    • 如果超类的全能初始化方法不适用于子类,或是与超类不同,那么需要覆盖这个超类方法
    • 子类的全能初始化方法都应该调用超类的对应方法,逐级向上
  • Tips 17 实现 description 方法
    • 在数组字典等集合对象打印时,都会调用对象的 description 方法,方便调试
    • 系统默认的 description 方法对于自定义的对象并没有输出较为有用的内容,所以可以实现这个方法方便我们显示对象
    • 在调试时会调用 debugDescription 方法(也就是在调试时 lldb 中输入 po 时调用的将会是 debugDescription),所以实现他可以帮助我们调试时获得更多的信息
    • 可以使用 NSDictionary 来实现 description 方法,这样显示和输出都会比较方便,例如:

      // Header File
      // 这里我略微修改了下原书中的示例代码
      @interface EOCLocation : NSObject
      @property (nonatomic, copy) NSString *title;
      @property (nonatomic) CGFloat latitude;
      @property (nonatomic) CGFloat longitude;
      @end
      // 我们要是可以使用 NSLog(@"%@", eoc_location) 直接输出这个对象的经纬度(也就是所有属性)就好了,那么可以参考下面的写法实现 description 方法
      @implementation EOCLocation
      - (NSString *)description {return [NSString stringWithFormat:@"<%@: %p, %@>",[self class],self,@{@"title": self.title,@"latitude": @(self.latitude),@"longitude": @(self.longitude),}];
      }
      @end
  • Tips 18 尽量使用不可变对象
    • 减少 side effect,在使用了一段时间的 RAC 和学习函数式思想后,一定程度上理解了不可变对象的好处
    • 具体开发实践中,应尽量把对外公布的属性设为只读,并且有必要时才对外公布,否则使用私有属性
    • 对于只读属性,可以不用指定内存管理语义(也就是 strong,weak,copy)
    • 对外只读的属性可以在对象内部,也就是类扩展(Class-Extension 也叫 Class-Continuation)中重新声明为可读写的
    • 可以使用 GCD 来设置读写操作为同步操作
    • 就算属性设置为只读,在外部仍可以使用 KVC 来访问这些属性,例如:[object setValue:@"value" forKey:@"propertyName"]
    • 集合属性(Array,Set,Dictionary)可以提供只读属性供外界使用(内部保存可变类型的变量,返回该变量的不可变拷贝),并提供操相应的操作方法,例如下面例子中,使用 -addFriend:-removeFriend: 方法来实现对 friends 集合的操作,这样保证了添加或删除盆友的操作对象是知情的。对于直接修改 friends 集合的操作对象是不知情的,这样可能会导致对象内各数据的不一致。

      @interface EOCPerson : NSObject
      @property (nonatomic, strong, readonly) NSSet *friends;
      @end@implementation EOCPerson {NSMutableSet *_internalFriends;
      }- (NSSet *)friends {return [_internalFriends copy];
      }- (void)addFriend:(EOCPerson *)person {[_internalFriends addObject:person];
      }- (void)removeFriend:(EOCPerson *)person {[_internalFriends removeObject:person];
      }
      @end
    • 不要在返回的对象上查询其是否是可变对象并对其进行操作,同上条这样对对象集合属性的直接修改,容易产生 bug

  • Tips 19 使用清晰而协调的命名方式
    • 方法名的风格要保证与自己的代码或是需要集成的框架一致,也就是上下文需要一致,这点最重要放第一
    • 起名遵循 Objective-C 的命名规范,这样的接口名字一定程度上提示了接口的作用
    • 方法名言简意赅,从左到右读起来最好像一个日常用于中的句子
    • 方法名里不要使用缩略后的类型名称
    • Objective-C 的方法名相较其他语言要长一些,但是可以更好地表达方法的作用,以及各个参数的意义,比如:
    Rectangle *recgangle = new Rectangle(5.0f, 10.0f);
    // 不如下面的命名方式
    Rectangle *recgangle = [Rectangle initWithSize:(float)width :(float)height];
    // 不如下面的命名方式
    Rectangle *recgangle = [Rectangle initWithWidth:(float)width andHeight:(float)height];
  • Tips 20 为私有方法名加前缀
    • 因为在 Objective-C 中没有私有方法,所有对象都可以响应任意消息,并且可以通过 runtime 获取对象可以相应的消息,所以我们使用特定的命名来区分私有方法
    • 在使用 Category 或继承系统中或第三方库中的类的时候,可以防止命名冲突
    • C 语言中使用 _ 下划线作为系统内部函数的开头所以我们不能使用 _ 作为私有方法的前缀(苹果的官方库也使用 _
    • 原书作者建议使用 p_ 来作为私有方法的前缀,个人建议使用开发中项目使用的前缀小写来作为类前缀,比如上文的 EOCPerson 中添加私有方法可以使用 eco_privateMethodName:,这样的前缀在第三方类库中出现重复的概率比较小
  • Tips 21 理解 Objective-C 错误模型
    • ARC 在默认情况下并不是异常安全的,也就是抛出异常的时候,在作用域末尾应该释放的对象将不会被释放
    • 可以使用 -fobjc-arc-exceptions 来告诉编译器需要生成异常安全的代码,但是这样会引入一些额外代码,并且在不抛出异常时也会执行这部分代码
    • 就算不使用 ARC 使用异常也很容易写出内存泄漏的代码,因为需要在抛出异常前清理所有申请的资源,所以现在我们只在非常罕见(严重错误,比如:抽象类中的方法没有实现)的情况下抛出异常,抛出之后不需要考虑回复的问题,并且退出应用,这样就不用编写复杂的异常安全代码
    • 对于不严重的错误,我们通过返回 nil/0 或是使用 NSError 来处理,NSError 中包含了错误处理所需的各种信息,我们自己的错误需要规划和设置好对应的 Error Domain,Error Code
    • 一般通过 delegate 来传递错误 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 或是输入参数返回错误 - (BOOL)doSomething:(NSError **)error
  • Tips 22 理解 NSCopying 协议
    • 实现 NSCopying 接口可以让类实现拷贝(copy)方法,- (id)copyWithZone:(NSZone *)zone 中的 zone 是以前开发时使用的内存区参数,目前已经不使用了,可以不用考虑他
    • 实现 NSMutableCopying 协议支持可变拷贝(mutableCopy)方法
    • 对象拷贝时需要决定是深拷贝还是浅拷贝,一般情况下用浅拷贝
    • 绝大多数情况下 NSCopying 实现的都是浅拷贝,所以如果使用深拷贝,建议创建一个单独的方法来完成

Chapter 4 协议(Protocol)和分类(Category)

  • Tips 23 使用委托(delegate)和数据源(data source)协议进行对象间通信

    • 委托模式(delegate pattern):对象把应对某个行为的责任委托给了另一个类
    • 类似我们经常使用的 UITableViewUITableViewDelegateUITableViewDataSource 分别定义了如何处理事件的接口和如何提供数据的接口,实现这两个接口为 UITableView 提供交互逻辑和显示数据,UITableView 本身只负责显示获取到的数据
    • 委托模式同样适用于异步事件,比如网络请求完成后,回调委托对象将结果传递回去,实现事件的异步处理
    • 使用委托对象的对象中的委托对象属性需要设置为 weak,防止循环引用
    • 使用委托中的方法时,使用 respondsToSelector: 先查询委托对象是否实现了该方法,特别是在协议中使用 @option 关键字标注的可选方法
    • 委托中的方法名要清晰明确,需要说明事件的来源,当前的事件,以及为什么委托对象需要获取这个事件,所有委托方法都需要将发起委托的对象发送到委托对象(作为第一个参数),让委托对象判断事件来源
    • 针对需要进行多次调用的委托对象(例如网络加载时下载进度),可以通过结构体等方法,在设置委托对象的时候,一次检查需要响应的方法并记录,之后在使用的时候,直接通过记录结果来判断是否实现了某个方法,不用每次都使用 respondsToSelector: 方法来查询是否实现,例:

      @interface EOCNetworkFetcher() {struct {unsigned int didReceiveData       : 1;unsigned int didFailWithError     : 1;unsigned int didUpdateProgressTo  : 1;} _delegateFlags;
      }
      @end@implementation EOCNetworkFetcher
      - (void)setDelegate:(id<EOCNetworkFetcherDelegate>)delegate {_delegate = delegate;_delegateFlags.didReceiveData = [delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)];_delegateFlags.didFailWithError = [delegate respondsToSelector:@selector(networkFetcher:didFailWithError:)];_delegateFlags.didUpdateProgressTo = [delegate respondsToSelector:@selector(networkFetcher:didUpdateProgressTo:)];
      }
      @end// 在需要调用 delegate 方法的时候
      if (_delegateFlags.didUpdateProgressTo) {[_delegate networkFetcher:self didUpdateProgressTo:currentProgress];
      }
  • Tips 24 将类的实现代码分散到便于管理的多个 Category 中
    • 在开发的过程中,类的代码只会越来越大,那么我们可以通过分类机制将类的代码打散,根据业务分散到不同的分类中
    • 应该把私有方法放到叫(Private)的分类中,隐藏实现细节
  • Tips 25 总是为第三方分类的分类名称加前缀
    • 如果分类中出现同名方法,容易出现奇怪的 bug,所以在为其他类添加分类的时候,分类名称和分类中的方法需要添加你自己使用的前缀
  • Tips 26 勿在分类中声明属性
    • 分类中可以定义方法(包括 getter 和 setter),但是不要定义属性,因为在分类中定义的属性不会生成实例变量
    • 虽然有 objc_setAssociatedObject 魔法可以用,但是这容易导致内存管理问题,因为无法使用属性记录内存管理语义,但是建议一般情况下不使用
    • 分类的主要作用是扩张类的功能,而不是封装数据
  • Tips 27 使用 Class-Continuation 分类,隐藏实现细节
    • Class-Continuation 分类必须定义在该类的实现文件中,并且可以声明实例变量,并且建议仅以此种方式增加实例变量
    • 头文件中声明为只读的属性,可以在实现文件中的 Class-Continuation 分类中扩展为可读写
    • 私有方法原型,和私有属性,都可以放到 Class-Continuation 分类中
    • 在 Class-Continuation 分类中可以声明实现的接口,并且外部不会知道
    • 可以通过私有属性很好的封装 C++/Objective-C++ 的代码,提供 Objective-C 的接口给其他代码使用
  • Tips 28 通过协议提供匿名对象
    • 使用类似 @property(nonatomic, weak) id<ProtocolName> delegate; 提供匿名类型对象作为 delegate,可以隐藏类名
    • 对于类型不重要,只需要提供可向应方法的对象,可以使用匿名对象,隐藏实现细节

对于 Chapter 1 的补充

第一章第四条中,多用类型常量,少用 #define 预处理指令中,建议大家使用类型常量而不是 #define 来定义常量,这里增加一个补充内容,swift 中,我们可以使用 struct 中的静态变量来声明常量,这样带来的一个好处是使用和分类管理非常方便

Xcode 8.0 带的 clang 4.0 后开始支持类常量,也就是定义属性的时候,可以加入 class 来修饰属性,这样这个属性是属于类的,于是乎,我们可以这样使用常量了

NSString *notificationName = XXXConstant.notificationNames.XXXUserDidLoginNotificationName;

看上去比类型常量长一些,不过似乎还算比较好看

定义的时候需要这样定义:

@interface XXXConstantNotificationNames : NSObject@property(nonatomic, readonly) NSString *XXXUserDidLoginNotificationName;@end@interface XXXConstant : NSObject@property(nonatomic, class, copy) XXXConstantNotificationNames *notificationNames;@end

并且,类常量是不会被 synthesize 的,也就是说编译器不会自动为类常量创建相应的变量,所以在实现文件中,我们需要这么写

@implementation XXXConstantNotificationNames- (NSString *)XXXUserDidLoginNotificationName {return @"XXXUserDidLoginNotificationName";
}@end@implementation XXXConstant
static XXXConstantNotificationNames *_notificationNames = nil;+ (void)load {_notificationNames = [[XXXConstantNotificationNames alloc] init];
}- (XXXConstantNotificationNames *) {reutrn _notificationNames;
}@end

看上去比定义一个 kXXXUserDidLoginNotificationName 字符串常量,麻烦了非常多,但是相信在项目代码量不断增加,以及工程变得越来越复杂以后,这样的做法对于代码管理上是非常有帮助的

转载于:https://www.cnblogs.com/noark9/p/7225106.html

Effective Objective-C 2.0 Tips 总结 Chapter 3 Chapter 4相关推荐

  1. [Effective Objective] 熟悉Objective-C

    了解 Objective-C Objective_C 是一种面向对象的语言.但与jave.C++等语言不同,它使用了消息结构(messaging structure)而非函数调用(function c ...

  2. C和指针(Chapter 1 Chapter 2)

    主要整理自己看c和指针(英文版)遇到的一些知识点.部分可能用英文表述. 1.What is the advantage of putting declarations, such as functio ...

  3. Journal entry of the eleventh chapter to chapter twelfth

    第十一章:正如很多人一样,觉得软件工程这个课程好像没什么用,感觉提高不了自己的写代码能力,学的都是理论知识,好像对于我们这种技术类的专业离得有点远,是这样的吗? 第十二章:每样东西都没有完美的,即使我 ...

  4. iOS 学习资料整理

    这份学习资料是为 iOS 初学者所准备的, 旨在帮助 iOS 初学者们快速找到适合自己的学习资料, 节省他们搜索资料的时间, 使他们更好的规划好自己的 iOS 学习路线, 更快的入门, 更准确的定位的 ...

  5. lt;转gt;iOSnbsp;学习资料整理

    阅读目录 视频教程(英文) 视频教程(中文) 书籍 博客 文章 相关网站 社区 工具/插件 GitHub Top 50 简介 邮件订阅 文档 指南 Awesome 系列 知乎上的讨论 Quora 上的 ...

  6. iOS 学习资料整理(中文版)

    这份学习资料是为 iOS 初学者所准备的, 旨在帮助 iOS 初学者们快速找到适合自己的学习资料, 节省他们搜索资料的时间, 使他们更好的规划好自己的 iOS 学习路线, 更快的入门, 更准确的定位的 ...

  7. iOS 学习资料大全

    转:  http://segmentfault.com/a/1190000002473595 这份学习资料是为 iOS 初学者所准备的, 旨在帮助 iOS 初学者们快速找到适合自己的学习资料, 节省他 ...

  8. 【精】iOS知识树,知识点(包括对象、Block、消息转发、GCD、运行时、runloop、动画、Push、KVO、tableview,UIViewController、提交AppStore)

    本文旨在总结iOS知识网络,知识点,该知识网络罗列出常见UIKit.Foundation的对象特点和一些使用经验,可以看成是一本书:文本编辑采用树的形式,对知识点进行罗列,并标注一些使用经验(★)希望 ...

  9. 语料库(精华版)chapter 3/4/5

    Chapter 3 雅思听力特别名词语料库 Chapter 4 雅思听力形容词/副词语料库 Chapter 5 雅思听力吞音/混合训练 Test Paper 1 Test Paper 2 Test P ...

最新文章

  1. .net 服务器自动执行,自动检测服务器使用流量并执行命令脚本
  2. 冯·卡门:用数学武装工程科学
  3. Visio图形自动编号
  4. phpcmsV9子栏目调用其父栏目名称、URL、catid等信息 - 方法总结
  5. 安大计算机学院院长汤进,淮北师范大学
  6. HALCON 20.11:深度学习笔记(2)
  7. 常用网盘资源搜索网站
  8. 《原则》瑞达利欧_epub+mobi+azw3
  9. UML建模:基于智慧校园的二手交易平台
  10. 【第007问 Unity中如何进行UV动画?】
  11. MYSQL统计收益排名
  12. DBF文件简介(转)
  13. 第四周web课堂作业
  14. 线性规划-pulp-复杂矩阵
  15. 超级哄女孩工具之一千枝会动的玫瑰实现
  16. c语言浮点数如何精确计算,浮点数精确运算的分析和解决办法
  17. iOS 四舍六入五成双算法
  18. JS 中常见的转义字符串
  19. 极海推出APM32A系列车规级MCU
  20. 【云原生】学习K8s,读完这篇就够了

热门文章

  1. java异常处理和自定义异常利用try和catch让程序继续下去(回来自己再写个例子试运行下)...
  2. HTML5学习笔记(二十六):JavaScript的错误处理
  3. android开发学习笔记系列(6)--代码规范
  4. nargout 【转】
  5. 《游戏人工智能编程》读书笔记 —— 向量的归一和点乘
  6. 非客观书评(三)——《ARM Cortex-M3 权威指南》
  7. 如何解决移动硬盘找不到的问题
  8. Python List相关函数使用实例
  9. 150分试卷c语言,连续5道C语言题目一共送150分啊,题目2.一个农场有头母牛,现 爱问知识人...
  10. php 上传进度条api,php如何实现上传进度条