Ekulelu's Blog

UIDocument简介

UIDocument是iOS的一个负责文件读取和存储的类,对于文档类的应用,使用这个类可以省去存储和读取时候线程的一些麻烦事情。尤其是和NSUndoManager配合之后,可以做到自动存储,很方便。

UIDocument里面只是一套存储和读取的逻辑框架,至于怎么读取和怎么存储,都是需要你自己去定义的。所以,一般使用UIDocument的做法就是继承它,然后实现它存储和读取相关的方法。
首先你需要创建它,这个url指定的可以是一个文件或文件夹

1
- (instancetype)initWithFileURL:(NSURL *)url;

基本的读写操作涉及到的方法有四个:

1
2
3
4
- (void)openWithCompletionHandler:(void (^ __nullable)(BOOL success))completionHandler __TVOS_PROHIBITED;
- (void)closeWithCompletionHandler:(void (^ __nullable)(BOOL success))completionHandler __TVOS_PROHIBITED;
- (BOOL)loadFromContents:(id)contents ofType:(nullable NSString *)typeName error:(NSError **)outError __TVOS_PROHIBITED;
- (nullable id)contentsForType:(NSString *)typeName error:(NSError **)outError __TVOS_PROHIBITED;

前面两个方法就是打开url对应的文件,和关闭打开的文件。
当调用文件打开方法后,它就会调用loadFromContents: ofType: error:方法,这个方法默认会读取url对应的文件,然后放到contents里面,这里面你需要自定义怎么去解析这个contents。
如果你url对应的是文件夹,那么contents里面是NSFileWrapper对象,用wrapper.fileWrappers得到一个字典,字典的key是文件或文件夹的名字,value仍然是个NSFileWrapper对象。如果NSFileWrapper对应的是一个文件,你可以使用wrap.regularFileContents得到这个文件的data。

1
2
3
4
NSFileWrapper *wrapper = (NSFileWrapper*)contents;
NSDictionary *fileWrappers = wrapper.fileWrappers;
NSFileWrapper *imgWrap = [fileWrappers objectForKey:@"imageFileName"];
page.image = [UIImage imageWithData:imgWrap.regularFileContents];

当系统需要存储的时候,它会调用contentsForType:error:方法,你需要在这个方法里面封装好你需要保存的数据,改数据会写到创建时候的url下面,如果你url是个文件夹,那么你用NSFileWrapper去封装你的保存的数据,如下

1
2
3
4
5
NSFileWrapper *wrapper =[[NSFileWrapper alloc]initDirectoryWithFileWrappers:@{}];
NSData *imgData = [self getImageData:page.image];
NSFileWrapper *imgWrap = [[NSFileWrapper alloc]initRegularFileWithContents:imgData];
[imgWrap setPreferredFilename:page.imageFileName];
[wrapper addFileWrapper:imgWrap];

那么系统怎么知道什么时候需要存储呢?一个是使用下面的方法。

1
- (void)updateChangeCount:(UIDocumentChangeKind)change

另外一个是使用undoManager,如果你设置了document的undoManager,那么对undoManager进行了缓存操作后,它就会自动通知document有了改变。这个操作会影响

1
- (BOOL)hasUnsavedChanges;

之后系统会自动调用autosaveWithCompletionHandler:,这个方法最终会调用到contentsForType:error:,然后进行文件写入。

1
- (void)autosaveWithCompletionHandler:(void (^ __nullable)(BOOL success))completionHandler;

注意是这个方法文档说不建议子类去调用,除非你想自定义自动存储的一些行为,如果你需要自己调用,请在主线程调用,否则不会进行操作。

在实际的使用过程中遇到几个坑:

1、如果使用home键后,再点击程序回到程序界面,那么会再次触发加载数据的流程。这里可能会导致一些业务问题。

2、在某些iPhone6或iPhoneSE上面偶现调用了contentsForType:error:方法后出现卡死的现象。需要重新启动手机才能恢复正常。后面自己按照UIDocument的这套流程写了一个Saver类,实现了基本的读写功能,起码能保证业务逻辑的正常。