Chapter 7. The System Frameworks
<br />
Item 49: Use Toll-Free Bridging for Collections with Custom Memory-Management Semantics
<br />
这一节讲Toll-Free Bridging。
CFArrayRef aCFArray = (__bridge CFArrayRef)anNSArray;
这是一个NSArray桥接成CFArray的例子。非OC对象有ARC不适用的问题。bridged cast涉及到ARC对OC对象的引用,__bridge
和__bridge_transfer
(把CF数据结构变成OC对象)都是保留了ARC对OC对象的所有权,所以就不用手动写对象的释放了。而__bridge_retained
则是放弃了所有权,这时需要手动调用CFRelease(CFObject)来手动释放非OC对象。
使用桥接来转换的目的是使用CoreFoundation中具有的一些独特功能。文中举的例子是改变字典对键和值得内存语义。普通的OC字典,对键进行copy,对值进行retain,而用CFDictionary可以改变这一点。例子有点长,然后我好像不是特别懂…等我懂了再来更一下。
<br />
Item 50: Use NSCache Instead of NSDictionary for Caches
<br />
这一节讲NSCache对象来做缓存。
抄一段Reference:
An NSCache object is a collection-like container, or cache, that stores key-value pairs, similar to the NSDictionary class.
While a key-value pair is in the cache, the cache maintains a strong reference to it. A common data type stored in NSCache objects is an object that implements the NSDiscardableContent protocol. Storing this type of object in a cache has benefits, because its content can be discarded when it is not needed anymore, thus saving memory.
NSCache objects differ from other mutable collections in a few ways:
- The NSCache class incorporates various auto-removal policies, which ensure that it does not use too much of the system’s memory. The system automatically carries out these policies if memory is needed by other applications. When invoked, these policies remove some items from the cache, minimizing its memory footprint.
- You can add, remove, and query items in the cache from different threads without having to lock the cache yourself.
- Unlike an NSMutableDictionary object, a cache does not copy the key objects that are put into it.
其实这一节讲的内容以上就大概都提到了。首先这个类是thread-safe的,因为缓存如果不thread-safe会很麻烦,我猜可能会引起重复下载。NSCache的键可以是不支持copy的对象,NSCache也不对键做copy操作,所以用起来比NSDictionary要灵活方便。另外比较厉害的是在内存紧张的时候它可以删减使用频率最低的对象。上面提到了这个对象需要实现NSDiscardableContent协议,比如NSPurgeableData类的对象。NSDiscardableContent协议中,beginContentAccess
和endContentAccess
这两个方法用来表示当前对象是否处于可被丢弃的状态。
<br />
Item 51: Keep initialize and load Implementations Lean
<br />
这一节讲load与initialize两个方法。
都是初始化的方法,但是有一些不同之处。load是程序启动时就会调用的,所有用到的类都会调用,调用时整个程序阻塞,要等待所有类加载完毕才会响应,所以尽量不要在里面做操作。特别是不要做用到别的类的操作,因为load的阶段属于fragile state,各个类的加载顺序不定,也不能确定哪个类加载完毕了,所以可能不能正常调用别的类的方法。另外load是不遵守类继承规则的,如果本类没有实现load方法,就不会调用了,不会像继承系统一样向父类寻找方法的实现。
initialize采取的是懒加载的方式,程序使用到这个类时才会调用,调用时线程阻塞,如果是主线程,会造成UI无响应,所以也是尽量不要做复杂的操作。initialize应只用来设置内部数据,如果设置其他类的数据,也会出现类似的可能没有完全加载好的问题。initialize遵守类继承规则,如果本类没有实现会向父类查找。
对于不能在编译器初始化的全局对象,可以放在initialize做。这个坑我踩过。因为NSString对象的初始化可以写在外面,我以为其他的对象也可以,然而并不能[\再见]。应该在外面声明,然后在initialize内部进行初始化,此时已经不是编译期而是运行期了,对象可以正常初始化了。
<br />
Item 52: Remember that NSTimer Retains Its Target
<br />
这一节讲NSTimer是怎么出现retain cycle的,以及怎样去解决。
直接看一下代码分析一下吧:
@interface EOCClass : NSObject
- (void)startPolling;
- (void)stopPolling;
@end
@implementation EOCClass {
NSTimer *_pollTimer;
}
- (id)init {
return [super init];
}
- (void)dealloc {
[_pollTimer invalidate];
}
- (void)stopPolling {
[_pollTimer invalidate];
[_pollTimer = nil];
}
- (void)startPolling {
__weak EOCClass *weakSelf = self;
_pollTimer = [NSTimer eoc_scheduledTimerWithTimeInterval:5.0
block:^{
EOCClass *strongSelf = weakSelf;
[strongSelf p_doPoll];
}
repeats:(BOOL)repeats];
}
- (void)p_doPoll {
//Poll the resource
}
@end
@interface NSTimer (EOCBlocksSupport)
+ (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats;
@end
@implementation NSTimer (EOCBlocksSupport)
+ (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats
{
return [self scheculedTimerWithTimeInterval:interval target:self selector:@selector(eoc_blockInvoke:) userInfo:[block copy] repeats:repeats];
}
+ (void)eoc_blockInvoke:(NSTimer *)timer {
void (^block)() = timer.userInfo;
if (block) {
block();
}
}
@end
再放一个EOCClass类原来的startPolling方法对比一下:
- (void)startPolling {
_pollTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 traget:self selector:@selector(p_doPoll) userInfo:nil repeats:(BOOL)repeats];
}
这里的场景是创建一个EOCClass类型的对象(起个名字叫EOCObject吧),然后调用它的startPolling方法。EOCObject对于pollTimer的保留很明显能看到。如果采用之前的startPolling方法,pollTimer因为以EOCObject为target,所以也会保留它,这样就形成了保留环。这个保留环打破的时机是pollTimer调用invalidate
的时候,也就是调用stopPolling
方法或者EOCObject被销毁时,但此时由于保留环的存在,EOCObject很难被销毁,那就只有采取当外部指向这个保留环的最后一个引用撤销时,手动调用stopPolling
,操作性比较差。
解决的思路是,不使用target来调用p_doPoll
方法,而改用传入block来实现,并且使用weakSelf避免了block中的保留环。这样pollTimer不再有指向EOCObject的强引用,也就避免了内存泄露的发生。具体的方法是,给NSTimer添加一个分类,把block作为userInfo传入,然后调用时在block里调用p_doPoll
就可以了。
终于有时间完结了这本书。开心O(∩_∩)O 撒花!