Effective Objective-C

Effective Objective-C的读书心得

了解Objective-C语言的起源

  • Objective-C从Smalltalk语言是从Smalltalk语言演化而来,
    Smalltalk是消息语言的鼻祖。

  • Objective-C是C语言的超集,在C语言基础上添加了面向对象等特性,可能一开始接触时你会觉得语法有点奇怪,那是因为Objective-C使用了动态绑定的消息结构,而Java,C++等等语言使用的是函数调用。

  • 消息结构与函数调用的关键区别在于:函数调用的语言,在编译阶段由编译器生成一些虚方法表,在运行时从这个表找到所要执行的方法去执行。而使用了动态绑定的消息结构在运行时接到一条消息,接下来要执行什么代码是运行期决定的,而不是编译器。

在类的文件中尽量少引用其他头文件

  • 如果需要引用一个类文件时,只是需要使用类名,不需要知道其中细节,可以用@class xx.h,这样做的好处会减少一定的编译时间。如果是用的#import全部导入的话,会出现a.h import了b.h,当c.h 又import a.h时,把b.h也都导入了,如果只是用到类名,真的比较浪费,也不够优雅
  • 有时候无法使用@class向前声明,比如某个类要遵循一项协议,这个协议在另外一个类中声明的,可以将协议这部分单独放在一个头文件,或者放在分类当中,以降低引用成本。

多用字面量语法,少用与之等价的方法

  • 传统创建数组方法:
    *languages
    1
    2
    3
    NSString *Swift = [languages objectAtIndex:2];
    NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:@"key", @"value", nil];
    NSString *value = [languages objectForKey:@"key"];
  • 字面量:
    *languages
    1
    2
    3
    NSString *Swift = languages[2];
    NSDictionary *dict = @{@"key" : @"value"};
    NSString *value = languages[@"key"];

    这样做的好处:使代码更简洁,易读,也会避免nil问题。比如languages数据中 someObject 如果为nil时,字面量语法就会抛出异常,而使用传统方法创建的languages数组值确是@[@”PHP”, @”Objective-C”];因为字面量语法其实是一种语法糖,效果是先创建了一个数组,然后再把括号中的对象都加到数组中来。

    不过字面量语法有一个小缺点就是创建的数组,字符串等等对象都是不可变的,如果想要可变的对象需要自己多执行一步mutableCopy,例如
*languages
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
```

## 多用类型常量,少用#define预处理指令
![参考](https://www.jianshu.com/p/064b5b82a3a2)

## 多用枚举表示状态、选项、状态码

![参考](https://www.jianshu.com/p/064b5b82a3a2)


# 对象、消息、运行期

## 理解“属性”这一概念

* 定义对外开放的属性时候尽量做到暴露权限最小化,不希望被修改的属性要加上readonly。
* atomic 并不能保证多线程安全,例如一个线程连续多次读取某个属性的值,而同时还有别的线程在修改这个属性值得时候,也还是一样会读到不同的值。atomic 的原理只是在 setter and getter 方法中加了一个@synchronized(self),所以iOS开发中属性都要声明为nonatomic,因为atomic严重影响了性能,但是在Mac OSX上开发却通常不存在这个性能问题

## 在对象内部尽量直接访问实例变量

* 在类内读取属性的数据时,应该通过直接实例变量来读,这样不经过Objecit-C的方法派发,编译器编译后的代码结果是直接访问存实例变量的那块内存中的值,而不会生成走方法派发的代码,这样的速度会更快。
* 给属性写入数据时,应该通过属性的方式来写入,这样会调用setter 方法。但是在某种情况下初始化方法以及dealloc方法中,总是应该直接通过实例变量来读写数据,这样做是为了避免子类复写了setter方法造成的异常。
* 使用了懒加载的属性,应该一直保持用属性的方式来读取写入数据。

## 理解“对象等同性”这一概念
### 思考下面输出什么?
```NSString *aString = @"iphone 8";
NSString *bString = [NSString stringWithFormat:@"iphone %i", 8];
NSLog(@"%d", [aString isEqual:bString]);
NSLog(@"%d", [aString isEqualToString:bString]);
NSLog(@"%d", aString == bString);

输出 110
==操作符只是比较了两个指针,而不是指针所指的对象

以“类族模式”隐藏实现细节

为什么下面这段if 永远为false

maybeAnArray
1
2
3
if ([maybeAnArray class] == [NSArray class]) {
//Code will never be executed
}

因为[maybeAnArray class] 的返回永远不会是NSArray,NSArray是一个类族,返回的值一直都是NSArray的实体子类。大部分collection类都是某个类族中的’抽象基类’
所以上面的if想要有机会执行的话要改成

1
2
3
4
id maybeAnArray = @[];
if ([maybeAnArray isKindOfClass [NSArray class]) {
//Code probably be executed
}

这样判断的意思是,maybeAnArray这个对象是否是NSArray类族中的一员
** 使用类族的好处:可以把实现细节隐藏再一套简单的公共接口后面 **

既有类中使用关联对象存放自定义数据

这条讲的是objc_setAssociatedObject和objc_getAssociatedObject,如何使用在这里就不多说了。值得强调的一点是,用关联对象可能会引入难于查找的bug,毕竟是在runtime阶段,所以可能要看情况谨慎选择

书籍原文档