I have an array that I want to enumerate using blocks concurrently. However, I'm having trouble making this thread safe. I am new to using blocks and locks, so I am hoping someone may be able to push me in the right direction for preventing this from crashing.
The point of this function is to loop over a number of files and folders.
- if folder, create a new dictionary item
- if file, add as child to folder key
- if folder, then recursively move into the folder to iterate over all files and folders and add to dictionary
This builds a dictionary structure of the file system. However, it is slow and I would like to do this concurrently.
My main function looks like this:
- (void)createDirectoryStructure:(NSString *)LR withArray:(NSMutableArray *)myArray {
__block NSFileManager *fm = [NSFileManager defaultManager];
__block BOOL isDir=NO;
__block NSString *local;
__block NSString *myKey;
[myArray enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *url=obj;
if ([LR isEqualToString:@"L"]) {
myKey = [url stringByDeletingLastPathComponent];
local = [self.rootdirL.path stringByAppendingString:url];
[fm fileExistsAtPath:local isDirectory:&isDir];
if (!isDir)
[self updateStructureWithKey:myKey andURL:url isDir:isDir forLR:LR];
}
}];
}
This function calls updateStructureWithKey
that looks like this:
-(void) updateStructureWithKey:(NSString*)myKey andURL:(NSString*)url isDir:(BOOL)isDir forLR:(NSString*)LR
{
NSArray *components=[myKey pathComponents];
NSString *addPath=@"";
NSUInteger counter=0;
for (NSString *component in components){
NSString *createDir=[addPath stringByAppendingPathComponent:component];
addPath=createDir;
counter+=1;
if ((unsigned long)counter<(unsigned long)components.count){
NSString *addchild = [createDir stringByAppendingPathComponent:[components objectAtIndex:(unsigned long)counter]];
[self addDictionaryItem:createDir withURL:addchild withLR:LR isDir:TRUE];
}
}
[self addDictionaryItem:myKey withURL:url withLR:LR isDir:isDir];
}
Which again calls addDictionaryItem
:
-(void) addDictionaryItem:(NSString *) mykey withURL:(NSString *)myurl withLR:(NSString *)LR isDir:(BOOL) myIsDir
{
if ([self.dict objectForKey:mykey]) {
NSMutableArray *myarray = [[self.dict objectForKey:mykey] objectForKey:@"myarray"];
NSMutableArray *myarrayLR = [[self.dict objectForKey:mykey] objectForKey:@"LR"];
NSMutableArray *myarrayIsDir = [[self.dict objectForKey:mykey] objectForKey:@"isdir"];
NSMutableDictionary *attrDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:myarrayLR, @"LR",
myarray, @"myarray", myarrayIsDir, @"isdir",
nil];
if (![myarray containsObject:myurl]){
[myarray addObject:myurl];
[myarrayLR addObject:LR];
[myarrayIsDir addObject:[NSNumber numberWithBool:myIsDir]];
if ([theLock tryLock]) {
[self.dict setObject:attrDict forKey:mykey];
[theLock unlock];
}
}
else {
NSMutableArray *arrayOfFiles = [[NSMutableArray alloc] init];
[arrayOfFiles addObject:myurl];
NSMutableArray *arrayOfLR = [[NSMutableArray alloc] init];
[arrayOfLR addObject:LR];
NSMutableArray *arrayIsDir = [[NSMutableArray alloc] init];
[arrayIsDir addObject:[NSNumber numberWithBool:myIsDir]];
NSMutableDictionary *attrDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:arrayOfLR, @"LR",
arrayOfFiles, @"myarray", arrayIsDir, @"isdir",
nil];
if ([theLock tryLock]) {
[self.dict setObject:attrDict forKey:mykey];
[theLock unlock];
}
}
}
}
In the final function, I use an NSLock
that is initialized as a property earlier. I thought that since the functions updateStructureWithKey
and addDictionaryItem
are defined inline of the block, I would not have to define their variables as __block
, but since I am changing a property, I would have to use NSLock to make sure the change is thread safe.
Still, when I run this code, it crashes with "EXC_BAD_ACCESS (code=EXC_I386_GPFLT)", but the position where it crashes varies.
A simplified test version of this problem is available as a Xcode project on GitHub.
I appreciate suggestions and hints from anyone who can help me understand blocks and thread safety.