iOS and Objective-C: How to keep an object globally - objective-c

Developing an app for iOS, I need to know how to have instanced and available an object created when user authenticates.
I am using OAuth2 method properly implementing gtm-oauth2 framework. The user entries, sees the login form displayed in a web view and correctly authenticates. In that moment, as detailed in the documentation, I go like this:
if (error != nil){
// Do whatever to control the error
}
else
{
// Authentication succeeded
// Assign the access token to the instance property for later use
self.accessToken = myAuth.accessToken;
[myAuth setShouldAuthorizeAllRequests:YES];
[self setAuth:myAuth];
// Display the access token to the user
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Authorization Succeeded"
message:[NSString stringWithFormat:#"Access Token: %#", myAuth.accessToken]
delegate:self
cancelButtonTitle:#"Dismiss"
otherButtonTitles:nil];
[alertView show];
}
Later, in the same controller, I use the self.auth object like this to access my API once the user has authenticated:
[request setURL:getCartsURL];
[request setValue:self.accessToken forHTTPHeaderField:#"Authorization"];
[self.auth authorizeRequest:request
completionHandler:^(NSError *error) {
NSString *output = nil;
if (error) {
output = [error description];
} else {
// Synchronous fetches like this are a really bad idea in Cocoa applications
//
// For a very easy async alternative, we could use GTMHTTPFetcher
NSURLResponse *response = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (data) {
// API fetch succeeded
output = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
SBJsonParser *jsonParser = [SBJsonParser new];
// Parse the JSON into an Object
id parsed = [jsonParser objectWithString:output];
NSArray *arrayResponse = [[NSArray alloc] initWithArray:parsed];
} else {
// fetch failed
output = [error description];
}
}
}];
So far, I have been using a local instance of self.auth object, what happens to be insufficient if I want to have that object globally accessed from any point of the whole app. Ok for the init view controller, but not for the whole app.
I think I can somehow access this first view controller to get the object anytime I want it. But I guess we have better methods to have it globally instanced and accessible from any point of the app.
Can you please help me with this?
Thanks a lot.

You should use a Singleton. Here is a nice article on how to set one up.

You could change the [self setAuth:myAuth]; of that ViewController to set an object on the AppDelegate. Create it there and set it, then you'll be able to access it from anywhere.
[[UIApplication sharedApplication] delegate] will give you a pointer to your app delegate, the one that was automatically created when you made the project.

Related

Passing delegate through another object with ARC

I've got 2 classes, MPRequest and MPModel.
The MPModel class has a method to lookup something from the core data store, and if not found, creates an MPRequest to retrieve it via a standard HTTP request (The method in MPModel is static and not and instance method).
What I want is to be able to get a progress of the current HTTP request. I know how to do this, but I'm getting a little stuck on how to inform the view controller. I tried creating a protocol, defining a delegate property in the MPRequest class, altering the method in MPModel to accept this delegate, and in turn passing it to the MPRequest when it is created.
This is fine, however ARC is then releasing this delegate whilst the request is running and thus doesn't do what I want. I'm trying to avoid making my delegate object a strong reference in case it throws up any reference cycles but I don't know any other way of doing this.
To start the request, from my view controller I'm running
[MPModel findAllWithBlock:^(NSFetchedResultsController *controller, NSError *error) {
....
} sortedBy:#"name" ascending:YES delegate:self]
Inside the findAllWithBlock method, I have
MPRequest *objRequest = [MPRequest requestWithURL:url];
objRequest.delegate = delegate;
[objRequest setRequestMethod:#"GET"];
[MPUser signRequest:objRequest];
[objRequest submit:^(MPResponse *resp, NSError *err) {
...
}
And in the MPRequest class I have the following property defined :
#property (nonatomic, weak) NSObject<MPRequestDelegate> *delegate;
Any ideas or suggestions?
As requested, here is some more code on how things are being called :
In the view controller :
[MPPlace findAllWithBlock:^(NSFetchedResultsController *controller, NSError *error) {
_placesController = controller;
[_listView reloadData];
[self addAnnotationsToMap];
[_loadingView stopAnimating];
if (_placesController.fetchedObjects.count > 0) {
// We've got our places, but if they're local copies
// only, new ones may have been added so just update
// our copy
MPSyncEngine *engine = [[MPSyncEngine alloc] initWithClass:[MPPlace class]];
engine.delegate = self;
[engine isReadyToSync:YES];
[[MPSyncManager sharedSyncManager] registerSyncEngine:engine];
[[MPSyncManager sharedSyncManager] sync];
}
} sortedBy:#"name" ascending:YES delegate:self];
Here, self is never going to be released for obvious reasons, so I don't see how this is the problem.
Above, MPPlace is a subclass of MPModel, but the implementation of the findAllWithBlock:sortedBy:ascending:delegate: is entirely in MPModel
The method within MPModel looks like this
NSManagedObjectContext *context = [[MPCoreDataManager sharedInstance] managedObjectContext];
[context performBlockAndWait:^{
__block NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([self class])];
[request setSortDescriptors:#[[[NSSortDescriptor alloc] initWithKey:key ascending:asc]]];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:nil];
[controller performFetch:&error];
if (!controller.fetchedObjects || controller.fetchedObjects.count == 0) {
// Nothing found or an error, query the server instead
NSString *url = [NSString stringWithFormat:#"%#%#", kMP_BASE_API_URL, [self baseURL]];
MPRequest *objRequest = [MPRequest requestWithURL:url];
objRequest.delegate = delegate;
[objRequest setRequestMethod:#"GET"];
[MPUser signRequest:objRequest];
[objRequest submit:^(MPResponse *resp, NSError *err) {
if (err) {
block(nil, err);
} else {
NSArray *objects = [self createListWithResponse:resp];
objects = [MPModel saveAllLocally:objects forEntityName:NSStringFromClass([self class])];
[controller performFetch:&error];
block(controller, nil);
}
}];
} else {
// Great, we found something :)
block (controller, nil);
}
}];
The delegate is simply being passed on to the MPRequest object being created. My initial concern was that the MPRequest object being created was being released by ARC (which I guess it probably is) but it didn't fix anything when I changed it. I can't make it an iVar as the method is static.
The submit method of the request looks like this :
_completionBlock = block;
_responseData = [[NSMutableData alloc] init];
[self prepareRequest];
[self prepareRequestHeaders];
_connection = [[NSURLConnection alloc] initWithRequest:_urlRequest
delegate:self];
And when the app starts downloading data, it calls :
[_responseData appendData:data];
[_delegate requestDidReceive:(float)data.length ofTotal:_contentLength];
Where _contentLength is simply a long storing the expected size of the response.
Got it working. It was partly an issue with threading, where the core data thread was ending before my request, me looking at the output from a different request entirely, and the way ARC handles memory in blocks.
Thanks for the help guys

how to display connection error when link is clicked from a cached page in UIWebView?

I'm working on an app with a UIWebView that goes to a single page -- we'll call it http://myPage.com -- that has a few links on it. Displaying a UIAlertView for a connection error when the user first clicks the go-to-myPage button that leads to http://myPage.com is no problem. But if there's no connection and the user clicks the go-to-myPage button, gets no UIAlertView because the cached version of http://myPage.com is returned, and then tries to follow a link from http://myPage.com, nothing happens. How can I rewrite or add to my code to display an alert in such a situation?
Here's my code:
-(void)loadWebViewWithbaseURLString:bus withURLString:us
{
self.request = [NSURLRequest requestWithURL:[NSURL URLWithString:us] cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval: 20];
self.connection=[[NSURLConnection alloc] initWithRequest:self.request delegate:nil];
NSError *error=nil;
NSURLResponse *response=nil;
if (self.connection)
{
self.urlData = [ NSURLConnection sendSynchronousRequest: self.request returningResponse:&response error:&error];
NSString *htmlString = [[NSString alloc] initWithData:self.urlData encoding:NSUTF8StringEncoding];
[self.webV loadHTMLString:htmlString baseURL:[NSURL URLWithString:bus]];
}
else
{
UIAlertView *alert = [[UIAlertView alloc] init];
alert.title = #"Unfortunately, I seem to be having a hard time connecting to the Internet. Would you mind trying again later? I'll make it worth your while, I promise.";
[alert show];
}
}
And of course the go-to-myPage code simply calls -(void)loadWebViewWithbaseURLString:bus withURLString:us with the specific URLs as arguments.
I asked the question in a clearer (I hope) way at NSURLRequest cachePolicy and connection in UIWebView and was ultimately able to figure it out myself, so I posted the answer....

NSURLConnection is not calling didFailWithError.

I am attempting to write a bit of code that checks the URL of a datasource, then populates an array with objects from that URL. It actually works well, but if there is a problem with the web connection or the address I want to populate the array with data from a bundled file. The issue I am having is that the connection didFailWithError method is never called. I tried passing a simple string but it does not call. I want the app to still function for people who are using ipod touch or are in airplane mode.
connection didReceiveResponse is working without issue.
This is what I'm working with.
- (void)loadListData{
NSLog(#"Loading data from sources");
NSURLRequest *listURLRequest = [NSURLRequest requestWithURL:integerPhoneListURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:1.0];
[[NSURLConnection alloc] initWithRequest:listURLRequest delegate:self];
if (!listConnectFail){
phoneListJSON =[NSData dataWithContentsOfURL:integerPhoneListURL];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:phoneListJSON waitUntilDone:YES];
} else {
//This will tell us if there is an error loading the file
NSLog(#"File not found on web init from file");
phoneListJSON =[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"contactlist" ofType:#"json"]];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:phoneListJSON waitUntilDone:YES];
}
//Initialize the filtered list with array of customer objects. Based on original data
filteredList = [[NSMutableArray alloc] init];
for (NSDictionary *dict in phoneListOriginal) {
contact *single = [[contact alloc] init];
single.fName = [dict objectForKey:#"fName"];
single.lName = [dict objectForKey:#"lName"];
single.extension = [dict objectForKey:#"extension"];
single.title = [dict objectForKey:#"title"];
single.department = [dict objectForKey:#"department"];
single.cellNumber = [dict objectForKey:#"cellNumber"];
//NSLog(#"%#", single.lName);
[filteredList addObject:single];
}
NSLog(#"Array filteredLIst contains %d records",[filteredList count]); }
-(void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
listConnectFail = YES;
NSLog(#"Connection Failed, pulling from file"); }
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
listConnectFail = NO;
NSLog(#"Connection Succeeded, populating from API");
}
I know it is probably something stupid that I am not seeing, but I could use the help to see what I don't
Thanks in advance!
How did you confirm that your delegate did not receive the message? Did you check the log?
Your code seems to assume that 'listConnectFail' will be set immediately after the NSURLConnection's init is done, which is not necessarily the case.
[[NSURLConnection alloc] initWithRequest:listURLRequest delegate:self];
if (!listConnectFail){...}
The NSURLConnection documentation states that 'The delegate will receive delegate messages as the load progresses.'
However, I am not sure about the airplane mode, maybe this particular error can be detected synchronously.

objective-c: returning data from server

The following are methods that I am using to retrieve data from a server while displaying a UIActivityIndicator. I'm trying to put these methods in the app delegate and then call them from other classes, but I don't know how to return my JSONData. Can anybody help with this?
-(void)startProcess:(NSString *)buildURL{
UIActivityIndicatorView *aInd = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActionSheetStyleBlackTranslucent];
[aInd setFrame:CGRectMake(0, 0, 50, 50)];
[aInd startAnimating];
// then call the timeCOnsumingmethod in separate thread.
[NSThread detachNewThreadSelector:#selector(getData:) toTarget:self withObject:buildURL];
}
- (void)getData:(NSString *)buildURL{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Query our database for a restaurant's menus
NSURL *url = [NSURL URLWithString:buildURL];
NSError *e;
NSString *jsonreturn = [[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&e];
NSData *jsonData = [jsonreturn dataUsingEncoding:NSUTF32BigEndianStringEncoding];
// NSError *error = nil;
[self performSelectorOnMainThread:#selector(endProcess:) withObject:jsonData waitUntilDone:YES];
[pool release];
//return jsonData;
}
- (IBAction)endProcess:(NSData *)jsonData{
// ??????????
return jsonData;
}
Not sure why got downvoted but your approach is all wrong. Here's what you want to do:
Add the UIActivityIndicatorView
Use NSURLConnection to asynchronously retrieve the data
Use NSJSONSerialization to decode the received JSON into a NSDictionary or NSArray
Remove the UIActivityIndicatorView
Your best bet would be to implement this as a separate class that takes a delegate object. You could implement a delegate protocol to indicate states like 'started network activity' (which your delegate could use to add a spinner view), and 'received data' (which would pass the decoded object back to the delegate - the delegate could then remove the spinner).
One of the benefits of this approach is you can easily set it up so that the connection/request is canceled when the object deallocs. Then you just store the request object as a property on your delegate, and when your delegate goes away, it deallocs the request, which cancels/cleans up properly.

How to remove a core data persistent store

I need to delete my persistent store (doing it object by object is not practical because I have over 100,000 objects). I've tried this:
- (IBAction)resetDatabase:(id)sender {
NSPersistentStore* store = [[__persistentStoreCoordinator persistentStores] lastObject];
NSError *error = nil;
NSURL *storeURL = store.URL;
// release context and model
[__managedObjectContext release];
[__managedObjectModel release];
__managedObjectModel = nil;
__managedObjectContext = nil;
[__persistentStoreCoordinator removePersistentStore:store error:nil];
[__persistentStoreCoordinator release];
__persistentStoreCoordinator = nil;
[[NSFileManager defaultManager] removeItemAtPath:storeURL.path error:&error];
if (error) {
NSLog(#"filemanager error %#", error);
}
// recreate the stack
__managedObjectContext = [self managedObjectContext];
}
But I get this error when I try to insert entities into the store afterwards:
This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation.
Update:
I tried releasing the MOC and MOM before removing the persistent store but I still get the same error.
Here is how I do a "reset data" function in several apps:
- (void)reset {
// Release CoreData chain
[_managedObjectContext release];
_managedObjectContext = nil;
[_managedObjectModel release];
_managedObjectModel = nil;
[_persistentStoreCoordinator release];
_persistentStoreCoordinator = nil;
// Delete the sqlite file
NSError *error = nil;
if ([fileManager fileExistsAtPath:_storeURL.path])
[fileManager removeItemAtURL:_storeURL error:&error];
// handle error...
}
Basically I just release the CoreData chain, then delete the persistentStore file. That's what you are trying to do, without using removePersistentStore, which I do not care since I will just rebuild the persistentStore coordinator later. Then at next core data call the chain is rebuilt transparently using singleton-lazy-style constructors like :
- (NSManagedObjectModel *) managedObjectModel {
if (!_managedObjectModel)
_managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
return _managedObjectModel;
}
You can do it externally given that you only need to do this while developing your application. I have a terminal open in which I remove the store manually before re-running my app. All you need to know is where it is located. I log it to console everytime my app runs with the following code:
[[CoreDataSingleton sharedManager] managedObjectContext]; //be sure to create the store first!
//Find targeted mom file in the Resources directory
NSString *momPath = [[NSBundle mainBundle] pathForResource:#"Parking" ofType:#"mom"];
NSLog(#"momd path: %#",momPath);
Hope that helps!
You need to make sure that any managed object context attached to the persistent store have been released before you try to delete the store. Otherwise, the context will evoke that error.