Don't know what to do with it. but i loaded data and load table when data is loaded,
[manager POST:path parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
arryGlobal = [NSMutableArray new];
[arryGlobal addObject:responseObject];
if([[[[arryGlobal valueForKey:#"Success"] objectAtIndex:0] stringValue] isEqualToString:#"1"]){
arryGlobal = [[arryGlobal valueForKey:#"Result"] objectAtIndex:0];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tblMainCategory reloadData];
});
}
else if([[[[arryGlobal valueForKey:#"Success"] objectAtIndex:0] stringValue] isEqualToString:#"0"]){
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
//here is place for code executed in error case
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error while sending"
message:#"Sorry, try again."
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
NSLog(#"Error: %#", [error localizedDescription]);
}];
It works perfectly two times, but when i go for 3rd time call this webservice it load data, get data, table successfully reloaded, but it not change content in table, it appear as it is in 2nd time.
SO what happens there ?? When I scroll table, then in cellForRowAtIndexPath array that i use to pass in table is contain data of 2nd time called Webservice.
EDIT:
i added this table view VievController in other view like :
MainCategory *objMain = [[MainCategory alloc] initWithNibName:#"MainCategory" bundle:nil];
[objMain LoadData:tag];
objMain.view.frame = CGRectMake(0, 0, self.bgViewCat.frame.size.width, self.bgViewCat.frame.size.height);
[self.bgViewCat insertSubview:objMain.view atIndex:1];
[self addChildViewController:objMain];
[objMain didMoveToParentViewController:self];
Try This
if([[[[arryGlobal valueForKey:#"Success"] objectAtIndex:0] stringValue] isEqualToString:#"1"])
{
[arryGlobal removeAllObjects];
arryGlobal=[NSarray alloc]init];
arryGlobal = [[arryGlobal valueForKey:#"Result"] objectAtIndex:0];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tblMainCategory reloadData];
});
}
the block:success() already run in main queue;you needn't use dispatch_async(dispatch_get_main_queue();
and arryGlobal is a kind of NSMutableArray;why you use the function called "thevalueforKey:" to get the value ?
I think maybe you should print the responseObject to check your data
ok so ther's error on :
[self.bgViewCat insertSubview:objMain.view atIndex:1];
Instead of this I use this and everything is fine, don't know why?:
[self.bgViewCat addSubview:objMain.view];
Related
I've 2 web-services, on of them retrieve all the images and other retrieve filtered images from web service.
When the app loads it call web service which retrieve all the images. And when user apply filters it retrieve the filtered images. But the problem I'm facing is:
Problem Statement:
When user select at least one filter it worked fine. But when user un-select (means none of the filters are selected) it goes to failure. My web service is coded in a way that when no parameters are passed it should return all the images, but it didn't. I want it to load the all images web-serivce again.
With Code Explanation:
[operation GET:#"stock_search" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
{
// operation is AFHTTPRequestOperationManager
NSMutableArray *temGalArray = [responseObject objectForKey:#"data"];
[imageArray removeAllObjects];
for (NSDictionary *myDict in temGalArray)
{
id object = [myDict objectForKey:#"square_image"];
if ([myDict objectForKey:#"square_image"]!=[NSNull null])
{
[imageArray addObject:myDict]; //this works fine
}
else if([object isEqual:[NSNull null]])
{
[self getGalleryFromWeb]; //***PROBLEM IS HERE***
//1: This condition is never true
//2: Self.getGalleryFromWeb is the webserivce that get
// all the images from web. There is no issue in that webservice
}
}
[galleryView reloadData];
}
//It always loads failure code below
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error Applying Filters"
message:#"Check Your Internet Connection"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}];
}
So what should I write under `else if' so that if no filter value is selected it load all the images again just like when the app loads. I hope I've cleared my problem as it is my first question so if there is anything that I miss I'm ready to provide.
When you got selection at that time check your filtered Array count and if it is >0 then don't call any web-service.
In this way your previously loaded images will not refresh.Just call filtered webservice only when arrays count is greater than >0 and after that reload your data.
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);
}
}];
}
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).
I have a UITableView that get its data from an array and the array contains the filenames of a directory.
I am trying to make user edit filename on row selection.
My code is:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self editChattWithName:[self.listArray objectAtIndex:indexPath.row] atIndex:indexPath];
[self.tabView deselectRowAtIndexPath:indexPath animated:YES];
}
- (void)editChattWithName:(NSString*)name atIndex:(NSIndexPath *)indexPath {
UIAlertView* editAlert = [[UIAlertView alloc]
initWithTitle:nil
message:#"Edit FileName"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Update", nil];
[editAlert setAlertViewStyle:UIAlertViewStylePlainTextInput];
UITextField* nameField = [editAlert textFieldAtIndex:0];
[nameField setPlaceholder:#"New FileName"];
[nameField setText:name];
[editAlert show];
[editAlert release];
NSString *newFileName = nameField.text;
[editAlert showWithCompletion:^(UIAlertView *alertView, NSInteger buttonIndex) {
if (buttonIndex == 0) { }
else if (buttonIndex == 1) {
NSError *error;
// Edit filename inside directory
[fm moveItemAtPath:[NSString stringWithFormat:#"%#%#",directory,name] toPath:[NSString stringWithFormat:#"%#%#",directory,newFileName] error:&error];
// Update value inside array
[self.listArray replaceObjectAtIndex:indexPath.row withObject:newChatName];
// reload table data to show new filename
[self.tabView reloadData];
NSLog(#"Old Filename: %#%#",directory,name);
NSLog(#"New Filename: %#%#",directory,newFileName);
NSLog(#"Error: %#",error);
}
}];
}
The issue is that name and newFileName are having the same value name and that is resulting an error with NSFileManager saying that the file already exists.
I have tried removing [nameField setText:name] but the problem was still there.
I am out of luck and not able to find the issue, your help is much appreciated.
Well, if you already know that the method moveItemAtPath:toPath: is causing the error only in case that the old and the new file name are identical then it should be quite easy to catch this error:
if (![newFileName isEqualToString:name]) {
[fm moveItemAtPath:[NSString stringWithFormat:#"%#%#",directory,name] toPath:[NSString stringWithFormat:#"%#%#",directory,newFileName] error:&error];
}
Now your file will only me moved (i.e. renamed) when the new file name differs from the old name.
Edit:
Furthermore, if you want to get the new file name that the user just entered in the alert view you should put this line:
NSString *newFileName = nameField.text;
in your completion block. Otherwise it will be set when the alert view is first displayed and hence have its initial value. To put it all together:
[editAlert showWithCompletion:^(UIAlertView *alertView, NSInteger buttonIndex) {
if (buttonIndex == 1) {
NSString *newFileName = nameField.text;
NSError *error;
// Edit filename inside directory
if (![newFileName isEqualToString:name]) {
[fm moveItemAtPath:[NSString stringWithFormat:#"%#%#",directory,name] toPath:[NSString stringWithFormat:#"%#%#",directory,newFileName] error:&error];
}
// Update value inside array
[self.listArray replaceObjectAtIndex:indexPath.row withObject:newChatName];
// reload table data to show new filename
[self.tabView reloadData];
}
}];
Supplement:
In order to not confuse other users it should be noted that showWithCompletion: is not a native UIAlertView method. An Objective-C category has been created to extend UIAlertView with this method. An example can be found here.
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.