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 字符串常量,麻烦了非常多,但是相信在项目代码量不断增加,以及工程变得越来越复杂以后,这样的做法对于代码管理上是非常有帮助的

Effective Objective-C 2.0 Tips 总结 Chapter 3 & Chapter 4的更多相关文章

  1. EFFECTIVE OBJECTIVE-C 2.0 TIPS 总结 CHAPTER 1 & CHAPTER 2

    下面只是对读到的所有 Tips 结合我平时开发中遇到的问题进行总结,每一个 Tips 和书中的每一条对应,本文的目的是去掉书中的大部分讨论的内容,让人能够马上使用这些 Tips,建议阅读过原书后食用更 ...

  2. 阅读《Effective Java》每条tips的理解和总结(1)

    <Effective Java>这本书的结构是90来条tips,有长有短,每条tip都值的学习.这里根据对书中每条tip的理解做简短的总结,方便日后回顾.持续更新~ 1. 考虑用静态方法代 ...

  3. iOS开发——技术精华Swift篇&Swift 2.0和Objective-C2.0混编之第三方框架的使用

    swift 语言是苹果公司在2014年的WWDC大会上发布的全新的编程语言.Swift语言继承了C语言以及Objective-C的特性,且克服了C语言的兼容性问题.Swift语言采用安全编程模式,且引 ...

  4. 执行sudo时报错:effective uid is not 0

    http://jingyan.baidu.com/article/c45ad29cd83d4b051753e232.html     今天将 / 授权给了一个普通用户 导致一些问题. 启事: 操作前一 ...

  5. [svc]执行sudo时报错:effective uid is not 0

    http://jingyan.baidu.com/article/c45ad29cd83d4b051753e232.html 今天将 / 授权给了一个普通用户 导致一些问题. 启事: 操作前一定要先在 ...

  6. 阅读《Effective Java》每条tips的理解和总结(2)(持续更新)

    15. 使类和成员的可访问性最小化 一个好用的类的属性必须要隐藏起来,干净的将它与类的api分离开来,类之间只通过api相互使用,降低他们之间的耦合性.为了做到这一点,建议根据情况选择尽可能低的访问级 ...

  7. Journal entry of the eighth chapter to chapter ten

    Chapter eight: 当我们做一个项目的时候,一开始可能会信息满满,或者说是通过一些调查分析后觉得自己的团队能完全实现用户所提出的所有要求,但是,往往在很自信的时候,我们都会处处碰壁,因为组内 ...

  8. Journal entry of the thirteenth chapter to chapter seventeenth(第十三章和十七章阅读与疑问)

    第十三章: 软件测试的意义在于: a.     发现软件错误: b.     有效定义和实现软件成分由低层到高层的组装过程: c.     验证软件是否满足任务书和系统定义文档所规定的技术要求: d. ...

  9. Journal entry of the eleventh chapter to chapter twelfth

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

随机推荐

  1. java设计模式之 装饰器模式

    装饰器模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构. 这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装 ...

  2. Docker示例

    运行一个Hello world   zane@zane-V:~$ docker run ubuntu /bin/echo 'Hello world' Unable to find image 'ubu ...

  3. linux-ubuntu下fastQC的安装

    1.fastqc是在Java环境下运行的:所以在安装fastqc之前,Linux下要有相应的Java运行环境(JRE).且java的版本应该在1.8.0版以上 2.java的安装:下载最新版本的Jav ...

  4. python socketserver监听多端口多进程

    多进程监听多端口 # 多线程socket # 程序监听两个端口,端口逻辑相同其中一个端口放在子进程下 # 每次请求会在产生一个进程处理请求 import SocketServer from multi ...

  5. SHELL命令集锦

    1.定时任务crond使用. crontab -e -u www文件编辑保存在/var/spool/cron/www文件中. 参考示例: */1 * * * * /usr/local/php/bin/ ...

  6. go 基础语法

    时间有限,简单记一些常用的,麻烦的不写了 定义变量:可以连续定义,也可以单个定义 var a int    int类型 var a="ds"   默认string类型 a:=&qu ...

  7. web移动端布局方式整理

    写H5页面一直写的有点随意,只是保证了页面在各个屏幕下显示良好,却没有保证到在各个屏幕下是等比例放大或者缩小.这些天在写一些页面,试着看看能不能写出等比例放大缩小的页面,发现不容易啊,在网上找了一些文 ...

  8. 使用Iterator的方式也可以顺利删除和遍历

    使用Iterator的方式也可以顺利删除和遍历 eg: public void iteratorRemove() { List<Student> students = this.getSt ...

  9. 持续集成篇 --Hudson持续集成服务器的安装配置与使用

    样例项目参考视频教程:http://www.roncoo.com/course/view/85d6008fe77c4199b0cdd2885eaeee53 IP:192.168.4.221  8G内存 ...

  10. linux 下载文件到本地磁盘的命令是什么

    linux下可以直接运行命令下载或上传文件1.检查并安装相应的包:yum install lrzsz2.使用 sz 文件名 现在相应的文件到本地磁盘.3.上传使用rz 选择相应文件即可.