当iOS设备内存不足时,系统会向运行中的应用发送一条内存过低警告通知。应用收到该通知后,应该立刻释放当前不需要使用的资源以及后期可以重新创建的对象。视图控制器在收到该通知的同时还会收到didReceiveMemoryWarning消息。
除了视图控制器,其他对象中也可能存在需要及时释放的数据。BNRImageStore对象就是如此:它可以释放当前没有显示的UIImage对象,等到需要时再从文件系统载入。
为了使视图控制器以外的对象也可以在内存不足时释放数据,必须使用通知中心(notification center)。每一个iOS应用中都有一个NSNotificationCenter对象,读者可以将其视作智能的通知栏,对象可以将自己注册为某个通知的观察者(observer),例如:“如果有人找到了我丢失的狗,请立刻通知我。”当另一个对象发送了一条通知:“我找到了一条狗。”通知中心就会将这条通知发送给对应的观察者。
内存过低警告通知的名称是UIApplicationDidReceiveMemoryWarning- Notification。凡是需要自己处理内存过低警告的对象,都可以注册并接收这个通知。编辑BNRImageStore.m中的initPrivate方法,将BNRImageStore对象注册为UIApplicationDidReceiveMemoryWarningNotification通知的观察者,代码如下:
- (instancetype)initPrivate
{
self = [super init];
if (self) {
_dictionary = [[NSMutableDictionary alloc] init];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:@selector(clearCache:)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
return self;
}
现在BNRImageStore对象已经注册为通知中心的观察者了(见图18-8)。
图18-8 注册为通知中心的观察者
当系统发出内存过低警告通知时,BNRImageStore对象会收到UIApplicationDid- ReceiveMemoryWarningNotification通知,并调用clearCache:方法清除缓存(见图18-9)。
图18-9 接收通知
在BNRImageStore.m中实现clearCache:,移除BNRImageStore对象所包含的所有UIImage对象。
- (void)clearCache:(NSNotification *)note
{
NSLog(@“flushing %d images out of the cache”, [self.dictionary count]);
[self.dictionary removeAllObjects];
}
当字典对象移除某个对象时,被移除对象的拥有方个数会减1。因此,当BNRImageStore对象移除其包含的所有UIImage对象时,这些UIImage对象的拥有方个数都会减1。减1后,没有拥有方的UIImage对象会被释放,并在需要时由BNRImageStore对象根据相应的文件再次创建。有些UIImage对象可能还会有其他拥有方,这些UIImage对象不会被释放。例如,BNRDetailViewController对象的imageView会保留某个UIImage对象,那么该对象不会被释放。当imageView放弃之前保留的UIImage对象时(例如UINavigationController对象弹出了BNRDetailViewController对象,或者应用将新的UIImage对象赋给了imageView),相应的UIImage对象才会被释放。
在模拟器中构建并运行应用,加入一些图片,然后在Hardware(硬件)菜单中选择Simulate Memory Warning(模拟内存警告),控制台中会输出日志信息,提示缓存图片已经被清除。
深入学习NSNotificationCenter
通知与委托、目标-动作相同,也是回调的一种形式。它们的区别是,委托和目标-动作需要对象直接向自己的委托或目标发送消息,而通知使用一位中介者:NSNotificationCenter。
Objective-C使用NSNotification表示通知,每个NSNotification对象都具有名称(NSNotificationCenter根据该名称检索此通知的所有观察者)、来源对象(发布该通知的对象)和可选的userInfo字典(来源对象需要告诉观察者的额外信息)。例如,当系统状态栏的frame发生变化后,UIApplication会发布名为UIApplicationDidChangeStatus- BarFrameNotification的通知,并带有一个存储状态栏的新frame的userInfo字典。如果代码中收到了该通知,则可以使用以下方式获取状态栏的新frame:
- (void)statusBarMovedOrResized:(NSNotification *)note
{
NSDictionary *userInfo = [note userInfo];
NSValue *wrappedRect = userInfo[UIApplicationStatusBarFrameUserInfoKey];
CGRect newFrame = [wrappedRect CGRectValue];
……使用newFrame……
}
需要注意的是,如果需要向NSDictionary中存储CGRect结构,必须先将其包装为NSValue对象——NSDictionary中只能存储Objective-C对象。
那么,如何了解userInfo字典中存储了哪些内容呢?类参考手册中会详细说明类发布的通知,大部分通知的说明都是:“This notification does not contain a userInfo dictionary.(该通知不会附带userInfo字典。)”对于带有userInfo字典的通知,文档中会列出字典中的所有键及其对应值的含义。
addObserver:selector:name:object:方法的最后一个参数通常是nil——假设观察的通知名称为“Fire!”,传入nil表示接受所有对象发布的“Fire!”通知。如果为该参数传入某个对象,那么观察者只会接受该对象发布的“Fire!”通知,其他对象发布的“Fire!”通知则会被忽略。
通知允许多个对象将回调函数注册到相同的事件上。同一个通知可以有许多观察者,当通知发布后,所有观察者都会执行之前注册的回调函数(同时执行,没有先后顺序)。如果多个对象需要监听同一个事件,则通知是最好的解决方案。例如,许多对象都需要在设备方向发生变化时执行一系列操作,因此该事件发生时系统会发布名为UIDeviceOrientationDid- ChangeNotification的通知。
最后需要说明的是,NSNotification和NSNotificationCenter与用户看到的推送通知(push notification)或本地通知(local notification)无关,NSNotificationCenter只用来处理应用内部的对象通信。