In one of my view controllers I am setting a label based on the "GET" data I receive from a separate NSObject class. Obviously it takes much less time to set the label then it does to fetch the data so the label is always set to nil. How can I insure the label isn't set till the data is done fetching.
This is the method preforming the "getting" in the NSObject class myClass
- (void) doGetURL:(NSString *) urlstring
callBackTarget:(id) target
callBackMethod:(NSString *) method
failedMethod:(NSString *) method_failed
{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:urlstring]];
NSLog(#"-- get URL with cookie : [%#] and hash:(%#)", [self cookie], [self modhash]);
if (cookie && [cookie length] > 0)
{
NSDictionary *properties = [NSDictionary dictionaryWithObjectsAndKeys:
cookieDomain, NSHTTPCookieDomain,
#"/", NSHTTPCookiePath,
#"reddit_session", NSHTTPCookieName,
cookie, NSHTTPCookieValue,
// [cookie stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], NSHTTPCookieValue,
nil];
NSHTTPCookie *http_cookie = [NSHTTPCookie cookieWithProperties:properties];
NSArray* cookies = [NSArray arrayWithObjects: http_cookie, nil];
NSDictionary * headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
[request setAllHTTPHeaderFields:headers];
}
NSURLConnection * connection = [NSURLConnection connectionWithRequest:request delegate:self];
NSString *connectionKey = [NSString stringWithFormat: #"%ld", ((intptr_t) connection)];
NSMutableDictionary *dl = [[NSMutableDictionary alloc] init];
[dl setValue:connectionKey forKey:#"connectionKey"];
if (target && method)
{
[dl setValue:target forKey:#"afterCompleteTarget"];
[dl setValue:method forKey:#"afterCompleteAction"];
}
[dl setValue:method_failed forKey:#"failedNotifyAction"];
[connections setValue:dl forKey:connectionKey];
}
That is being called in another method within myClass
- (void)getUserInfo:(NSString*)user
{
NSString *getString = [NSString stringWithFormat:#"%#/user/%#/about.json",server,user];
[self doGetURL:getString callBackTarget:self callBackMethod:#"userInfoResponse:" failedMethod:#"connectionFailedDialog:"];
}
The call back method:
- (void)userInfoResponse:(id)sender
{
NSLog(#"userInfoResponse in()");
NSData * data = (NSData *) sender;
NSError *error;
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:&error];
NSDictionary *response = [json objectForKey:#"data"];
//futureLabelStr is a property of myClass
futureLabelStr = [response objectForKey:#"name"];;
}
then the label is set in the View Controller:
- (void)viewDidLoad
{
[myClass getUserInfo:#"some_user"];
myLabel.txt = myClass.futureLabelStr;
}
Please let me know is I need to add more or anything I tried to organize it as best I could but I might have missed something.
You don't want to "halt" your viewController's viewDidLoad, you want to notify it, when
the information changes.
You could do that by either sending a notification when myClass is done and -userInfoResponse: is called (Look at NSNotificationCenter), or implement a delegate pattern in myClass. You could set your viewController as a delegate for myClass and call a delegate method when myClass is finished fetching on viewController that would itself update the label.
Or, looking at your code, you could set your viewController as the receiver of the callback methods with minimal change to your code, even though that is not the best approach because it violates MVC patterns:
[self doGetURL:getString callBackTarget:viewController callBackMethod:#"userInfoResponse:" failedMethod:#"connectionFailedDialog:"];
You would of course need a reference to viewController in myClass and the viewController would need to implement this methods (which is a MVC pattern violation).
Send the data call on a new thread and finish viewDidLoad as normal. Then use NSNotification center from whoever is fetching this (should be a model) to the viewController saying "hey, I got that label for you, come get it and refresh"
Then the VC will just set the label using the data from the model. Check out this link for using NSNotificationCenter stackoverflow.com/questions/2191594/send-and-receive-messages-through-nsnotificationcenter-in-objective-c.
For multithreading read up on grand central dispatch.
Related
New to iOS development. So I passed a method with an object address, thinking that I would be altering the same object by doing so:
_posts = [[NSMutableArray alloc]init];
FetchPosts *dataControl = [[FetchPosts alloc]init];
[dataControl accessPosts: _posts];
And the below code receives the passed in object.
-(void)accessPosts: (NSMutableArray *)transition {
//access the posts here.
_readablePosts = transition;
NSURL *url = [NSURL URLWithString: #"private"];
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
_jsonPosts = [[NSMutableData alloc]init];
[_jsonPosts appendData: data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError *error;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:_jsonPosts options:NSJSONReadingAllowFragments error:&error];
for(int i = 0; i<jsonArray.count; i++) {
NSDictionary *element = jsonArray[i];
Posts *newPost = [[Posts alloc]init];
newPost.title = element[#"Title"];
newPost.discovery = element[#"Discovery"];
newPost.summary = element[#"Description"];
newPost.contact = element[#"Contact"];
[_readablePosts addObject:newPost];
}
}
From my perspective, everything seems to be in place properly. However, when I return to my original method, the _posts does not hold the correct object as it should. Am I doing something wrong with my pointers here?
You call this method -
[dataControl accessPosts: _posts];
Which invokes
[NSURLConnection connectionWithRequest:request delegate:self];
This is a non-blocking method, so it returns immediately and calls the delegate methods once it has retrieved data to be processed.
If you access _posts as soon accessPosts returns you will be doing so before the data has been retrieved from the network.
Only after connectionDidFinishLoading: has been called can you access the data in _posts
One solution is to use a delegation pattern. Your outer code would set itself as a delegate to the FetchPosts object, with the delegate method being called from connectionDidFinishLoading. Not only will this address your asynchronous issue it avoid potentially unsafe updating of the NSMutableArray and avoids the use of side-effects.
You will need to create an appropriate protocol in FetchPosts.h but then you can use something like -
-(void) requestPosts {
FetchPosts *dataControl = [[FetchPosts alloc]init];
dataControl.delegate=self;
[dataControl accessPosts];
}
-(void) didReceiveNewPosts:(NSArray *)posts {
// Do something with posts
}
FetchPosts.m
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError *error;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:_jsonPosts options:NSJSONReadingAllowFragments error:&error];
for(int i = 0; i<jsonArray.count; i++) {
NSDictionary *element = jsonArray[i];
Posts *newPost = [[Posts alloc]init];
newPost.title = element[#"Title"];
newPost.discovery = element[#"Discovery"];
newPost.summary = element[#"Description"];
newPost.contact = element[#"Contact"];
[_readablePosts addObject:newPost];
}
if ([self.delegate respondsToSelector:#selector(didReceiveNewPosts:)]) {
[self.delegate didReceiveNewPosts:_readablePosts];
}
}
I think that the json is empty, the method didReceiveData is called each time that the device obtain a chunk of data, this method can be called more times during a downloading. If you create a new mutable data and append data is like you are resetting the NSDAta object.
You should create the Json post object in didReceiveresponse.
I have a method in my "MasterView" () class that parses .json data from a URL then populates a table view with the information. In order to be more organized and group the method with other needed methods I attempted to move it into another NSOject class but it didn't work; no errors, no exceptions the table view simply doesn't populate.
Here is the original method in the "Master Class"
- (void) fetchPosts:
{
NSError *error;
NSData *responseData = [NSData dataWithContentsOfURL:myURL];
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSArray *objects = [[json objectForKey:#"data"] objectForKey:#"children"];
arr = [[NSMutableArray alloc] init];
for (NSDictionary *object in objects) {
NSString *title = [[object objectForKey:#"data"] objectForKey:#"title"];
//Post is just a random NSObject Class
Post *post = [[Post alloc] init];
post.title = title;
[arr addObject:post];
}
NSLog(#"Called");
[self.tableView reloadData];
}
The Edited Method in the other class:
- (void) fetchPosts:(NSURL *)myURL withPostArray:(NSMutableArray*)postArr andTableView: (UITableView*)tableView
{
NSLog(#"CAlled");
NSError *error;
NSData *responseData = [NSData dataWithContentsOfURL:myURL];
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSArray *objects = [[json objectForKey:#"data"] objectForKey:#"children"];
postArr = [[NSMutableArray alloc] init];
for (NSDictionary *object in objects) {
NSString *title = [[object objectForKey:#"data"] objectForKey:#"title"];
Post *post = [[Post alloc] init];
post.title = title;
[postArr addObject:post];
}
[tableView reloadData];
}
The original Method that works is called: [self fetchPosts:]; the other is: [MyClass fetchPosts:myUrl withPostArray:arr andTableView:self.tableView];
I edited some information out to make it more readable so please let me know if there is any mistakes.
MyClass.h:
#interface MyClass : NSObject <UITableViewDelegate, UITableViewDataSource>
Setting the datasource in MasterView:
//In ViewDidLoad
_delegate = myClass;
self.tableView.dataSource = _delegate;
self.tableView.delegate = _delegate;
//In .h
#property (strong, nonatomic) MyClass *delegate;
Im getting nothing from the compiler when I call [MyClass fetchPosts:myUrl withPostArray:arr andTableView:self.tableView];
If the table view doesn't populate, then the table view is not getting the needed data through the data source.
It's possible that you didn't set the dataSource of your tableView to the new NSObject you created, or that MasterView is still the dataSource of the tableView.
Also, make sure that this method is actually called and the passed tableView is the one presented in the view.
Edit: You have three solutions:
Assign the data source to the new object you created so it handles updating the table view with data, since it now has the actual data.
Adjust that method to return the parsed data to the MasterView and it calls [self.tableView reloadData]. But this is not really good from MVC's point of view.
The third option requires you to create a UIVieController to handle your MasterView and it should be the dataSource for the table view. The view controller should call the said method from the new object, to retrieve the data and update the table view. i.e. like the 2nd solution, but a view controller will call the method and not the MasterView.
I want to create a table view slide menu like FB or Linkedin, I mean dynamically, so I have to make some requests at the same time. I am using AFNetworking. with a custom AFHTTTPClient which its called YPLHTTPClients and it is a AFHTTPClient subclass. In this class I have two methods. SharedClient and initWithBaseURL.
I also want to use enqueueBatchOfHTTPRequestOperationsWithRequests method in this class to return data to the viewController and create the tableView of my menu.
I would like to call a method who uses enqueueBatchOfHTTPRequestOperationsWithRequeststhis function in the client and return a dictionary or something with my information data, instead of do everything in the ViewController like I do here:
NSMutableArray *mutableRequests = [NSMutableArray array];
for (NSString *URLString in [NSArray arrayWithObjects:#"users", #"intProjects", nil]) {
[mutableRequests addObject:[[YPLHTTPClient sharedHTTPClient] requestWithMethod:#"GET" path:URLString parameters:nil]];
}
__block NSDictionary *parsedObject1, *parsedObject2;
[[YPLHTTPClient sharedHTTPClient] enqueueBatchOfHTTPRequestOperationsWithRequests:mutableRequests progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"%lu of %lu Completed", (unsigned long)numberOfCompletedOperations, (unsigned long)totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
NSError *thisError;
parsedObject1 = [NSJSONSerialization JSONObjectWithData:[[operations objectAtIndex:0] responseData] options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments error:&thisError];
NSLog(#"Completion: %#", parsedObject1 );
parsedObject2 = [NSJSONSerialization JSONObjectWithData:[[operations objectAtIndex:1] responseData] options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments error:&thisError];
NSLog(#"Completion: %#", parsedObject2 );
I also would like to know, how can I show and Image while I am downloading this data.
Thank you
I'm not entirely sure what you're asking, but you can use something like MBProgressHUD to show a progress bar that's updated in progressBlock (progress = numberOfCompletedOperations / totalNumberOfOperations).
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSError *error = nil;
NSURL *urlRequest = [NSURL URLWithString:[NSString stringWithFormat:#"...", URL]];
NSString *json = [NSString stringWithContentsOfURL:urlRequest
encoding:NSASCIIStringEncoding
error:&error];
JKParseOptionFlags options = JKParseOptionStrict;
NSDictionary *results = [json objectFromJSONStringWithParseOptions:options];
NSString *body = [[results objectForKey:#"item"] objectForKey:#"description"];
Article *article = [[Article alloc] initWithTitle:title URL:URL body:body];
[self.articles insertObject:article atIndex:0];
});
Right outside of that I have [self.tableView reloadData]; but if I call NSLog(#"%d", self.articles.count); right after that it returns 0. Why is it not adding it? If I call an NSLog inside that block accessing article's body property it will print it, so the object seemingly gets created fine. And yes, the method that this is in does get called (by viewDidLoad).
The body gets executed asynchronously, so it doesn't start running that body until some time after your function is done. So all you do is put some code on a queue (which will not be run until later), and check if the article got added to the list (which it won't, until later).
If you check inside the code, that is actually checking a while later, when the queue is done running the code...
I can't tell you more without seeing the declaration of your articles object but what this usually means is that the NSMutableArray object you're trying to use is nil. At the same time that you're logging the article's body property, try logging the articles object as well. If you declared your array as
NSMutableArray *articles;
Then it won't work - articles is still nil and can't accept objects. Declare/instantiate using one of the following options:
NSMutableArray *articles = [NSMutableArray array];
or
NSMutableArray *articles = [[NSMutableArray alloc] init];
I have a controller that is the root of a workflow. If there is no data object for the workflow, then I create a new one, and if there is, I use the existing one. I have a property for the model object (an NSManagedObject)
#property (nonatomic, retain) Round *currentRound;
and I call the following whenever the corresponding view is shown
self.currentRound = [self.service findActiveRound];
if (!self.currentRound) {
NSLog((#"configure for new round"));
self.currentRound = [self.service createNewRound];
...
} else {
NSLog(#"configure for continue");
// bad data here
}
The problem is at the place marked in the above, sometimes the data object is corrupted. In the parts I didn't show I set the values on some text fields to represent the values in the model. Sometimes its ok, but eventually the properties on the model object are empty and things break
In the debugger, the reference to the round doesn't appear to change, but NSLogging the relevant properties shows them nullified. debugging seems to delay the onset of the corruption.
I am aware I am not saving the context...should that matter? And if so, how come it doesn't always fail the first time I come back to this controller?
My findActiveRound message is nothing special, but in case it matters
-(Round *) findActiveRound
{
NSLog(#"Create Active Round");
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Round" inManagedObjectContext:context];
[request setEntity:entity];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"isComplete == %#", [NSNumber numberWithBool:NO]];
[request setPredicate:pred];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if ([results count] == 0) {
return nil;
} else {
return [results objectAtIndex:0];
}
}
Many thanx.
EDIT FOR RESPONSE
By corrupted I mean when I try to get some simple string properties off the model object, I get nil values. So In the code above (where I think I have a round) I do stuff like
self.roundName.text = self.currentRound.venue;
self.teeSelection.text = self.currentRound.tees;
and don't see the data I entered. Since it only fails sometimes, but always fails eventually, I will see the data I entered for a while before its gone.
I'm pretty sure the context is the same. My service is a singleton and created like so
#implementation HscService
+(id) getInstance
{
static HscService *singleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [[self alloc] init];
});
return singleton;
}
-(id) init
{
if (self = [super init]) {
model = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSString *path = [self itemArchivePath];
NSURL *storeUrl = [NSURL fileURLWithPath:path];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES],
NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES],
NSInferMappingModelAutomaticallyOption, nil];
NSError *error = nil;
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
[NSException raise:#"Open failed" format: #"Reason: %#", [error localizedDescription]];
}
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:psc];
[context setUndoManager:nil];
}
return self;
}
-(NSString *) itemArchivePath
{
NSArray *docDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *dir = [docDirectories objectAtIndex:0];
return [dir stringByAppendingPathComponent:#"hsc1.data"];
}
and in every controller I get the singleton to perform operations. I plan on defining a delegate around round operations, and implementing it in my AppDelegate, so I'm only getting the service once in the app, but don't think that should matter for now....
Are you sure the data is actually corrupted? Managed object contexts are highly efficient, and it's normal to fault in the debugger. From the docs:
"Faulting is a mechanism Core Data employs to reduce your
application’s memory usage..."
See http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdFaultingUniquing.html
If the data is actually missing and cannot be accessed by other methods, make sure you're using the same Managed Object Context to access the data. If the data has not been committed to the data store, it will not "sync" between MOCs.