I am using recursive blocks to fetch tweets via the Twitter API and do encounter a bad memory leak: 50 recursive fetches lead to a memory footprint > 500 MB.
This method is called from inside my view controller and controls the lists of which the timelines should be fetched:
- (void)fetchTweetsForUsersInList:(List*)list withCompletionHandler:
(simpleCompletionBlock)completionBlock
{
[self.managedDocument.managedObjectContext performBlock:^{
NSFetchRequest *request = [NSFetchRequest
fetchRequestWithEntityName:#"User"];
request.predicate = predicate;
request.sortDescriptors = sortDescriptors;
request.shouldRefreshRefetchedObjects = YES;
NSError *error;
NSArray *users = [self.managedDocument.managedObjectContext
executeFetchRequest:request error:&error];
__weak __block void (^weakCompletion)(NSError*);
__block void (^completion)(NSError*);
__block NSUInteger i = 0;
__block User *user = users[i];
unsigned long long since_id = ....;
unsigned long long maxId = ....;
completion = ^(NSError *error) {
if (i == users.count - 1 ) {
completionBlock(nil);
}
else {
i++;
user = users[i];
[self fetchTimelineOfUser:user
sinceId:since_id maxId:maxId
withCompletionHandler:weakCompletion];
}
};
weakCompletion = completion;
[self fetchTimelineOfUser:user
sinceId:since_id maxId:maxId
withCompletionHandler:completion];
}];
}
And this is fetchTimelineOfUser which is called recursively:
- (void)fetchTimelineOfUser:(User*)user
sinceId:(unsigned long long)since_id
maxId:(unsigned long long)maxId
withCompletionHandler:(simpleCompletionBlock)completionBlock
{
TwitterRequest *twitterRequest = self.twitterRequest;
NSUInteger count = 2000;
__weak __block void (^weakCompletion)(NSError*, unsigned long long, NSArray*);
__block void (^completion)(NSError*, unsigned long long, NSArray*);
completion = ^(NSError *error, unsigned long long minId, NSArray *tweets) {
[self.managedDocument.managedObjectContext performBlockAndWait:^{
[tweets enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// Create new Tweet object
Tweet *tweet = [NSEntityDescription
insertNewObjectForEntityForName:#"Tweet"
inManagedObjectContext:self.managedDocument.managedObjectContext];
tweet.text = [obj valueForKey:#"text"];
NSError *saveError;
[self.managedDocument.managedObjectContext save:&saveError];
// (1) If no more tweets above since_id or the minimum
// tweet id has reached an existing tweet id, recursion is complete:
if (tweets.count == 0 || minId - 1 == since_id) {
NSError *saveError;
[self.managedDocument.managedObjectContext
save:&saveError];
[self.managedDocument.managedObjectContext
performBlockAndWait:^{
[self.managedDocument saveToURL:self.managedDocument.fileURL
forSaveOperation:UIDocumentSaveForOverwriting
completionHandler:^(BOOL success) {
completionBlock(nil);
return;
}];
}];
}
// (2) Otherwise RECURSIVELY call ...
else {
[twitterRequest
fetchTimelineForUser:user.screen_name
sinceId:since_id maxId:minId-1
count:count
withCompletion:weakCompletion];
}
}];
}
};
weakCompletion = completion;
[twitterRequest fetchTimelineForUser:user.screen_name
sinceId:since_id maxId:maxId
count:count
withCompletion:completion];
}
Question:
Is it possible that I have a retain cycle or other memory leak?
Do you have any idea why my memory footprint is so high?
Thank you!
Related
My app receives a json object the first time is executed (with three pin point locations); there is a mapKit (the first screen) and a TableView where the user can check those locations. The issue is that when I first launch the app, there are no pins on the map. But if I switch to the table I can see them - on the cells - and if I switch again to the map, the pins appear...I don't Know why this happens, shouldn't I see the pins right after the app launch? The Map code:
- (void)viewDidLoad {
[super viewDidLoad];
NSNotificationCenter *notification=[NSNotificationCenter defaultCenter];
[notification addObserver:self selector:#selector (receiveNotification:) name:#"notification" object:self];
_mapView.showsUserLocation=YES;
_mapView.showsBuildings=YES;
_locationManager = [[CLLocationManager alloc] init];
[_locationManager requestAlwaysAuthorization];
_mapView.delegate = self;
_locationManager.delegate=self;
}
-(void)viewDidAppear:(BOOL)animated{
[self receiveNotification:nil];
}
-(void)receiveNotification:(NSNotification*)notification{
NSArray *spots = [Spot spotType:#"users"];
NSArray *places = [Spot spotWithType:#"users"];
[_mapView addAnnotations:spots];
[_mapView addAnnotations:places];
}
And the table:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.dataSource = self;
self.detailList=#[#"Your Favourite Spots",#"Our suggestion"];
}
-(void)viewDidAppear:(BOOL)animated{
_lisbonSpots = [[Spot spotType:#"users"]mutableCopy];
_users=[[Spot spotWithType:#"users"]mutableCopy];
[self.tableView reloadData];
}
EDIT - The Spot Class
#implementation Spot
#dynamic ID;
#dynamic name;
#dynamic desc;
#dynamic type;
#dynamic phone;
#dynamic latitude;
#dynamic longitude;
+ (instancetype)spotWithName:(NSString *)name andCoord:
(CLLocationCoordinate2D)coord type:(NSString*)type desc:(NSString*)desc phone:(NSString*)phone{
NSPersistentContainer *persistenceContainer = [AppDelegate sharedDelegate].persistentContainer;
NSManagedObjectContext *context = persistenceContainer.viewContext;
Spot *spot = [NSEntityDescription insertNewObjectForEntityForName:#"Spot" inManagedObjectContext:context];
spot.name = name;
spot.latitude = coord.latitude;
spot.longitude = coord.longitude;
spot.type=type;
spot.desc=desc;
spot.phone=phone;
[[AppDelegate sharedDelegate] saveContext];
return spot;
}
+ (instancetype)spotWithDict:(NSDictionary *)dict {
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake([dict[#"latitude"] doubleValue], [dict[#"longitude"] doubleValue]);
return [Spot spotWithName:dict[#"name"] andCoord:coord type:dict[#"type"] desc:dict[#"desc"] phone:dict[#"phone"]];
}
+ (NSArray*)getSpotType:(NSString*)type withPredicate:(NSString*) pred andMessage:(NSString*)message {
NSPersistentContainer *persistenceContainer = [AppDelegate sharedDelegate].persistentContainer;
NSPredicate* predicate = [NSPredicate predicateWithFormat:pred, type];
NSManagedObjectContext *context = persistenceContainer.viewContext;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Spot"];
[request setPredicate:predicate];
NSError *error;
NSArray *result = [context executeFetchRequest:request error:&error];
if (error != nil) {
NSLog(message, [error localizedDescription]);
return nil;
}
return result;
}
+ (NSArray*)spotType:(NSString*)type {
return [Spot getSpotType:type withPredicate:#"type =%#" andMessage:#"[Spot spotType] -> %#"];
}
+ (NSArray*)spotWithType:(NSString*)type {
return [Spot getSpotType:type withPredicate:#"NOT (type = %#)" andMessage:#"[Spot spotWithType] -> %#"];
}
- (CLLocationCoordinate2D)coordinate {
return CLLocationCoordinate2DMake(self.latitude, self.longitude);
}
- (NSString *)title {
return self.name;
}
- (NSString *)description {
return [NSString stringWithFormat:#"%#", self.name];
}
#end
EDIT: The SpotService class
#implementation SpotService
+ (NSURL *)serviceURL {
return [NSURL URLWithString:#"http://training.reativ.io/ios/lisbon-spots"];
}
+ (BOOL)service:(id<SpotServiceInvoker>)invoker {
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:[SpotService serviceURL]];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error != nil) {
NSLog(#"Response: %#", response);
NSLog(#"Error: %#", error);
return;
}
NSArray *lisbonSecrets = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
if ([invoker respondsToSelector:#selector(receiveSpot:)]){
[invoker receiveSpot:lisbonSecrets];
}
for(NSDictionary *dict in lisbonSecrets) {
[Spot spotWithDict:dict];
}
});
}];
[task resume];
return YES;
}
My guess is - your Spot class retrieve data asynchronously and when you call [Spot spotType:#"users"] for the first time from viewDidAppear on your MapView there is no data retrieved yet. When you switch view controller the data appears and the everything works smoothly.
But it's better to show us your Spot class. Probably your need a completion handler or something like this to achieve expected behaviour.
Also, you call addAnnotations every time when your map appears on the screen and it means that MKMapView will add a copy of the annotations each time your call this methods. It's better to add additional checks to be sure that you do not add the same annotations more than once.
I am implementing MailCore2 to fetch user's email in my app. I have successfully fetched the email of the user, code is as follows : -
- (void)loadAccountWithUsername:(NSString *)username
password:(NSString *)password
hostname:(NSString *)hostname
oauth2Token:(NSString *)oauth2Token
{
self.imapSession = [[MCOIMAPSession alloc] init];
self.imapSession.hostname = hostname;
self.imapSession.port = 993;
self.imapSession.username = username;
self.imapSession.password = password;
if (oauth2Token != nil) {
self.imapSession.OAuth2Token = oauth2Token;
self.imapSession.authType = MCOAuthTypeXOAuth2;
}
self.imapSession.connectionType = MCOConnectionTypeTLS;
MasterViewController * __weak weakSelf = self;
self.imapSession.connectionLogger = ^(void * connectionID, MCOConnectionLogType type, NSData * data) {
#synchronized(weakSelf) {
if (type != MCOConnectionLogTypeSentPrivate) {
// NSLog(#"event logged:%p %i withData: %#", connectionID, type, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
}
};
// Reset the inbox
self.messages = nil;
self.totalNumberOfInboxMessages = -1;
self.isLoading = NO;
self.messagePreviews = [NSMutableDictionary dictionary];
[self.tableView reloadData];
NSLog(#"checking account");
self.imapCheckOp = [self.imapSession checkAccountOperation];
[self.imapCheckOp start:^(NSError *error) {
MasterViewController *strongSelf = weakSelf;
NSLog(#"finished checking account.");
if (error == nil) {
[strongSelf loadLastNMessages:NUMBER_OF_MESSAGES_TO_LOAD];
} else {
NSLog(#"error loading account: %#", error);
}
strongSelf.imapCheckOp = nil;
}];
}
- (void)loadLastNMessages:(NSUInteger)nMessages
{
self.isLoading = YES;
MCOIMAPMessagesRequestKind requestKind = (MCOIMAPMessagesRequestKind)
(MCOIMAPMessagesRequestKindHeaders | MCOIMAPMessagesRequestKindStructure |
MCOIMAPMessagesRequestKindInternalDate | MCOIMAPMessagesRequestKindHeaderSubject |
MCOIMAPMessagesRequestKindFlags);
NSString *inboxFolder = #"INBOX";
MCOIMAPFolderInfoOperation *inboxFolderInfo = [self.imapSession folderInfoOperation:inboxFolder];
[inboxFolderInfo start:^(NSError *error, MCOIMAPFolderInfo *info)
{
BOOL totalNumberOfMessagesDidChange =
self.totalNumberOfInboxMessages != [info messageCount];
self.totalNumberOfInboxMessages = [info messageCount];
NSUInteger numberOfMessagesToLoad =
MIN(self.totalNumberOfInboxMessages, nMessages);
if (numberOfMessagesToLoad == 0)
{
self.isLoading = NO;
return;
}
MCORange fetchRange;
// If total number of messages did not change since last fetch,
// assume nothing was deleted since our last fetch and just
// fetch what we don't have
if (!totalNumberOfMessagesDidChange && self.messages.count)
{
numberOfMessagesToLoad -= self.messages.count;
fetchRange =
MCORangeMake(self.totalNumberOfInboxMessages -
self.messages.count -
(numberOfMessagesToLoad - 1),
(numberOfMessagesToLoad - 1));
}
// Else just fetch the last N messages
else
{
fetchRange =
MCORangeMake(self.totalNumberOfInboxMessages -
(numberOfMessagesToLoad - 1),
(numberOfMessagesToLoad - 1));
}
self.imapMessagesFetchOp =
[self.imapSession fetchMessagesByNumberOperationWithFolder:inboxFolder
requestKind:requestKind
numbers:
[MCOIndexSet indexSetWithRange:fetchRange]];
[self.imapMessagesFetchOp setProgress:^(unsigned int progress) {
NSLog(#"Progress: %u of %u", progress, numberOfMessagesToLoad);
}];
__weak MasterViewController *weakSelf = self;
[self.imapMessagesFetchOp start:
^(NSError *error, NSArray *messages, MCOIndexSet *vanishedMessages)
{
MasterViewController *strongSelf = weakSelf;
NSLog(#"fetched all messages.");
self.isLoading = NO;
NSSortDescriptor *sort =
[NSSortDescriptor sortDescriptorWithKey:#"header.date" ascending:NO];
NSMutableArray *combinedMessages =
[NSMutableArray arrayWithArray:messages];
[combinedMessages addObjectsFromArray:strongSelf.messages];
strongSelf.messages =
[combinedMessages sortedArrayUsingDescriptors:#[sort]];
[strongSelf.tableView reloadData];
}];
}];
}
Now I want to integrate search email functionality in my app. Is there any method available in mailcore2 framework to fetch the searched email with keyword.
Already tried with the link but it was not helpful.
iOS-Is there any method available for fetching CTCoreMessage with specific key words from CTCoreAccount or CTCoreFolder in Mailcore library?
I'm implementing a logic to check whether a contact exists or not in the contacts list and based upon this result I'm inserting the contact. My progress so far:
__block NSString *strPhoneNumber = #"1093874652";
if ([CNContactStore class]) {
CNContactStore *addressBook = [[CNContactStore alloc] init];
NSArray *keysToFetch =#[CNContactPhoneNumbersKey];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSError *error = nil;
CNContactFetchRequest *fetchRequest =[[CNContactFetchRequest alloc] initWithKeysToFetch:keysToFetch];
__block BOOL isExists = NO;
[addressBook enumerateContactsWithFetchRequest:fetchRequest error:&error usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
NSArray *phoneNumbers =[[contact.phoneNumbers valueForKey:#"value"] valueForKey:#"digits"];
if ([phoneNumbers containsObject:strPhoneNumber]) {
isExists = YES;
*stop = YES;
}
else
isExists = NO;
}];
dispatch_async(dispatch_get_main_queue(), ^{
if (isExists == NO) {
//This is the method for saving the contacts. I'm not implementing here.
[self saveContactWithName:#"John Doe" withContactEmail:#"johndoe#abc.com#" withContactPhone:str];
}
});
});
}
Now, the problem is after enumerating, the code under if (isExists == NO) fires several times and saving the contact multiple times.How do I stop it? My only need is if the contact exits then don't save it otherwise save it only one time. Any help will be appreciated.
replace the below part in your code,
NSArray *phoneNumbers = [[contact.phoneNumbers valueForKey:#"value"] valueForKey:#"digits"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"self == [c] %#", strPhoneNumber];
NSArray *filtered = [phoneNumbers filteredArrayUsingPredicate:predicate];
if ([filtered count] > 0) {
isExists = YES;
*stop = YES;
}
else
isExists = NO;
}];
I've ran into a problem, I'm currently using GKLeaderboards and using it to fill a model. I'm having no problems fetching the data, my problem occurs when I go to fill the tableView with the data, and it's not done filling the array before its called to fill the TableView. From what i've read i need to use Grand Central Dispatch so that its not loading on the main thread.
Any help would be much appreciated.
+(EILeaderBoardModel *)scoresAndNameFromLeaderBoard
{
static EILeaderBoardModel *leaderBoard = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
leaderBoard = [[EILeaderBoardModel alloc] init];
leaderBoard.highScorePlayerArray = [[self class] GameCenterLeaderBoard];
});
return leaderBoard;
}
+ (NSMutableArray *)GameCenterLeaderBoard
{
NSMutableArray *_highScorePlayer = [NSMutableArray new];
GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] init];
leaderboardRequest.playerScope = GKLeaderboardPlayerScopeGlobal;
leaderboardRequest.timeScope = GKLeaderboardTimeScopeAllTime;
leaderboardRequest.range = NSMakeRange(1, 20);
leaderboardRequest.identifier = GameHighscoreIdentifier;
[leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if (error != nil)
{
// Handle the error.
}
if (scores != nil)
{
for (NSUInteger i = 0; i < scores.count; i++) {
GKScore *score = (GKScore *)scores[i];
[GKPlayer loadPlayersForIdentifiers:#[score.playerID] withCompletionHandler:^(NSArray *players, NSError *error) {
GKPlayer *player = (GKPlayer *)players[0];
[player loadPhotoForSize:GKPhotoSizeSmall withCompletionHandler:^(UIImage *photo, NSError *error) {
if (error != nil) {
}
UIImage *_avatar;
if (photo != nil) {
_avatar = photo;
} else {
_avatar = [UIImage imageNamed:#"unknownPersonImage.png"];
}
EIPlayer *currentPlayer = [EIPlayer nameLabel:player.displayName
scoreLabel:[NSString stringWithFormat:#"%llD",score.value]
avatar:_avatar];
[_highScorePlayer addObject:currentPlayer];
}];
}];
}
}
}];
return _highScorePlayer;
}
dispatch_queue_t queue = dispatch_queue_create("com.yourQueue.company", 0);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(queue, ^{
// Do all your data fetching work here
// also like adding values to arrays.
dispatch_async(main, ^{
// Do all your UI update logic here like updating data into tables.
[self.tableView reloadData];
});
});
hope this helps.
What I am trying to do is a Facebook wrapper for the Facebook iOS SDK. Basically the idea is that my ViewController should do nothing but showing ex. my friends that will be acquired with a simple call like
self.friends = [FacebookWrapper myFriends];
[self.tableView reloadData];
My wrapper myFriends method should look like this
+ (NSArray *)myFriends
{
__block NSArray *friends = nil;
[FBSession openActiveSessionWithReadPermissions:nil allowLoginUI:YES completionHandler:^(FBSession *session, FBSessionState status, NSError *error) {
if(FB_ISSESSIONOPENWITHSTATE(status)) {
[FBRequestConnection startForMyFriendsWithCompletionHandler:^(FBRequestConnection *connection, id data, NSError *error) {
CFRunLoopStop(CFRunLoopGetCurrent());
if(error) {
return;
}
NSArray *friendsData = (NSArray *)[data data];
NSMutableArray *fbFriends = [NSMutableArray array];
for(id friendData in friendsData) {
Friend *friend = [Friend friendWithDictionary:friendData];
fbFriends addObject:friend];
}
friends = [NSArray arrayWithArray:fbFriends];
}];
CFRunLoopRun();
}
}];
return friends;
}
The issue is that the openActiveSessionWithReadPermissions and startForMyFriendsWithCompletionHandler are asynchronous blocks so the method returns before the blocks complete their task.
Any help would be much appreciated.
I created a similar wrapper in the past and my approach was passing a "completion block" when calling my wrapper method; this completion block is then triggered once all the asynchronous calls are done running, and it receives whatever data your method would return in a synchronous scenario (in your case, the array of friends).
To illustrate - you could have your "myFriends" method redefined as:
+ (void)myFriendsWithCompletionBlock:(void (^)(NSArray *friends))completionBlock;
Then in the implementation, right after the friends = [NSArray arrayWithArray:fbFriends]; line, you would add this:
if (completionBlock != nil) {
completionBlock(friends);
}
... and remove the return statement at the end.
Finally, on your view controller (or any object using the method, you would do something like this:
[FacebookWrapper myFriendsWithCompletionBlock:^(NSArray *friends){
// do what you need to do with the friends array
}];
Of course, this is still asynchronous - but there's no way around since that's how the Facebook SDK was build (and, to be fair, this is probably the best way to do it - waiting for requests to finish synchronous would be terrible!)
Edit: I noticed you're also returning from the wrapper method in case it fails; in that situation, instead of returning you would do something like this:
if (completionBlock != nil) {
completionBlock(nil);
}
That would cause the friends array to be nil when your completion block is called - you can then treat that error there however seems appropriate to you.
Hope this helped!
If you are dispatching an asynchronouos block, you can communicate with your UIViewController subclass by calling back to it:
[self someSelectorWithCallbackData:stuffWhichYouWantToGiveBack];
This will call self to get captured by the block, and so will work as expected. From the relevant method you can refresh the view / reload the tableview / dance a jig as required.
Depending on the context, you may need to __block scope self, eg
__block UIViewController *bsself = self;
But if you do the latter, be careful to avoid a retain loop (the build and analysis tools are fairly good at pointing this out).
Think you need to use a protol
#class Webservice;
#protocol WebserviceDelegate
#optional
-(void)webservice:(Webservice *)webservice didFetchPosts:(NSArray *)posts;
-(void)webservice:(Webservice *)webservice didFetchComments:(NSArray *)comments forPostID:(NSString *)postID launchComments:(BOOL)launch;
-(void)webservice:(Webservice *)webservice didLoginWithUser:(User *)user;
-(void)webservice:(Webservice *)webservice didVoteWithSuccess:(BOOL)success forObject:(id)object direction:(BOOL)up;
#end
#interface Webservice : NSObject {
__weak id <WebserviceDelegate> delegate;
}
//Delegate
#property (weak) id <WebserviceDelegate> delegate;
-(void)getHomepage {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSURLResponse *response;
NSError *error;
// Create the URL Request
NSMutableURLRequest *request = [Webservice NewGetRequestForURL:[NSURL URLWithString:#"https://www.hnsearch.com/bigrss"]];
// Start the request
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
//Handle response
//Callback to main thread
if (responseData) {
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSStringEncodingConversionAllowLossy];
if (responseString.length > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self parseIDsAndGrabPosts:responseString];
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:nil];
});
}
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:nil];
});
}
});
}
-(void)parseIDsAndGrabPosts:(NSString *)parseString {
// Parse String and grab IDs
NSMutableArray *items = [#[] mutableCopy];
NSArray *itemIDs = [parseString componentsSeparatedByString:#"<hnsearch_id>"];
for (int xx = 1; xx < itemIDs.count; xx++) {
NSString *idSubString = itemIDs[xx];
[items addObject:[idSubString substringWithRange:NSMakeRange(0, 13)]];
}
// Send IDs back to HNSearch for Posts
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSURLResponse *response;
NSError *error;
// Create Request String
NSString *requestString = #"http://api.thriftdb.com/api.hnsearch.com/items/_bulk/get_multi?ids=";
for (NSString *item in items) {
requestString = [requestString stringByAppendingString:[NSString stringWithFormat:#"%#,", item]];
}
// Create the URL Request
NSMutableURLRequest *request = [Webservice NewGetRequestForURL:[NSURL URLWithString:requestString]];
// Start the request
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
//Handle response
//Callback to main thread
if (responseData) {
NSArray *responseArray = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:&error];
if (responseArray) {
NSMutableArray *postArray = [#[] mutableCopy];
for (NSDictionary *dict in responseArray) {
[postArray addObject:[Post postFromDictionary:dict]];
}
NSArray *orderedPostArray = [self orderPosts:postArray byItemIDs:items];
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:orderedPostArray];
// Update Karma for User
if ([HNSingleton sharedHNSingleton].User) {
[self reloadUserFromURLString:[NSString stringWithFormat:#"https://news.ycombinator.com/user?id=%#", [HNSingleton sharedHNSingleton].User.Username]];
}
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:nil];
});
}
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate webservice:self didFetchPosts:nil];
});
}
});
}