固化是由iOSSDK提供的一种保存和读取对象的机制,使用非常广泛。当应用固化某个对象时,会将该对象的所有属性存入指定的文件。当应用解固(unarchive)某个对象时,会从指定的文件读取相应的数据,然后根据数据还原对象。
为了能够固化或解固某个对象,相应对象的类必须遵守NSCoding协议,并且实现两个必需方法:encodeWithCoder:和initWithCoder:
无论编码哪种类型的数据,必须有相应的键才能存入NSCoder对象。这个键是字符串,负责标识相应的属性。按照约定,编码某个属性时要使用的键就是该属性的名称。
当应用需要编码某个对象时(encodeObject:forKey:中的第一个参数),会向该对象发送encodeWithCoder:消息。收到该消息的对象需要编码自己的属性,所以也会向这些属性发送encodeWithCoder:消息(见图181)。因此,对象的编码过程是一个递归过程:编码中的对象会再编码其他对象。
XIB文件也是基于固化机制的。当读者在Xcode中将某个视图拖曳至画布时,Xcode会创建相应的对象。保存XIB文件时,Xcode会将这些视图固化至指定的文件(UIView遵守NSCoding协议)。当应用需要载入XIB文件时,就会解固XIB文件中的视图。和普通的固化文件相比,XIB文件会略有差别,但是两者保存和载入的流程大致相同。
应用沙盒
每个iOS应用都有自己专属的应用沙盒。应用沙盒就是文件系统中的目录,但是iOS系统会将每个应用的沙盒目录与文件系统的其他部分隔离(见图183)。应用必须“待”在自己的沙盒里,并只能访问自己的沙盒。
应用沙盒
image.png
归档,请参考项目study或者网上。
iOS应用的多种状态当应用启动后,会进入激活状态(activestate),可以显示界面、接收事件并处理事件。
当应用处在激活状态时,可能会被某个系统事件打断,临时进入未激活状态(inactivestate)。这类系统事件包括收到短消息、收到推送、来电或闹钟到点等。发生系统事件时,iOS会显示相应的提示界面并遮住当前应用的部分界面。当应用处于未激活状态时,其大部分界面是可见的(iOS显示的提示界面只会遮住部分窗口),也可以执行代码,但是不会接收事件。通常情况下,应用只会在未激活状态停留很短的时间。按下位于iOS设备顶部的锁定按钮,当前处于激活状态的应用会切换至未激活状态,并且会保留未激活状态,直到设备解锁。
当用户按下主屏幕按钮(Homebutton)时,或者通过某种途径切换至另一个应用时,当前运行的应用会从激活状态切换为后台运行状态(backgroundstate)(实际上,应用会先从激活状态切换为未激活状态,停留极短的时间,然后再进入后台运行状态)。处于后台运行状态的应用仍然可以执行代码,但是其界面不再可见,也不能接收事件。默认情况下,进入后台运行状态的应用有大约10秒的时间,然后会进入挂起状态(suspendedstate)。读者在开发应用时,不能依赖这个不确定的时间差,而是应该尽快保存用户数据并释放系统资源。
处于挂起状态的应用不能执行代码,其界面也不可见,并且会释放在挂起状态下无须使用的所有资源。挂起的应用就像是进行了“低温干燥”的处理,可以在用户再次启动时快速解冻。
image.png
当iOS系统认为当前可用的内存过低时,会根据需要终止处于挂起状态的应用。当系统有足够多的空余内存时,处于挂起状态的应用可以一直保留该状态。当处于挂起状态的应用即将被系统终止时,就不会收到相应的通告,系统会直接将其从内存中移除(终止后的应用,其图标可能还会留在多任务界面中,按下图标会重新启动应用)。
通过NSData将数据写入文件
向某个NSData对象发送writeToFile:atomically:消息,可以将该对象中的数据写入指定的文件。writeToFile:atomically:的第一个实参负责指定文件路径。第二个实参atomically是一个布尔值,当atomically为YES时,NSData对象会先将数据写入某个临时文件,然后等写入操作成功后再将文件移至第一个实参所指定的路径,并覆盖已有的文件。这样,即使应用在写入文件的过程中崩溃,也不会损坏现有的数据。
需要注意的是,这种将数据写入文件的方式不是固化。虽然NSData对象自身也可以固化,但writeToFile:atomically:的工作原理是将NSData对象中的数据逐字节复制到文件中。
NSNotificationCenter和内存过低警告
当iOS设备内存不足时,系统会向运行中的应用发送一条内存过低警告通知。应用收到该通知后,应该立刻释放当前不需要使用的资源以及后期可以重新创建的对象。视图控制器在收到该通知的同时还会收到didReceiveMemoryWarning消息。
除了视图控制器,其他对象中也可能存在需要及时释放的数据。BNRImageStore对象就是如此:它可以释放当前没有显示的UIImage对象,等到需要时再从文件系统载入。
为了使视图控制器以外的对象也可以在内存不足时释放数据,必须使用通知中心(notificationcenter)。每一个iOS应用中都有一个NSNotificationCenter对象,读者可以将其视作智能的通知栏,对象可以将自己注册为某个通知的观察者(observer)
内存过低警告通知的名称是UIApplicationDidReceiveMemoryWarningNotification。凡是需要自己处理内存过低警告的对象,都可以注册并接收这个通知。
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearCache:)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
注册为通知中心的观察者
当系统发出内存过低警告通知时,注册对象会收到UIApplicationDidReceiveMemoryWarningNotification通知,并调用clearCache:方法清除缓存。
接收通知
NSNotification和NSNotificationCenter与用户看到的推送通知(push notification)或本地通知(local notification)无关,NSNotificationCenter只用来处理应用内部的对象通信。
通知允许多个对象将回调函数注册到相同的事件上。同一个通知可以有许多观察者,当通知发布后,所有观察者都会执行之前注册的回调函数(同时执行,没有先后顺序)。如果多个对象需要监听同一个事件,则通知是最好的解决方案。例如,许多对象都需要在设备方向发生变化时执行一系列操作,因此该事件发生时系统会发布名为UIDeviceOrientationDidChangeNotification的通知。
模型视图控制器存储设计模式: Model-View-Controller-Store
标准的模型视图控制器设计模式要求控制对象负责模型对象的保存和读取。但在实际情况中,这样做的效果并不是很好。控制对象的主要任务是处理模型对象和视图对象之间的交互,如果还要负责实现所有的存取细节,则可能会“不堪重负”。为此,可以将模型对象的存取逻辑移入另一类对象:存储对象。
通过存储对象所提供的方法,控制对象可以获取或保存模型对象。保存和读取模型对象的实现细节全部由存储对象负责。以本章中的Homepwner为例,存储对象(BNRItemStore对象)会通过某个指定的文件来创建和保存BNRItem对象。除了文件,存储对象也可以使用数据库、Web服务或其他途径来为控制对象创建模型对象。
除了能够简化控制器类,这种设计模式的另一个好处是:不用修改控制对象或应用的其他部分,就能修改存储对象的工作方式。这种修改可以很简单,例如修改数据的目录结构;也可以很复杂,例如修改数据的格式。因此,无论某个应用有多少个需要存取数据的控制对象,都只需要修改相应的存储对象即可。
除了self,还有一个名为_cmd的隐式变量,它是当前方法的选择器。NSStringFromSelector函数可以根据指定的选择器生成相应的字符串。
文件系统的读取和写入
是在编写iOS应用时,标准I/O函数并不常用。这是因为还有其他的途径可以读/写二进制数据或文本数据,而且更方便。如果要读/写二进制数据,则可以使用NSData。如果要读/写文本数据,则可以分别使用NSString的实例方法initWithContentsOfFile:和writeToFile:atomically:encoding:error:
应用在执行方法时,可能会因为各种原因而导致失败。例如,将数据写入文件时会因为路径无效而失败,也可能会因为没有足够的权限而失败。NSError对象的作用是保存失败的原因。向NSError对象发送localizedDescription消息,可以得到相应错误的描述信息。因为这些信息是给人看的(humanreadable),所以可以向用户显示或通过控制台输出。
通常情况下,只有当某个方法在执行代码时发生了错误,才有必要创建NSError对象。也就是说,负责创建NSError对象的应该是被调用的方法,而不是调用方。为了能让调用方得到被调用的方法所创建的NSError对象,需要将指针变量的地址(&err)作为实参传给相应的方法。假设有某个方法(方法A),该方法的某个实参可以返回一个NSError对象,那么在调用方法A前,需要先创建一个类型为NSError*的指针变量(局域变量)。注意,这里不需要创建NSError对象,因为这是方法A的任务。然后将这个局部变量的地址(&err)传给可能会出错的方法A。如果方法A在执行代码时发生了错误,就会创建一个NSError对象,并将新创建的对象的地址赋给传入的指针。如果调用方不关心NSError对象,则可以传入nil。
很多语言将所有非预期(unexpected)错误作为异常抛出,但是ObjectiveC的异常只用来处理程序错误。当异常抛出时,详细信息都封装在NSException对象中。这些信息主要用来帮助程序员调试代码,例如“试图在只有两个对象的数组中访问第七个对象。”NSException中还包括方法调用栈信息,指明了抛出异常的代码位置。
NSException和NSError的使用场景不同。如果需要指出程序员的编码错误,则应该使用NSException。例如,一个方法只能接受奇数作为参数,但是程序员在调用该方法时传入了偶数,这时应该抛出异常,以方便程序员解决代码错误。相反,对于预期(expected)错误,如用户错误和设备环境错误,应该使用NSError。例如,一个方法需要读取用户照片,但是没有访问用户相册的权限,这时应该向方法调用者返回一个NSError对象,指出不能执行本次操作的原因。
和NSString类似,NSDictionary和NSArray也有writeToFile:和initWithContentsOfFile:。只有当collection对象包含可序列化(propertylistserializable)对象时,才能通过writeToFile:这类方法将数据存入文件。可序列化对象包括NSString、NSNumber、NSDate、NSData、NSArray和NSDictionary。NSArray对象或NSDictionary对象这类文件的写入方法生成的都是XML格式的property list文件。
几乎所有的操作系统都能读取XML格式的propertylist文件,所以使用这种格式存储数据会很方便。很多Web服务程序会采用这种格式的文件作为输入和输出。
iOS应用可以在运行时载入应用程序包中的文件。要获得应用程序包中的某个文件的全路径,需要先得到代表应用程序包的NSBundle对象,然后通过该对象得到某个文件的全路径,代码如下:
//获取代表应用程序包的NSBundle对象
NSBundle* applicationBundle=[NSBundlemainBundle];
//通过NSBundle对象,获得包内名为myImage.png的文件的全路径
NSString* path=[applicationBundle pathForResource:@“myImage” ofType:@“png”];
网友评论