I am having trouble with some batch document processing in iOS and was hoping I could get some help here. The process I'm trying to implement uses an input directory on iCloud drive, pulls all of the documents there, and adds one record into the iCloud database for each. Right now, the 'add iCloud' code isn't in here but it's easy enough to do if I pass the right pointers around. I'd like to give the user a progress bar so they can check on processing and get an idea how well it's going along. This a technique I often use in macOS and just assumed it would work ok in iOS but I'm having difficulties.
Now the strange part about this is that if I remove the code that runs the loop on a background thread and run the loader class on the main thread, the process more or less works. The progress bar doesn't work of course (it never gets the main thread back) but the document open code is called the correct number of times, and processes are spawned to open the documents and all of that. When I run it as is, I get an abend that sort of seems to indicate that a allocate or deallocate process is broken. Don't actually get a crash, only a break that constantly loops.
The basic process is as follows:
User selects load option and a target directory from a screen and presses 'go'. As part of the segue, the loader process is instantiated and waits for a bit to begin (there are better ways to do this but they can wait until I get the basic process built)
User is segued to another screen which displays a progress bar. Here is the code for it:
//
// LoadPopover.m
// TestFindIt
//
// Created by Joe Ruth on 7/16/16.
// Copyright © 2016 Joe Ruth. All rights reserved.
//
#import "LoadPopover.h"
@interface LoadPopover ()
@end
@implementation LoadPopover;
@synthesize localSecondViewController, localLoadCloudDBClass, loadProgressBar, screenReloadDirectory;
@synthesize loadedDocuments;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)pressedDoneButton:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
-(void)viewDidAppear:(BOOL)animated {
[localLoadCloudDBClass addObserver:self
forKeyPath:@"iterationcounter"
options:NSKeyValueObservingOptionNew
context:NULL];
[localLoadCloudDBClass addObserver:self
forKeyPath:@"totalcounter"
options:NSKeyValueObservingOptionNew
context:NULL];
loadProgressBar.progress = 0.00;
self.loadedDocuments = [[NSMutableArray alloc] init];
localLoadCloudDBClass.loadedDocuments = self.loadedDocuments;
[localLoadCloudDBClass LoadCloudDBClassRun:self];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)objectchange:(NSDictionary *)changecontext:(void *)context
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
int iterationcounter;
int totalcounter;
iterationcounter = (int) localLoadCloudDBClass.iterationcounter;
totalcounter = (int) localLoadCloudDBClass.totalcounter;
if (totalcounter != 0) {
loadProgressBar.progress = (float) iterationcounter / (float) totalcounter;}
else {
loadProgressBar.progress = 0.00;}
}];
}
@end
The segue screen establishes observer for some counter properties for the loader class.
The loader is pretty simple. Right now it's just pages through the iCloud database and starts a document open process for each record in the database. At some point, I'm going to need to get a little fancier with the document open process but right now it's proof of concept.
//
// LoadCloudDBClass.m
// TestFindIt
//
// Created by Joe Ruth on 7/15/16.
// Copyright © 2016 Joe Ruth. All rights reserved.
//
#import "LoadCloudDBClass.h"
#import "Item.h"
#import "SaveObject.h"
#import "SaveObjectUIDocument.h"
@implementation LoadCloudDBClass
@synthesize localFindItDataController, screenReloadDirectory, localLoadPopover, iterationcounter, totalcounter;
@synthesize loadedDocuments;
- (void) LoadCloudDBClassRun:(NSObject *) parameterTestFinditCaller {
dispatch_queue_t backgroundQueue = dispatch_queue_create("Background Queue",NULL);
dispatch_async(backgroundQueue, ^{
NSError *error;
unsigned long check_count;
unsigned long total_count;
NSURL *fileURL;
int stopper;
__block BOOL blockSuccess = NO;
__block BOOL blockCalled = NO;
[self setIterationcounter:(NSInteger) 0];
[self setTotalcounter:(NSInteger) 0];
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *rootURL = [fm URLForUbiquityContainerIdentifier:nil];
NSURL *newFolderTemp = [rootURL URLByAppendingPathComponent:@"Documents" isDirectory:YES];
NSURL *newFolder = [newFolderTemp URLByAppendingPathComponent:screenReloadDirectory isDirectory:YES];
NSNumber *isDirectory;
if (![newFolder getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {return;}
NSArray *theFiles = [fm contentsOfDirectoryAtURL:newFolder
includingPropertiesForKeys:[NSArray arrayWithObject:NSURLNameKey]
options:NSDirectoryEnumerationSkipsHiddenFiles
error:nil];
[self setTotalcounter:(NSInteger) [theFiles count]];
total_count = [theFiles count];
check_count = 0;
while ((check_count < total_count) && (total_count != 0)) {
fileURL = [theFiles objectAtIndex:check_count];
SaveObjectUIDocument *tempdoc = [[SaveObjectUIDocument alloc] initWithFileURL:fileURL];
//tempdoc.loadedDocuments = loadedDocuments;
long set_iterationcounter = iterationcounter + 1;
[self setIterationcounter:(NSInteger) set_iterationcounter];
[tempdoc openWithCompletionHandler:^(BOOL success) {
NSLog (@" try Open");
if (success) {
blockSuccess = success;
blockCalled = YES;
//[self.loadedDocuments addObject:tempdoc];
NSLog(@"Opened");}
else
{NSLog(@"Not Opened");}
}];
blockCalled = NO;
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:10];
while (!blockCalled && [loopUntil timeIntervalSinceNow] > 0)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:loopUntil];
}
check_count++;
}
stopper=5;
});
}
@end
The document coding is as follows:
Here is the .h file
//
// SaveObject.h
// TestFindIt
//
// Created by Joe Ruth on 1/10/16.
// Copyright © 2016 Joe Ruth. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface SaveObject : NSObject <NSCoding> {
NSData *_sxmlData;
}
- (id)initWithData:(NSData *)in_xmlData;
@property (nonatomic, copy) NSData *sxmlData;
@end
Here is the accompanying .m file
//
// SaveObject.m
// TestFindIt
//
// Created by Joe Ruth on 1/10/16.
// Copyright © 2016 Joe Ruth. All rights reserved.
//
#import "SaveObject.h"
@implementation SaveObject
@synthesize sxmlData = _sxmlData;
- (id)initWithData:(NSData *)in_xmlData {
if ((self = [super init])) {
self.sxmlData = in_xmlData;
}
return self;
}
- (id)init {
return [self initWithData:nil];
}
#pragma mark NSCoding
#define kVersionKey @"Version"
#define kDataKey @"Data"
#define kSaveObjectXMLData @"SaveObjectXMLData"
//- (void)encodeWithCoder:(NSCoder *)encoder {
// [encoder encodeInt:1 forKey:kVersionKey];
// [encoder encodeObject:self.sxmlData forKey:kDataKey];
//}
//- (id)initWithCoder:(NSCoder *)decoder {
// [decoder decodeIntForKey:kVersionKey];
// NSData * outxmlData = [decoder decodeObjectForKey:kDataKey];
// NSLog(@">>>>>>>>>>>>>>>>>>> %@",outxmlData);
// return [self initWithData:outxmlData];
//}
#pragma mark -
#pragma mark NSCoding Protocol
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.sxmlData forKey:kSaveObjectXMLData];
}
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self != nil) {
self.sxmlData = [coder decodeObjectForKey:kSaveObjectXMLData];
}
return self;
}
@end
Here is the high level document coding .h file
//
// SaveObjectUIDocument.h
// TestFindIt
//
// Created by Joe Ruth on 1/10/16.
// Copyright © 2016 Joe Ruth. All rights reserved.
//
#import <UIKit/UIKit.h>
@class SaveObject;
@interface SaveObjectUIDocument : UIDocument {
SaveObject *_SaveObject;
}
@property (strong, nonatomic) SaveObject *SaveObject;
@end
And the accompanying .m file. Later the iCloud add code would go here and I'd need to make sure the open MOC object address was passed around properly.
//
// Created by Joe Ruth on 1/10/16.
// Copyright © 2016 Joe Ruth. All rights reserved.
//
#import "SaveObjectUIDocument.h"
#import "SaveObject.h"
#define kArchiveKey @"SaveObjectXMLData"
@interface SaveObjectUIDocument ()
@property (nonatomic, strong) NSData * data;
@end
@implementation SaveObjectUIDocument;
@synthesize SaveObject = _SaveObject;
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
if ([contents length] > 0) {
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:contents];
self.SaveObject = [unarchiver decodeObjectForKey:kArchiveKey];
[unarchiver finishDecoding];}
else {
self.SaveObject = [[SaveObject alloc] initWithData:nil];}
return YES;
}
- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:self.SaveObject forKey:kArchiveKey];
[archiver finishEncoding];
return data;
}
- (void)handleError:(NSError *)error userInteractionPermitted:(BOOL)userInteractionPermitted {
NSLog(@"Error: %@ userInfo=%@", error.localizedDescription, error.userInfo);
[super handleError:error userInteractionPermitted:userInteractionPermitted];
}
@end