本文为《Effect Objective-C 2.0 编写高质量iOS与OS X代码的52个有效的方法》的读后感。由于书中的内容很多都是经典的案例,然而直接记住有一定的压力。所以将重点列在下面,方便后期回顾。
第一章 熟悉 Objective-C
- Objective-C为C语言添加了面向对象这一个特性,是C的超集。由于动态绑定消息结构,只有在运行时候才会检查对象的类型,所以接收到消息后,会在运行期而非编译器时期来决定。(冠以动态绑定将在下面的后面详细介绍。)
- 改变头文件引用情况,从而改善依赖关系。
- 方法:在不需要调用类内部对象的时候,直接使用
@class className
来取代#import className.h
- 原因:
- 在.h文件中尽可能减少不必要的头文件,一般如果只是为了加入某个类直接可以使用
@class className
来进行添加,否则容易产生循环import,在编译的时候编译器会报错。 - 对于保存在一个较大类中的 protocol ,可以将这个 protocol 单独放在一个文件中,以防引入类导致的编译时间的降低。
- 在.h文件中尽可能减少不必要的头文件,一般如果只是为了加入某个类直接可以使用
- 方法:在不需要调用类内部对象的时候,直接使用
- 采用字面量来代替等价的方法。
- 方法:
- NSString 对象创建采用
@"this is a NSString Object";
- NSArray 对象创建采用
@[obj1, obj2, obj3];
- NSDictionary 对象创建采用
@{ key1 : obj1, key2 : obj2, key3 : obj3};
- NSString 对象创建采用
- 原因:Objective-C 对于创建一些常用的对象给予了我们一些语法糖,从而更加简明扼要。
- 注意点:
- 如果在创建的过程中如果有对象是nil的,那么会导致程序 crash。
- 产生的对象都是不可变的,也就是 immutable, 如果要创建对应的可变对象的话,需要使用
[object mutableCopy]
- 方法:
多用类型常量,少用#define
方法:
static const NSTimeInterval kAnimationDuration = 0.3f;
代替#define ANIMATION_DURATION 0.3;
static NSString *const StringValue = @"this is a string"
代替#define STRING_VALUE @"this is a String"
- 对于需要暴露给外部的属性命名如下12345// Class.hextern NSString *const StringValue;// Class.mNSString *const StringValue = @"this is a string"
原因:
- 由于
#define
采用的是直接在预处理的过程中就把对应的内容进行替换。而在编写的过程中不会成功提醒。 - 这样能够清晰的描述所需要设置的类型。
- 由于
- 注意点:
- static 不能出现在 .h 文件中,因为在 .h 文件中使用了 static 也就意味着这个变量为全局变量。
- const 是为了防止被修改而存在的。
- 对于不会改变的变量,使用 const
- .m 文件中使用 static 进行修饰的意思就是每一份该变量的作用于只局限在该编译单元中。编译器每收到一份编译单元(一般是一个类的实现文件),那就会生成一份目标文件(.o文件)。如果没有使用 static 进行修饰,那么当编译器进行编译,如果别的类里面也使用了这个变量名,那么就会提示123duplicate symbol _property in:a.ob.o
对于状态码使用枚举类型。
方法:
12345678910typedef NS_ENUM(NSUInteger, ValueStatu) {ValueAStatus,ValueBStatus,ValueCStatus,}typedef NS_OPTION(NSUInteger, ValueOption) {ValueAOption = 1 << 0,ValueBOption = 1 << 1,ValueCOption = 1 << 2,}原因:
- 枚举类型可以更好地表达有限个的类型的意思。方便后期添加。
- 对于
NS_OPTION
这个类型,可以判断语句中直接使用ValueMyOption & ValueBOption
来进行判断我所提供的变量是否包含了多个选项。 - 使用
typedef
而不是直接使用原有的enum
是为了匹配各种编译器,因为苹果给我们在底层对相关内容进行了封装。
第二章 对象、消息、运行期(runtime)
- 理解“属性”这一概念
- 方法:使用
@property (nonatomic, strong) *Class className
来代替直接使用@public NSData *_data
。 - 原因:
- 如果使用 JAVA、C++ 的创建对象的方法,编译在编译器计算偏移量,那么在修改这个类的定以后需要进行重新编译。
- 如果在2个属性中间加入了第三个属性,那么就会打乱原有的偏移量,如果其他代码还在调用这个偏移量,那么就会发生错误。
- Objective-C 使用
类对象
对相关的内容进行管理,从而永远能够获得正确的偏移量。(在运行期的过程中向类中新增实例变量也可以正确获取。)这就是稳定的应用程序二进制接口(Application Binary Interface, ABI)
- 注意点:
- 使用
@property
来声明变量,编译器会在编译期自动帮你创建 setter 方法和 getter ,这个过程叫做“自动合成(autosynthesis)”,同时对于这个属性,会生成对应的实例变量。同时 Objective-C 中延续了 C 中 struct 的语法糖,对于属性可以直接使用点语法获得对应的实例变量。 - 你可以使用
@synthesize
来手动创建对应的实例变量的名字,默认编译器会自动帮你创建实例变量的名字,一般是原有名字前面加上下划线(_)
- 对于如果你不想让编译器自动帮你创建 setter 或 getter 方法,你直接手动创建即可,但是如果你不想让编译器帮你创建 setter 和 getter 方法,那么就需要在 implementation 中使用
@dynamic
来声明对应的属性。
- 使用
- 方法:使用
- 属性的特质
在前面的@property (nonatomic, strong) *Class className
中,括号内,有2个特质,这两个特质表示了这个属性有哪些特性。而这些特质我们分为4类- 原子性:
- atomic(default),原子性,表示该属性在读写时候会加上同步锁,从而防止读取脏数据的可能性。但是极大的影响性能。
- nonatomic,非原子性,表示该属性在一般情况下有可能会读取到脏数据。
- 读/写权限。
- readwrite (default),具有读写特质,如果使用了
@synthesize
说明编译器会自动帮你创建对应的读写方法。 - readonly, 只读属性,如果使用了
@synthesize
说明编译器会自动帮你创建读取方法。
- readwrite (default),具有读写特质,如果使用了
- 内存管理语义
- assign(default),只会针对“纯量类型”,比如说
CGFloat
,NSInteger
等等。 - strong,表示了一种“拥有关系”,设置新值的时候,会保留新值,释放旧值。
- weak,表示了一种“非拥有关系”,设置新值的时候,不会保留新值,也不会保留旧值,如果他指向的对象被释放,他就会置 nil 。(实现原理类似于写一个字典中,然后根据字典中的 key ,来获得他的 value ,从而确定是否需要置为空)
- unsafe_unretained,表示了一种“非拥有关系”,他和 weak 类似,不过在目标被摧毁的时候,他不会置空,所以是不安全的。
- copy,这个特性和 strong 相似,不过他在创建新值的时候不会保留新值,而是创建一份新值的拷贝(copy),但是这个时候,他创建的是新值是不可变的(即不是MutableCopy)。
- assign(default),只会针对“纯量类型”,比如说
- 方法名
- getter=
,指定对应的getter方法名,比如说 @property (nonatomic, getter=isOn) BOOL on;
这样他的 getter 方法就是isOn
,从而在调用的时候更加明白。 - setter=
,一般很少去实现这个方法。实现 setter 方法可能会导致程序出现 Bug。 - 注意:在赋值语句的时候,最好直接使用实例变量进行赋值。因为这样防止因为 setter 方法发生改变导致的问题。同时,如果属性是
readonly
那么他就没有setter
方法这样做会导致使用setter方法失败。
- getter=
- 注意点:在iOS开发中,因为性能原因,所以我们一般使用 nonatomic ,而在 OS X 中进行开发的时候,我们使用 atomic 一般也不会遇到瓶颈。
- 原子性:
- 初始化的过程中尽可能使用实例变量进行访问。
- 使用实例变量来获得对应的实例变量进行赋值速度更快
- 使用实例变量不会调用 setter 方法,如果该属性是 copy 方法,那么就不会触发 copy 方法,只会保留新值并释放旧值。
- 使用实例变量不会触发 KVO 通过属性来访问,可以方便的使用断点来找到对应的那个属性的时机。
- 在对象内部的时候,读取使用实例变量,赋值使用属性。
- 对于懒加载,需要使用属性进行调用,从而创建实例变量。
对象等同性。
- 方法:使用
isEqual
来保证两个对象是相同的。 原因:
- 因为直接 == 比较的是两个指针本身。
- NSObject 类的协议中用两个判断相同的方法使用
isEqual
和hash
。默认是两个指针值相同才返回真。 - 对于某个对象,我们使用下面代码来返回他的哈希值(哈希值相同,两个对象不一定相同)1234567- (NSUInteger)hash {NSUInteger firstNameHash = [_firstName hash];NSUInteger lastNameHash = [_lastName hash];NSUInteger ageHash = _age;return firstNameHash ^ lastNameHash ^ age;}
注意:一般对于特定类的相等的描写:
- 首先判断指针是否相等。
- 然后判断类是否相等
- 然后判断值是否相等。
- 容器相同性:{(1,2)}->{(1),(1,2)}->{(1,2),(1,2)}->copy->{(1,2)}
- 方法:使用
- 以“类族模式”隐藏实现细节。
- 方法:子类继承父类,父类在创建方法中使用枚举来返回不同子类的类型,同时将绘制逻辑放到父类中实现。
- 原因:基于Objective-C不是强制类型,隐藏实现方法,使得调用更加简单,更加清晰。
- 在既有类中使用关联对象存放自定义数据。
- 方法:引入
<objc/runtime.h>
,然后调用void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
,id objc_getAssociatedObject(id object, void *key)
,void objc_removeAssociatedObject(id object)
三个方法来创建,调用,删除关联自定义数据。 - 原因:更易读懂,无需在代码块中来回游走。
- 注意点:
- 关联类型种类包括 assign, nonatomic, atomic, retain, copy, retain, copy。
- 不能大量使用,否则容易导致代码失控。
- 该方法是最后解,即用其他方法无法实现的时候才会使用这个方法。
- 方法:引入
- 理解objc_msgSend的作用
- 方法:使用
id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter)
,其中someObject是接受者,@selector(messageName:)是选择子。这样返回的是是一个id对象。同理存在objc_msgSend_stret(返回struct)
,objc_msgSend_fpret(返回浮点数)
,objc_msgSendSuper(给id对象的超类发送信息。)
- 原因:Objective-C 是一门动态语言,objc_msgSend 就是发送去查找“方法列表”(每个类都有自己的方法列表,同时在查找完之后会保存在这个类自己的快速映射表,方便下次查找)并调用相关函数,如果没有调用到,那么就会沿着继承体系去进行查找,如果最后还是没有,那么就使用转发操作。
- 具体表现:尾调用优化(tail-call optimization)
- 方法:使用
- 消息转发机制
- 使用方法: