第一次阅读《Effective Objective-C 2.0》这本书是在15年,这些年过去回过头去看,当年那些经验还是一点都不过时,有些细节点还是值得反复强调。我在这里列举一些大家可能平时不大会留意的地方。
-- 2019-04-12 00:39 来自StrongX的博客
通过上图我们知道OC变量指针保存在栈中,而实际内容则是保存在堆中,这里我们就需要注意悬挂指针的产生,悬挂指针难以调试,而且经常造成的现象就是一会取值正确,一会取值不正确。这是由于堆中的内容已经被标记为释放,而实际上内存并没有被释放,能否正确取到值取决于这一片内存是否被重写,产生悬挂指针是非常危险的。
- 常量一定要同时使用
static
和const
来修饰。const
表示该变量不可修改,若是修改由const修饰的变量编译器就会报错。而static
表示变量仅在定义此变量的编译单元中可见。
而实际上我在之前老的项目的确见到过没有遵循这一准则的情况,没有同时使用static
和const
同时来修饰一个变量,而仅仅使用了const
来做修饰。如果不使用static
来修饰变量,那么编译器就会对此变量添加一个“外部符号”。那么此时你如果同时在两个文件中定义了相同的变量名,那么编译器就会报错:
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
当然编译器会告诉在哪些文件中那个变量发生了重复定义。所以我们在定义常量的时候一定要同时使用static
和const
来进行定义,因为发生重名情况并不罕见。
-
readonly
和readwrite
读写权限:
我们经常有这样的场景:我们需要提供变量对外可读,但是在类实现中可以去修改,所以我们在 .h
文件中定义变量为readonly
:
@property (nonatomic, strong,readonly) NSString *name;
然而这个时候我们无法.m
文件中进行修改,当你试图去修改一个由readonly
定义的变量时,编译器会发出错误:
Assignment to readonly property
那么这个时候我们可以在分类
中同时将变量定义为readwrite
(不了解分类概念的同学,可以自行去了解下)
@property (nonatomic, strong, readwrite) NSString *name;
- 苹果的框架中使用了大量的类族,比如
NSArray
、NSNumber
······我们需要注意类族隐藏了大量的子类,暴露给我们的只是一个抽象类,我们使用抽象类初始化方法返回给我的并不是抽象类本身,注意点: - 在创建类族的子类的时候需要额外注意,当我们将一个抽象类作为父类的时候一定要实现它所有的初始化方法和原始方法。
- 抽象类本身自己不提供存储,存储是由自己实现的,所以你继承自抽象类的子类也一定要自己实现存储,(比如给他一个属性
NSArray
),笔者不建议去创建一个抽象类的子类,你可以使用扩展的方式去代替。当然你自己在设计自己的架构的时候,类族是一种很值得考虑的方式(如果有一大堆子类的时候)。 - 以下判断永远不会为真:
id maybeAnArray = /* ... */;
if ([maybeAnArray class] == [NSArray class]) {
// Will never be hit
}
我们都知道使用
NSCache
来实现缓存,从使用方法来看和使用NSDictionary
类似,那么我们为什么不直接使用NSDictionary
?使用NSCache
有几个好处:NSCache
实现了自动删减缓存,也就是说当系统发出低内存警告的时候,NSCache
会自动去释放最久未使用的对象,并且你还可以对NSCache
设置最大内存等相关设置。当然这些功能,你使用NSDictionary
添加些额外参数也能够实现,不过老实说你花了大功夫实现你的缓存机制后可能还真的不一定比NSCache
好用,这个让我想到某个很出名的图片加载框架~~~~在某些情况下我们将某个对象最为字典的key(可能绝大多数情况下是字符串),苹果的代码中也有很多这样的对象,比如weak的弱引用表......当我们将对象作为字典的key时,对象必须只是拷贝,也就是我们需要实现
NSCopying
协议,然而NSCache
不需要,你可以将任意OC对象最为NSCache
的键值。NSCache
默认是线程安全,线程安全对缓存来说是非常重要的。如果你使用NSDictionary
那么你必须手动添加很多lock等操作,lock这个东西加好了没问题,一不留神发生死锁是很常见的。
- 我们知道使用
NSTimer
的时候有很多需要注意点,如:
1、未销毁计时器而导致发生内存泄漏。
2、由于runloop Mode而产生的各种问题
······
笔者之前一直使用GCD Source来替代NSTimer,不过在书中提到了另外一种方式,笔者认为很新颖,之前从未想到过。
上面这段代码完美解决了由于计时器持有self
,self
又持有计时器而导致计时器若是不销毁就会发生内存泄漏的问题,计时器不再持有self
,在iOS 10以后NSTimer
开放给我们的接口中出现了这么一个方法:
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
使用此方法创建的计时器同样不会造成“保留环”,估计实现方式和上面贴的那段代码估计原理是类似的。
先写这么多,下回再写,晚安。