Reputation: 564
This may be a naive question, but I'll ask it anyway as I cannot find any documentation that clears up this issue in my head.
I'm running iOS5.1 both on device and in the simulator with Xcode45-DP4.
I have a loop that iterates over an array of a number of instances of a class. In that loop I use performSelector on the instances to start a thread that does some relatively slow network operations — pulling down data that I'd rather do in the background.
[arrayOfFriends enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Friend *f = (Friend*)obj;
iOSSLog(@"%d", idx);
[f performSelectorInBackground:@selector(showDescription) withObject:nil];
-(void)fetchTwitterStatus
{
iOSSLog(@"Trying to fetch twitterstatus %@ %@", self.hash, self.twitterUserName);
[mLocalTwitterUser fetchTwitterAPIUserStatusWithScreenName:twitterUserName
withCompletionHandler:^(NSArray *arrayOfStatus, NSError *error) {
if(error) {
iOSSLog(@"%@", error);
} else {
iOSSLog(@"Got twitterstatus %@ %d", self.twitterUserName, [arrayOfStatus count]);
@synchronized(items) {
[items addObjectsFromArray:arrayOfStatus];
}
}
}];
}
In my test case there are four instances. Each instance gets its selector, you know..selected. The first three definitely start but only the last actually completes as indicated by the log line that says "Got twitterstatus..." Which is weird.
I can also verify that the method the selector calls "fetchTwitterStatus"
What is the little fundamental nugget of multithreading that I'm missing here?
EDIT: here's fetchTwitterAPIUserStatusWithScreenName...quite a bit here, but effectively it's calling the Twitter API Endpoint user_timeline with a JSON response.
- (void)fetchTwitterUserStatusWithScreenName:(NSString *)screenname
excludeReplies:(BOOL)excludeReplies
withCompletionHandler:(OtterTwitterSearchHandler)completionHandler
{
self.twitterAPIStatusHandler = completionHandler;
//self.fetchTwitterUserStatusHandler = completionHandler;
NSString *urlString = [NSString stringWithFormat:@"https://api.twitter.com/1/statuses/user_timeline.json?screen_name=%@&include_rts=true&include_entities=true&exclude_replies=%@&count=50", screenname, excludeReplies?@"true":@"false"];
NSURL *url = [NSURL URLWithString:urlString];
#warning this isn't the way to do it - just checking the cache for refresh of the scroller
[[ASIDownloadCache sharedCache]removeCachedDataForURL:url];
iOSSRequest *request = [[iOSSRequest alloc] initWithURL:url
parameters:nil
requestMethod:iOSSRequestMethodGET];
NSMutableDictionary *oauthParams = [NSMutableDictionary dictionary];
[oauthParams setObject:[[Twitter sharedService] apiKey] forKey:kASIOAuthConsumerKey];
[oauthParams setObject:[[Twitter sharedService] apiSecret] forKey:kASIOAuthConsumerSecret];
[oauthParams setObject:[self oAuthAccessToken] forKey:kASIOAuthTokenKey];
[oauthParams setObject:kASIOAuthSignatureMethodHMAC_SHA1 forKey:kASIOAuthSignatureMethodKey];
[oauthParams setObject:@"1.0" forKey:kASIOAuthVersionKey];
[oauthParams setObject:[self oAuthAccessTokenSecret] forKey:kASIOAuthTokenSecretKey];
request.oauth_params = oauthParams;
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if (error) {
if (self.twitterAPIStatusHandler) {
self.twitterAPIStatusHandler(nil, error);
self.twitterAPIStatusHandler = nil;
}
} else {
NSMutableArray *recentStatusForTwitterUser = [[NSMutableArray alloc]init];
NSArray *array = [Twitter JSONFromData:responseData];
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
TwitterStatus *twitterStatus = nil;
twitterStatus = [[TwitterStatus alloc]initWithDictionary:obj];
[recentStatusForTwitterUser addObject:twitterStatus];
}];
if (self.twitterAPIStatusHandler) {
self.twitterAPIStatusHandler(recentStatusForTwitterUser, nil);
self.twitterAPIStatusHandler = nil;
}
}
}];
}
Upvotes: 1
Views: 251
Reputation: 11805
I'd suggest using the asynchronous abstractions already provided where possible. It would be a fairly rare/unique situation where you need to deal with threads directly.
I've found treating each network-based background task as a synchronous NSOperation
on a queue works really well.
Get a new instance of NSOperationQueue
, configure it, add tasks to it, and manage the queue. The benefit of this approach is that each task can be implemented as a simple synchronous task, and the queue takes care of concurrency. Optionally you can set dependencies (this task must complete before that one).
Upvotes: 1
Reputation: 162712
What is the little fundamental nugget of multithreading that I'm missing here?
That taking non-multithreaded code and spinning off a random number of threads by performing an arbitrary method in the background is doomed to failure.
Concurrency is a design pattern that must be carefully considered from the start (or is a monumental refactoring effort).
First, you don't want to spawn a thread per network connection. Secondly, given that these are just HTTP requests, you would want to use the systems built in classes for asynchronous HTTP communications. Finally, your concurrency model must exactly specify how you are keeping all data in isolation until you hit whatever mechanism you are using to synchronize the data back into the central store.
Hard to say where that code is going off the rails without seeing more information.
Upvotes: 0