How to slow down ios process - ios

I have an app that can send information to a server. This information is stacked up during the day (while the client uses the app), and when he so desires, he can hit the "update" button to send everything on the server.
This always worked fine until he recently had a flow increase and went from updating 10 objects to more than 100.
Obviously, the update takes more time, taht's not the issue.
The issue is, at some point, i'm getting
Error: Error Domain=NSURLErrorDomain Code=-1001 "La requête a expiré."
UserInfo=0x189874b0 {NSErrorFailingURLStringKey=http://www.*********.be/upload,
NSErrorFailingURLKey=http://www.************.be/upload,
NSLocalizedDescription=La requête a expiré.,
NSUnderlyingError=0x189abd70 "La requête a expiré."}
For the frenchophobes, " The request has expired " is what i get back, and i've hidden the url with ****, as you noticed.
Now, i've tried locally, it works fine with a small update, but when i loop 150 times on my update (i send 150 times the same thing), at some point i just get the above error X times. This error does not specificall occur with all the last items, it can be 20 in the middle, or 30, etc.
Is there a way i can change that?
Here is a piece of code that must be related to the issue.
// Set the max number of concurrent operations (threads)
//[operationQueue setMaxConcurrentOperationCount:3]; // Todo: try increasing max thread count
[operationQueue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount]; //dynamic thread count
self.queueCount = persons.count;
self.currentQueue = 1;
for (Person *person in persons) {
for (int i = 0 ; i<130 ; i++){ //this is where i try to break the app
[self createSendPersonOperation:person];
}}
Now what would probably work is put the last line in a "thing" that would slow down the process every 20 or so occurences, so the server or the app doesn't go crazy.
Is this possible? if so, how?
Note : I am a junior dev trying to get into a senior's code, and that guy is not available, so i'm open to all the help i can have.
Edit : also, do you think my error comes from a server-sided issue or is definitly an app-sided issue?
Edit : Complete HTTP request.
So for every person that is saved into the app, when the user decides to update, it does that for every Person in the array of persons.
- (void)createSendPersonOperation:(Person *)person
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:#"text/html", #"application/json", nil];
NSDictionary *params = #{
#"email": person.email,
#"gender": person.gender,
#"language": person.language,
#"hasFacebook": person.hasFacebook,
#"sendPostalCard": person.sendPostalCard
};
NSLog(#"params: %#", params);
[manager POST:kURLUpdate parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
// Add picture to the form
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pictureFilePath = [documentsDirectory stringByAppendingPathComponent:person.picture];
NSURL *pictureURL = [NSURL fileURLWithPath:pictureFilePath];
[formData appendPartWithFileURL:pictureURL name:#"picture" error:nil];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Success: %#", responseObject);
if ([responseObject isKindOfClass:[NSDictionary class]]) {
if ([responseObject objectForKey:#"error"]) {
NSLog(#"Error 1");
NSDictionary *error = [responseObject objectForKey:#"error"];
NSLog(#"Error message: %#", [error objectForKey:#"message"]);
} else {
// Set Person's sended attribute
person.sended = #YES;
[Person saveObject:[[PersistentStack sharedInstance] managedObjectContext] error:nil];
}
} else {
NSLog(#"Error 2");
}
[self decreaseQueueCount];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
NSLog(#"Parameter that failed : %#", [params objectForKey:#"email"]);
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Erreur"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"Fermer"
otherButtonTitles:nil];
[alertView show];
self.updateHud.mode = MBProgressHUDModeText;
self.updateHud.labelText = AMLocalizedString(#"update.failure.message", #"");
[self.updateHud hide:YES afterDelay:3];
}];
}

I don't really know the source of your problem, but if you think slowing the app will at least help you understand your problem you could do it with something like this:
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:15];
while ([loopUntil timeIntervalSinceNow] > 0) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:loopUntil];
}
It will wait for 15 seconds before continue, so you can put this one after 20~30 requests as you suggested.
I really believe you should consider grouping your requests or something like that so you won't overload your server (if that is really your problem).

Related

JSON file Download from Server , file is loaded 8 times?

I am loading my LiveRatingDemo.json from the internet server.
Everything works but,
The LiveRatingDemo.json is loaded 8 times from the server why?
What have I forgotten or done wrong?
my LiveRatingDemo.json :
{
"ratingArray":[{
"ratingClubID":"101",
"ratingValue":"2"
},
{
"ratingClubID":"102",
"ratingValue":"4"
},
{
"ratingClubID":"103",
"ratingValue":"5"
},
{
"ratingClubID":"104",
"ratingValue":"1"
},
{
"ratingClubID":"105",
"ratingValue":"3"
},
{
"ratingClubID":"106",
"ratingValue":"5"
},
{
"ratingClubID":"107",
"ratingValue":"4"
}
]
}
my objective c Code :
-(void)parseLiveRating
{
NSURL *baseURL = [NSURL URLWithString:getLiveRating];
NSString *path = [NSString stringWithFormat:#"LiveRatingDemo.json"];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL];
[manager GET:path parameters:nil progress:nil success:^(NSURLSessionDataTask *task, id JSON)
{
NSDictionary *dictTemp = (NSDictionary *) JSON;
NSArray *arrRating = [dictTemp objectForKey:#"ratingArray"];
NSLog(#"Test Ausgabe : %#", arrRating);
for (int i=0; i<arrRating.count; i++) {
GetRating *rat = [[GetRating alloc] init];
[rat parseResponse:[arrRating objectAtIndex:i]];
[mratingLive addObject:rat];
NSLog(#"Success: %#", JSON);
[self displayAllRatings];
}
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(#"Error Loading: %#", error);
}];
}
Thank you for your efforts
I give you the solution.From your question you asked
The LiveRatingDemo.json is loaded 8 times from the server why?
The First Reason is
1.You added or put NSLog(#"Success: %#", JSON) code in for loop.Whenever for loop runs,it prints the result.If you have 8
objects,eight times it prints or displays the result.
So you need to remove the NSLog(#"Success: %#", JSON) from the for loop.
The Second Reason is
2.Then you must remove the [self displayAllRatings] line from the for loop.Becuase I think in [self displayAllRatings] you call the
mratingLive array.So every time that method calls when for loop runs.
Just call that method out of the for loop.

How do I inject deep linking query parameters into appLinkData when using Facebook app links on iOS?

My question is predicated on Jason Clark’s statement at f8 2014 that app links is designed to sit side by side well with deep linking technologies. I have a word game app published in 2013 to Canada and Australia using Facebook’s authentication but without deep linking. For the US market launch my intent was to add Facebook deep linking. Managed with the request and feed dialogs to send the Match Identifier UUID for both initiating a match and then bragging about the match (FriendSmash example) at that time to open the game on that match. My day job got in the way and I did not have time to finish QA and support a launch to the US. Last summer I upgraded SDKs (Facebook 3.14) and the url query no longer contained the string ‘notif’ to allow me to parse for the match Id. Therefore, I switched from using [FBSession.activeSession handleOpenURL:url] to the current [FBAppCall handleOpenURL:url …] and some other code from the new FriendSmash example. However, the url passed from the Facebook app no longer contains the ‘ref’ and therefore not the ‘notif’ which was the method documented to utilize my deep linking content. I have since updated my project to Facebook iOS SDK 3.21 (predicated in part by other SDKs, e.g. AWS not supporting arm64).
I currently use the following code to send the brag
- (void) sendBrag
{
// This function will invoke the Feed Dialog to post to a user's Timeline and News Feed
// It will attemnt to use the Facebook Native Share dialog
// If that's not supported we'll fall back to the web based dialog.
NSArray *friend = [[NSArray alloc] initWithObjects: bragMatch.opponentFacebookId, nil];
NSString *linkURL = [NSString stringWithFormat:#"http://www.ruleofwords.com/?deeplink_matchno=%#", bragMatch.initiatorMatchNo];
//NSString *linkURL = [NSString stringWithFormat:#"http://www.ruleofwords.com"];
NSString *pictureURL = #"https://pacific-castle-1361.herokuapp.com/rowappicon.png";
NSMutableString *msg = [[NSMutableString alloc]
initWithString:#"Just scored 0 points in Rule of Words. Good luck trying to beat that."];
[msg replaceOccurrencesOfString:#"0" withString:bragMatch.initiatorMatchPoints
options:NSBackwardsSearch range: NSMakeRange(0, [msg length])];
// Prepare the native share dialog parameters
FBLinkShareParams *shareParams = [[FBLinkShareParams alloc] init];
shareParams.friends = friend;
shareParams.link = [NSURL URLWithString:linkURL];
shareParams.name = #"Rule of Words Bragging Rights";
shareParams.caption= msg;
shareParams.picture= [NSURL URLWithString:pictureURL];
shareParams.linkDescription = #"Rule of Words, the contagious photography word social game on iPhone.";
if ([FBDialogs canPresentShareDialogWithParams:shareParams]){
[FBDialogs presentShareDialogWithParams:shareParams
clientState:nil
handler:^(FBAppCall *call, NSDictionary *results, NSError *error) {
if(error) {
NSLog(#"Error publishing story.");
} else if (results[#"completionGesture"] && [results[#"completionGesture"] isEqualToString:#"cancel"]) {
NSLog(#"User canceled story publishing.");
} else {
NSLog(#"Story published.");
}
}];
} else {
// Prepare the web dialog parameters
NSDictionary *params = #{
#"to": bragMatch.opponentFacebookId,
#"name" : shareParams.name,
#"caption" : shareParams.caption,
#"description" : shareParams.linkDescription,
#"picture" : pictureURL,
#"link" : linkURL
};
// Invoke the dialog
[FBWebDialogs presentFeedDialogModallyWithSession:nil
parameters:params
handler:
^(FBWebDialogResult result, NSURL *resultURL, NSError *error) {
if (error) {
NSLog(#"Error publishing story %#.", error);
} else {
if (result == FBWebDialogResultDialogNotCompleted) {
NSLog(#"User canceled story publishing.");
} else {
NSLog(#"Story published.");
}
}}];
}
}
(Aside - In my stepping through the sendBrag code the FBDialogs canPresentShareDialogWithParams always fails so that every time it defaults to a feed dialog. Anyone know what is the state is where the share dialog works? )
Then in my AppDelegate I have…
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
NSLog(#"app is %# and url is %# and sourceApp is %#", application, url, sourceApplication);
// attempt to extract a token from the url
return [FBAppCall handleOpenURL:url
sourceApplication:sourceApplication
fallbackHandler:^(FBAppCall *call) {
// If there is an active session
if (FBSession.activeSession.isOpen) {
// Check the incoming link
[self handleAppLinkData:call.appLinkData];
} else {
NSLog(#"Access Token is %#", call.accessTokenData);
if (call.accessTokenData) {
// If token data is passed in and there's
// no active session.
if ([self handleAppLinkToken:call.accessTokenData]) {
// Attempt to open the session using the
// cached token and if successful then
// check the incoming link
[self handleAppLinkData:call.appLinkData];
}
}
}
}];
- (void) handleAppLinkData:(FBAppLinkData *)appLinkData {
NSURL *targetURL = appLinkData.targetURL;
if (targetURL) {
//NSURL *targetURL = [NSURL URLWithString:targetURLString];
NSDictionary *targetParams = [self parseURLParams:[targetURL query]];
NSString *ref = [targetParams valueForKey:#"ref"];
// Check if ref parm is among incoming news feed link, otw attribution link
if ([ref isEqualToString:#"notif"]) {
// Get the request id
NSString *requestIDParam = targetParams[#"request_ids"];
NSArray *requestIDs = [requestIDParam
componentsSeparatedByString:#","];
// Get data (of last request) from Graph API call to request id endpoint
//
[self notificationGet:requestIDs[[requestIDs count]-1]];
}
}
}
- (void) notificationGet:(NSString *)requestid {
__block NSString *title;
__block NSString *message;
[FBRequestConnection startWithGraphPath:requestid
completionHandler:^(FBRequestConnection *connection,
id result,
NSError *error) {
if (! error) {
if (result[#"data"])
{
title = [NSString stringWithFormat:
#"%# requests a match!", result[#"from"][#"name"]];
NSString *jsonString = result[#"data"];
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
if (!jsonData) {
NSLog(#"JSON decode error: %#", error);
return;
}
NSError *jsonError = nil;
NSDictionary *requestData =[NSJSONSerialization JSONObjectWithData:jsonData options:0
error:&jsonError];
if (jsonError) {
NSLog(#"JSON decode error: %#", error);
return;
}
message = [NSString stringWithFormat:#"Match #: %#, Flashes: %#",
requestData[#"deeplink_matchno"],
requestData[#"flashes_gifted"]];
// set global opponentFacebookId for bragging
[[rowGlobals sharedGlobalVars] setRowGlobal:requestData[#"deeplink_matchno"]
forKey:#"deeplink_matchno"] ;
// Set up FB Deep Link Notification
// homeViewController is registered for notification
[[NSNotificationCenter defaultCenter]
postNotificationName:FBDeepLinkNotification
object:requestData[#"deeplink_matchno"]];
NSLog(#"successfully posted DeepLink notification") ;
} else {
title = [NSString stringWithFormat:#"%# sent you a request", result[#"from"][#"name"]];
message = result[#"message"];
}
// Delete the request notification
[self notificationClear:result[#"id"]];
} else {
title = #"General Deep Link Error" ;
message = #"Error occured with Facebook FBRequest Connection. This Facebook notification likely has been previously processed." ;
}
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil,
nil];
[alert show];
}];
}
Now I recognize I have some clean up to align the dialog setup with how I parse appLinkData to get the matchId UUID for my NSNotification to open on the game, but I am not getting that far to bother cleaning it up.
I have tried many meta tags on my site but at the moment I am just using
<meta property=”al:ios:url” content=”ruleofwords”>
where ruleofwords is a custom URL I added to my app’s plist.
but I cannot seem to put together any URL other than
NSString *linkURL = [NSString stringWithFormat:#"http://www.ruleofwords.com"];
which will launch my game by clicking on the Facebook link…
When I use anything like
NSString *urlLink = [NSString stringWithFormat:#"https://www.ruleofwords.com/?deeplink_matchNo=%#", bragMatch.initiatorMatchNo];
Then Facebook will only launch my web page without the custom URL link to my game.
Moreover, Facebook no longer launches it directly; it brings up my web page with an added link displayed as the offset brackets app Links logo.(Although not explicit it does technically follow the Facebook flow diagram). Touching this app Link logo does launch my game. This seems to be the case whether I have the meta tag on my website or not (now I may have other problems since we are only using Godaddy website builder).
Previously with the request dialog I had the ability to populate values such as #”data” with things like my match Id and gifts
NSData *jsonData = [NSJSONSerialization
dataWithJSONObject:#{
#"deeplink_matchno": bragMatch.initiatorMatchNo,
#"flashes_gifted": #"0"}
options:0
error:&error];
if (!jsonData) {
NSLog(#"JSON error: %#", error);
return;
}
NSString *challengeStr = [[NSString alloc]
initWithData:jsonData
encoding:NSUTF8StringEncoding];
NSMutableDictionary *params =
[NSMutableDictionary dictionaryWithObjectsAndKeys:
bragMatch.opponentFacebookId, #"to",
#"Rule of Words Bragging", #"name",
msg, #"caption",
#"Rule of Words, the contagious photography word social game on iPhone.",
#"description",
urlLink, #"link",
#"https://pacific-castle-6969.herokuapp.com/rowappicon.png", #"picture",
challengeStr, #"data",
nil];
// Invoke the dialog
//[self.facebook dialog:#"feed" andParams:params andDelegate:self];
//[FBWebDialogs presentFeedDialogModallyWithSession:nil
[FBWebDialogs presentRequestsDialogModallyWithSession:nil
message:msg
title:nil
parameters:params
handler:
^(FBWebDialogResult result, NSURL *resultURL, NSError *error) {
if (error) {
// Case A: Error launching the dialog or publishing story.
NSString *errormsg = [NSString stringWithFormat:#"Error encountered posting Facebook brag with error %#", error.localizedDescription];
NSLog(#"%#", errormsg);
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Facebook Brag Error"
message:errormsg
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles: nil];
[alert show];
} else {
if (result == FBWebDialogResultDialogNotCompleted) {
// Case B: User clicked the "x" icon
NSLog(#"Player canceled Facebook brag post via x icon.");
} else {
// Case C: Dialog shown and the user clicks Cancel or Share
NSDictionary *urlParams = [self parseURLParams:[resultURL query]];
//if (![urlParams valueForKey:#"post_id"]) {
if ([urlParams valueForKey:#"error_code"]) {
// User clicked the Cancel button
NSLog(#"error message is %#", [urlParams valueForKey:#"error_message"]);
NSLog(#"Player canceled Facebook brag post via Cancel button.");
} else {
// User clicked the Share button
//NSString *postID = [urlParams valueForKey:#"post_id"];
NSString *postID = [urlParams valueForKey:#"request"];
NSLog(#"Player Successfuly Posted RoW Match via Facebook brag, id: %#", postID);
}
}
}
}];
However, I no longer have al_applink_data in the passed URL for this request dialog, it just has an access_token with the original custom URL.
url is fb407545862655947://authorize/#access_token=CAAFyqSpfj8sBAGxahmdTCl1D9fs5hZBt3OfKP2MHCtM8STHadqEjlyFnXDTNHScjsxZCI6q0H8ZAppNSqJIJt83uN4LemkfklLjOdPTv3JZBtg3xTVZBKOhzdOMaZCGob5x2FPddZBzmaZBhIaE8dIgNqPfi9IlEuwQ2oHq6tEgA1w1pNESnHuDyK9gD7vswAC93nO7cCmCT4KBgxI22UDB3nINYZA058g8AZD&expires_in=3600 and sourceApp is com.facebook.Facebook
When I add the #”data” param with
[FBWebDialogs presentFeedDialogModallyWithSession:…]
although this argument is accepted by the feed dialog I the url does not get my match UUID or anything extra from my #”data” param…
url is ruleofwords:///?al_applink_data=%7B%22target_url%22%3A%22http%3A%5C%2F%5C%2Fwww.ruleofwords.com%5C%2F%22%2C%22extras%22%3A%7B%22fb_ref%22%3A%22Default%22%7D%2C%22referer_app_link%22%3A%7B%22url%22%3A%22fb%3A%5C%2F%5C%2F%5C%2F%3Fbacktrack_id%3Ddlr5p6iu_6a-OeUn%22%2C%22app_name%22%3A%22Facebook%22%7D%7D and sourceApp is com.facebook.Facebook
So I am at a loss how I am supposed to inject content specific information so that I can open my game with the match, which the opponent has requested or bragged about. I probably would have given up a long time ago but I did once have deep linking working (SDK 3.5?). I am wondering if I should be at another release level of the SDK for deep linking. It seems like the Facebook platform has significantly changed how it packages appLinkData. And although Facebook’s documentation has improved over the past two years, many of the examples which are required to put this all together do not seem to be referring to the same level of SDK. So Facebook how do I inject content into appLinkData so I can support deep linking with SDK 3.21? I thought about adding the Bolts framework but Jason implied it was designed for outlinking apps, which is not my case.
I can directly type in my iPhone Safari something like ‘ruleofwords://matchId=UUID’ and get the app delegate to parse out the [url query] so I could probably implement deep linking with push notifications, but I still like the ability to have a Facebook user tap on a link and launch the specific match directly.

How to Fetch data out from block in AFNetworking ios?

Creating first app with webservices, I am using AFNetworking for webservices. Everything is working fine but i have no idea , that how to fetch data out from block which i am getting in response. This is what i have done so far
+(WebServices *)sharedManager{
static WebServices *managerServices = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
managerServices = [[self alloc] init];
});
return managerServices;
}
-(NSArray *)firstPostService{
//1
NSURL *url = [NSURL URLWithString:BaseURLString];
//2
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:url];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSDictionary *param = #{#"request" : #"get_pull_down_menu" , #"data" : #"0,0,3,1"};
[manager POST:#"person.php" parameters:param success:^(NSURLSessionDataTask *task, id responseObject) {
[self methodUsingJsonFromSuccessBlock:responseObject];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Error retrieving data" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[av show];
}];
if (list.count == 0) {
NSLog(#"Nothing in array yet!!");
}
else{
NSLog(#"Object 1 is : %#", [list objectAtIndex:1]);
}
return list;
}
- (void)methodUsingJsonFromSuccessBlock:(id)json {
// use the json
NSString *string = [NSString stringWithUTF8String:[json bytes]];
NSLog(#"This is data : %#", string);
list = [string componentsSeparatedByString:#"\n"];
NSLog(#"After sepration first object: %#", [list objectAtIndex:1]);
//NSLog(#"json from the block : %#", json);
}
What i understand reading from different blogs and tuts, that block is a separate thread and what every i do finishes with it. I read some where that this is normally use for it
dispatch_async(dispatch_get_main_queue(), ^{
data = [string componentsSeparatedByString:#"\n"];
//WHERE DATA IS __block NSArray * data = [[NSArray alloc] init];
});
and i was returning it in the of the function(firstPostService) but nothing happen. i still get an empty array outside the block. Kindly help me , suggest me some good reading stuff. Thanking you all in advance.
You say:
I need this data to my view controller i am trying to return in dispatch part but it is not allowing. Is it possible to get data into my viewcontroller class ?
Yes, it's possible. But, no, firstPostService should not return the results. It can't because it returns immediately, but the POST completion blocks won't be called until much later. There's nothing to return by the time firstPostService returns.
At the end of your original question, you said:
What i understand reading from different blogs and tuts, that block is a separate thread and what every i do finishes with it. I read some where that this is normally use for it
dispatch_async(dispatch_get_main_queue(), ^{
data = [string componentsSeparatedByString:#"\n"];
//WHERE DATA IS __block NSArray * data = [[NSArray alloc] init];
});
This is not the appropriate pattern of __block local variable. You generally use that __block pattern when dealing with some block that runs synchronously (for example the block of an enumeration method). But while you can use __block variable with asynchronous block, you almost never do (and it doesn't quite make sense to even try to do it). When you use appropriate completion block patterns, there's no need for any __block variable.
So, let's go back to your original code sample: So, you should take a page from AFNetworking and employ completion blocks yourself. When the AFNetworking POST method wanted to return data to your code asynchonously, it used a completion block pattern, instead. Thus, if your own firstPostService wants to pass back data asynchronously, it should do the same.
For example:
#interface WebServices ()
#property (nonatomic, strong) AFHTTPSessionManager *manager;
#end
#implementation WebServices
// note, use `instancetype` rather than actually referring to WebServices
// in the `sharedManager` method
+ (instancetype)sharedManager
{
static id sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
// I'd also suggest that you init the `AFHTTPSessionManager` only once when this
// object is first instantiated, rather than doing it when `firstPostService` is
// called
- (instancetype)init
{
self = [super init];
if (self) {
NSURL *url = [NSURL URLWithString:BaseURLString];
self.manager = [[AFHTTPSessionManager alloc] initWithBaseURL:url];
self.manager.responseSerializer = [AFHTTPResponseSerializer serializer];
}
return self;
}
// Notice:
//
// 1. This now has a return type of `void`, because when it instantly returns,
// there is no data to return.
//
// 2. In order to pass the data back, we use the "completion handler" pattern.
- (void)firstPostServiceWithCompletionHandler:(void (^)(NSArray *list, NSError *error))completionHandler {
NSDictionary *param = #{#"request" : #"get_pull_down_menu" , #"data" : #"0,0,3,1"};
[self.manager POST:#"person.php" parameters:param success:^(NSURLSessionDataTask *task, id responseObject) {
NSArray *list = [self methodUsingJsonFromSuccessBlock:responseObject];
if (completionHandler) {
completionHandler(list, nil);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
[[[UIAlertView alloc] initWithTitle:#"Error retrieving data" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil] show];
if (completionHandler) {
completionHandler(nil, error);
}
}];
// // none of this code belongs here!!! You are dealing with asynchronous methods.
// // the `list` has not been returned by the time you get here!!! You shouldn't even
// // be using instance variable anyway!
//
// if (list.count == 0) {
//
// NSLog(#"Nothing in array yet!!");
// }
// else{
// NSLog(#"Object 1 is : %#", [list objectAtIndex:1]);
//
// }
// return list;
}
- (NSArray *)methodUsingJsonFromSuccessBlock:(NSData *)data {
// note, do not use `stringWithUTF8String` with the `bytes` of the `NSData`
// this is the right way to convert `NSData` to `NSString`:
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"This is string representation of the data : %#", string);
// Note, retire the `list` instance variable, and instead use a local variable
NSArray *list = [string componentsSeparatedByString:#"\n"];
NSLog(#"After sepration first object: %#", [list objectAtIndex:1]);
return list;
}
#end
Then, you could invoke that like so:
[[WebServices sharedManager] firstPostServiceWithCompletionHandler:^(NSArray *list, NSError *error) {
if (error) {
// handle the error here
} else {
// use the `list` results here
}
}];
// NOTE, DO NOT USE `list` HERE. By the time you get here, `list` has not been
// returned. Only use it in the above block.
//
// In fact, you can see that if you put a `NSLog` or breakpoint here, and again, above
// where it says "use the `list` results` here", you'll see that it's running the code
// inside that block _after_ this code down here!
I'd suggest you tackle the above first, to first make sure you completely understand the proper asynchronous technique of the completion block pattern. We don't want to complicate things quite yet. Make sure you're getting the sort of data you wanted before you proceed to what I will describe below.
But, once you've grokked the above, it's time to look at your JSON parsing. You make several reference to JSON, but if that's what it really is, then using componentsSeparatedByString is not the right way to parse it. You should use NSJSONSerialization. Or even better, you can let AFNetworking do that for you (right now, you're making it more complicated than it needs to be and your results will not be formatted correctly).
Above, I kept your methodUsingJsonFromSuccessBlock in the process, but if you're really dealing with JSON, you should eliminate that method entirely. Let AFNetworking do this for you.
You should eliminate the line that says:
responseSerializer = [AFHTTPResponseSerializer serializer];
The default serializer is AFJSONResponseSerializer which is what you want to use if handling JSON requests.
The methodUsingJsonFromSuccessBlock is then no longer needed because AFNetworking will do the JSON conversion for you. So firstPostServiceWithCompletionHandler should look like:
- (void)firstPostServiceWithCompletionHandler:(void (^)(NSArray *list, NSError *error))completionHandler {
NSDictionary *param = #{#"request" : #"get_pull_down_menu" , #"data" : #"0,0,3,1"};
[self.manager POST:#"person.php" parameters:param success:^(NSURLSessionDataTask *task, id responseObject) {
if (completionHandler) {
completionHandler(responseObject, nil);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
[[[UIAlertView alloc] initWithTitle:#"Error retrieving data" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil] show];
if (completionHandler) {
completionHandler(nil, error);
}
}];
}

Downloading, saving and reloading UITableView is blocking UI

I have method called collectData in my app which is the most important part of my View Controller. In that method I do a couple of signicant things (downloading, parsing, saving to persistent store), so it would be easier for you to take a look:
-(void)collectData
{
// Downloading all groups and saving them to Core Data
[[AFHTTPRequestOperationManager manager] GET:ALL_GROUPS parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSMutableDictionary* groups = [NSMutableDictionary new];
NSMutableArray* newIds = [NSMutableArray new];
NSError *error;
// Saving everything from response to MOC
for (id group in responseObject) {
Group *groupEntity = [NSEntityDescription insertNewObjectForEntityForName:#"Group" inManagedObjectContext:self.moc];
groupEntity.name = [group valueForKey:#"name"];
groupEntity.cashID = [group valueForKey:#"id"];
groupEntity.caseInsensitiveName = [[group valueForKey:#"name"] lowercaseString];
groupEntity.selected = #NO;
// Filling up helping variables
groups[groupEntity.cashID] = groupEntity;
[newIds addObject:groupEntity.cashID];
}
// Fetching existing groups from Persistant store
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:#"Group"];
[r setIncludesPendingChanges:NO];
r.predicate = [NSPredicate predicateWithFormat:#"cashID IN %#",newIds];
NSArray *existingGroups = [self.moc executeFetchRequest:r error:&error];
// Deleting groups which already are in database
for (Group* g in existingGroups) {
Group* newGroup = groups[g.cashID];
g.name = [newGroup valueForKey:#"name"];
g.cashID = [newGroup valueForKey:#"cashID"];
g.caseInsensitiveName = [[newGroup valueForKey:#"name"] lowercaseString];
[self.moc deleteObject:newGroup];
}
// Saving Entity modification date and setting it to pull to refresh
[self saveModificationDate:[NSDate date] forEntityNamed:#"Group"];
[self.pullToRefreshView.contentView setLastUpdatedAt:[self getModificationDateForEntityNamed:#"Group"]
withPullToRefreshView:self.pullToRefreshView];
// Save groups to presistant store
if (![self.moc save:&error]) {
NSLog(#"Couldn't save: %#", [error localizedDescription]);
}
[[self fetchedResultsController] performFetch:&error];
[self.pullToRefreshView finishLoading];
[self.tableView reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Show alert with info about internet connection
[self.pullToRefreshView finishLoading];
UIAlertView *internetAlert = [[UIAlertView alloc] initWithTitle:#"Ups!" message:#"Wygląda na to, że nie masz połączenia z internetem" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[internetAlert show];
}];
}
So when I start collecting data (first run or push to refresh) this method is blocking UI.
I want to avoid this but when I put the success block into another dispatch_async and get back to main queue only for [self.tableView reloadData] I face problem with saving to persistent store or something with bad indexes.
How can I do this whole thing in background and leave UI responsive to the user?
Just an idea, give it a try using dispatch_sync. Have a look at this explanation here where log result something similar to your need. Put [yourTableView reloadData] after synchronous block.
Hope it helps!
It seems AFNetwork call is not async so just try to call your method via performselector.

Using AFNetworking NSOperations to download a number of files serially.....runs out of memory

Note: I'm using ARC.
I have some code that makes 1 request to an http server for a list of files (via JSON). It then parses that list into model objects which it uses to add a download operation (for downloading that file) to a different nsoperationqueue and then once it's done adding all of those operations (queue starts out suspended) it kicks off the queue and waits for all the operations to finish before continuing. (Note: this is all done on background threads so as not to block the main thread).
Here's the basic code:
NSURLRequest* request = [NSURLRequest requestWithURL:parseServiceUrl];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
op.responseSerializer = [AFJSONResponseSerializer serializer];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//NSLog(#"JSON: %#", responseObject);
// Parse JSON into model objects
NSNumber* results = [responseObject objectForKey:#"results"];
if ([results intValue] > 0)
{
dispatch_async(_processQueue, ^{
_totalFiles = [results intValue];
_timestamp = [responseObject objectForKey:#"timestamp"];
NSArray* files = [responseObject objectForKey:#"files"];
for (NSDictionary* fileDict in files)
{
DownloadableFile* file = [[DownloadableFile alloc] init];
file.file_id = [fileDict objectForKey:#"file_id"];
file.file_location = [fileDict objectForKey:#"file_location"];
file.timestamp = [fileDict objectForKey:#"timestamp"];
file.orderInQueue = [files indexOfObject:fileDict];
NSNumber* action = [fileDict objectForKey:#"action"];
if ([action intValue] >= 1)
{
if ([file.file_location.lastPathComponent.pathExtension isEqualToString:#""])
{
continue;
}
[self downloadSingleFile:file];
}
else // action == 0 so DELETE file if it exists
{
if ([[NSFileManager defaultManager] fileExistsAtPath:file.localPath])
{
NSError* error;
[[NSFileManager defaultManager] removeItemAtPath:file.localPath error:&error];
if (error)
{
NSLog(#"Error deleting file after given an Action of 0: %#: %#", file.file_location, error);
}
}
}
[self updateProgress:[files indexOfObject:fileDict] withTotal:[files count]];
}
dispatch_sync(dispatch_get_main_queue(), ^{
[_label setText:#"Syncing Files..."];
});
[_dlQueue setSuspended:NO];
[_dlQueue waitUntilAllOperationsAreFinished];
[SettingsManager sharedInstance].timestamp = _timestamp;
dispatch_async(dispatch_get_main_queue(), ^{
callback(nil);
});
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
callback(nil);
});
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
callback(error);
}];
[_parseQueue addOperation:op];
and then the downloadSingleFile method:
- (void)downloadSingleFile:(DownloadableFile*)dfile
{
NSURLRequest* req = [NSURLRequest requestWithURL:dfile.downloadUrl];
AFHTTPRequestOperation* reqOper = [[AFHTTPRequestOperation alloc] initWithRequest:req];
reqOper.responseSerializer = [AFHTTPResponseSerializer serializer];
[reqOper setCompletionBlockWithSuccess:^(AFHTTPRequestOperation* op, id response)
{
__weak NSData* fileData = response;
NSError* error;
__weak DownloadableFile* file = dfile;
NSString* fullPath = [file.localPath substringToIndex:[file.localPath rangeOfString:file.localPath.lastPathComponent options:NSBackwardsSearch].location];
[[NSFileManager defaultManager] createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:Nil error:&error];
if (error)
{
NSLog(#"Error creating directory path: %#: %#", fullPath, error);
}
else
{
error = nil;
[fileData writeToFile:file.localPath options:NSDataWritingFileProtectionComplete error:&error];
if (error)
{
NSLog(#"Error writing fileData for file: %#: %#", file.file_location, error);
}
}
[self updateProgress:file.orderInQueue withTotal:_totalFiles];
}
failure:^(AFHTTPRequestOperation* op, NSError* error)
{
[self updateProgress:dfile.orderInQueue withTotal:_totalFiles];
NSLog(#"Error downloading %#: %#", dfile.downloadUrl, error.localizedDescription);
}];
[_dlQueue addOperation:reqOper];
}
What I'm seeing is a constant spike in memory as more files get downloaded. It's like the responseObject or maybe even the whole completionBlock is not being let go of.
I've tried making the responseObject __weak as well as fileData. I've tried adding an autoreleasepool and I've tried making the actual file domain object __weak too but still memory climbs and climbs.
I've run Instruments and not seen any leaks persay but it never gets to a point where all the files have been downloaded before it runs out of memory with a big fat "can't allocate region" error. Looking at allocations, I see a bunch of connection:didFinishLoading and connection:didReceiveData methods that never seem to be let go of, however. I can't seem to debug it further than that though.
My question: Why is it running out of memory? What is not getting deallocated and how can I get it to do such?
There is a few things going on here. The biggest is that you are downloading the entire file, storing it in memory, and then writing it out to disk when the download is complete. Even with just one file of 500 MB, you will run out of memory.
The correct way to do this is using an NSOutputStream with asynchronous downloads. The key is to write out the data as soon as it arrives. It should look like this:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.outputStream write:[data bytes] maxLength:[data length]];
}
Also of note, you are creating your weak references inside the block, not outside. Because of that, you are still creating a retain cycle and leaking memory. When you create weak references, it should look like this.
NSOperation *op = [[NSOperation alloc] init];
__weak NSOperation *weakOp = op;
op.completion = ^{
// Use only weakOp within this block
};
Lastly, your code is using #autoreleasepool. NSAutoreleasePool, and the ARC equivalent #autoreleasepool are only useful in very limited situations. As a general rule, if you aren't absolutely sure you need one, you don't.
With the help of a friend, I was able to figure out the problem.
The problem was actually in the first block of code:
[_dlQueue waitUntilAllOperationsAreFinished];
Apparently , waiting for all operations to finish meant none of those operations would be released either.
Instead of that, I ended up adding a final operation to the queue that would do the final processing and callback and memory is much more stable now.
[_dlQueue addOperationWithBlock:^{
[SettingsManager sharedInstance].timestamp = _timestamp;
dispatch_async(dispatch_get_main_queue(), ^{
callback(nil);
});
}];
What kind of file you are downloading? If you are working with Images or videos you nee to clear URLCache as when you doneload images it create CFDATA and some information in cache and it does not cleared out. You need to clear it explicitly when your single file download completed. It will never caught as a leak also.
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
[sharedCache release];
If you are using ARC replace
[sharedCache release];
with
sharedCache = nil;
Hope It may help you.

Resources