Reputation: 196
I have a question about blocks and synchronicity in my app. Here is the scenario:
I have a NetworkManager which uses AFHTTPRequestOperationManager (AFNetworking 2.0) to make calls to the server. I have a ContentStore class singleton, which provides content for the app. Any class in the app can ask the content store for content, and it passes a block to receive that content. If the ContentStore has the content in memory or in an archive it passes it to the block from the class asking for content. If it doesn't it makes a request to the NetworkManager, and passes a block to the NetworkManager and this block calls the block from the original class when the content arrives from the server. So far this is all good and works well.
The issue is sometimes a class can call the ContentStore for content when a request has already been made for that content, but a reply hasn't been received yet. In this case two requests go to the server for the same content, which doesn't actually cause a problem, but its inefficient.
I came up with a system that does the following: if there is a request is underway to the server for content, the ContentStore saves all the blocks passed to it in an array, when the reply is received from the server the ContentStore loops through the array of blocks and passes the content to each block.
This works well, but I wonder if there is a possible glitch. When a class calls the ContentStore for content and a request is underway, is it possible for a block to be added to the array while the downloadCompletionBlock from the code below is looping through that same array?
Thanks to anyone who takes the time to understand and answer my question. If you need more information please let me know.
@interface ContentStore()
@property (nonatomic, strong) NSDictionary *downloadDictionary;
@property (nonatomic, strong) NSMutableArray *downloadBlocksForRequest;
@property (nonatomic, strong) NetworkManger *networkManager;
@end
@implementation ContentStore
- (void) downloadWithCompletionBlock:(void (^)(NSDictionary *))completionBlock
{
if (_downloadDictionary) {
completionBlock(_downloadDictionary);
return;
} else {
_downloadDictionary = [NSKeyedUnarchiver unarchiveObjectWithFile:[self downloadArchivePath]];
if (_downloadDictionary) {
completionBlock(_downloadDictionary);
return;
} else if (_downloadBlocksForRequest) {
[_downloadBlocksForRequest addObject:completionBlock];
return;
} else {
_downloadBlocksForRequest = [[NSMutableArray alloc]init];
[_downloadBlocksForRequest addObject:completionBlock];
}
}
void (^downloadCompletionBlock)(NSDictionary *) = ^(NSDictionary *downloadDictionary)
{
// do other work necessary
[NSKeyedArchiver archiveRootObject:downloadDictionary toFile:[self downloadArchivePath]];
[self setDownloadDictionary:downloadDictionary];
for (int i=0; i<_facesBlocksForRequest.count; i++) {
void (^complBlock)(NSDictionary *) = [_facesBlocksForRequest objectAtIndex:i];
complBlock(downloadDictionary);
}
_downloadBlocksForRequest = nil;
};
[_networkManager downloadWithCompletionBlock:downloadCompletionBlock];
}
Upvotes: 2
Views: 147
Reputation: 122439
One solution is what BlackRider said, use some kind of cross-thread synchronization to protect accesses to the shared array.
The other solution is to do both operations on the array (adding to the array and handling received content) on the same thread (or serial queue). That would probably require that the "ask for content" operation dispatch to this thread (or queue). This should be no problem in your case, since the method is already asynchronous.
Upvotes: 0
Reputation: 17622
NSMutableArray
, like most (if not all) mutable containers in iOS, is not thread-safe. So your concern is justified, and this code might introduce a very hard-to-catch bug. You might want to synchronize access to it by doing
@synchronized(_downloadBlocksForRequest) {
// enumerate _downloadBlocksForRequest, or add an object to it, etc.
}
Upvotes: 1