I have a large legacy UIKit project in Objective C, which I need to rewrite for AppKit in Swift. The part I struggling the most is converting UIDocument to NSDocument. A file that UIDocument represents is a container and this adds another level of complexity for me. Here's the reading and writing code:
#pragma mark - writing
- (void)encodeObject:(id<NSCoding>)object toWrappers:(NSMutableDictionary *)wrappers preferredFilename:(NSString *)preferredFilename {
@autoreleasepool {
NSKeyedArchiver * archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:false];
[archiver encodeObject:object forKey:@"data"];
[archiver finishEncoding];
NSData *data = [archiver encodedData];
NSFileWrapper * wrapper = [[NSFileWrapper alloc] initRegularFileWithContents:data];
[wrappers setObject:wrapper forKey:preferredFilename];
}
}
- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
if (self.backupData == nil) {
return nil;
}
NSMutableDictionary * wrappers = [NSMutableDictionary dictionary];
[self encodeObject:self.backupData toWrappers:wrappers preferredFilename:@"backup.data"];
NSFileWrapper * fileWrapper = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:wrappers];
return fileWrapper;
}
#pragma mark - reading
- (id)decodeObjectFromWrapperWithPreferredFilename:(NSString *)preferredFilename {
NSFileWrapper * fileWrapper = [self.fileWrapper.fileWrappers objectForKey:preferredFilename];
if (!fileWrapper) {
NSLog(@"Unexpected error in BackupDocument -decodeObjectFromWrapperWithPreferredFilename: Couldn't find %@ in file wrapper!", preferredFilename);
return nil;
}
NSData * data = [fileWrapper regularFileContents];
NSKeyedUnarchiver * unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:nil];
unarchiver.requiresSecureCoding = NO;
return [unarchiver decodeObjectForKey:@"data"];
}
- (BNGBackupData *)backupData {
if (_backupData == nil) {
if (self.fileWrapper != nil) {
self.backupData = [self decodeObjectFromWrapperWithPreferredFilename:DATA_FILENAME];
} else {
self.backupData = [[BNGBackupData alloc] init];
}
}
return _backupData;
}
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
self.fileWrapper = (NSFileWrapper *) contents;
// The rest will be lazy loaded...
self.backupData = nil;
return YES;
}
I honestly don’t quite understand how it works, especially all of these @autoreleasepool operators and id return types instead of more clear type notation in Swift, so after some attempts I tried to do it light way, just renaming UIDocument to NSDocument and extending it with required methods calling existing Objective C methods from it, but didn’t succeed too.
extension BackupDocument {
open override func data(ofType typeName: String) throws -> Data {
if let data = decodeObjectFromWrapper(withPreferredFilename: "backup.data") as? Data {
return data
} else {
Swift.print("\n\nNo Data esquired from decodeObjectFromWrapper(withPreferredFilename:\n\n")
return Data()
}
// return self.data.
}
open override nonisolated func read(from data: Data, ofType typeName: String) throws {
DispatchQueue.main.async {
let wrappers = NSMutableDictionary()
self.encodeObject(self.data, toWrappers: wrappers, preferredFilename: "backup.data")
}
}
}
Can anybody explain the right way implementing NSDocument for this type of files?