Unable to maintain session in AFNetworking client IOS 5 / 6 - iphone

Hi I am using AFNetworking in my IOS app and I am unable to maintain session. I am using an AFNetworking client and using it in all requests to the server.
I have gone through the following questions: How to manage sessions with AFNetworking , but I don't plan to manipulate the cookies or the session. I intend to implement a session throughout the life cycle of the app.
My AFNetworking client's .m is as follows
#implementation MyApiClient
+(MyApiClient *)sharedClient {
static MyApiClient *_sharedClient = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedClient = [[MyApiClient alloc] initWithBaseURL:[GlobalParams sharedInstance].baseUrl];
});
return _sharedClient;
}
-(id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObject:#"text/html"]];
self.parameterEncoding = AFFormURLParameterEncoding;
return self;
}
#end
and I make the following requests to the server on the call of "- (void)searchBarSearchButtonClicked:(UISearchBar *)search" -->>
NSString *path = [NSString stringWithFormat:#"mobile"];
NSURLRequest *request = [[MyApiClient sharedClient] requestWithMethod:#"GET" path:path parameters:#{#"q":search.text}];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request,
NSHTTPURLResponse *response, id json) {
// code for successful return goes here
NSLog(#"search feed %#", json);
search_feed_json = [json objectForKey:#"searchFeed"];
if (search_feed_json.count > 0) {
//<#statements-if-true#>
show_feed_json = search_feed_json;
//reload table view to load the current non-empty activity feed into the table
[self.tableView reloadData];
} else {
//<#statements-if-false#>
NSLog(#"Response: %#", #"no feed");
}
} failure :^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
// code for failed request goes here
NSLog(#"nops : %#", error);
}];
[operation start];
Can anyone please guide me or point out where I am wrong ?
Any help would be appreciated.
Thanks in advance.
Edit ::
I found my answer in the following post : https://stackoverflow.com/a/14405805/1935921
I initialised the methods listed in the answer in my GlobalParams class, which contains all the Global parameters and methods.
I call the "saveCookies" method when the app does the login and the server sends the Cookies.
Then these cookies are loaded every time I make any subsequent request by using the method "loadCookies". The Code looks as follows :
GlobalParams.h
#import <Foundation/Foundation.h>
#interface GlobalParams : NSObject
// Your property settings for your variables go here
// here's one example:
#property (nonatomic, assign) NSURL *baseUrl;
// This is the method to access this Singleton class
+ (GlobalParams *)sharedInstance;
- (void)saveCookies;
- (void)loadCookies;
#end
GlobalParams.m
#import "GlobalParams.h"
#implementation GlobalParams
#synthesize myUserData,baseUrl;
+ (GlobalParams *)sharedInstance
{
// the instance of this class is stored here
static GlobalParams *myInstance = nil;
// check to see if an instance already exists
if (nil == dronnaInstance) {
myInstance = [[[self class] alloc] init];
myInstance.baseUrl = [NSURL URLWithString:#"http://www.test.dronna.com/"];
}
// return the instance of this class
return myInstance;
}
- (void)saveCookies{
NSData *cookiesData = [NSKeyedArchiver archivedDataWithRootObject: [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject: cookiesData forKey: #"sessionCookies"];
[defaults synchronize];
}
- (void)loadCookies{
NSArray *cookies = [NSKeyedUnarchiver unarchiveObjectWithData: [[NSUserDefaults standardUserDefaults] objectForKey: #"sessionCookies"]];
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in cookies){
[cookieStorage setCookie: cookie];
}
}
#end
then I call " [[GlobalParams sharedInstance] saveCookies]; " or " [[GlobalParams sharedInstance] loadCookies]; "(depending on read/write required in cookies) in all my server calls after defining the NSMutableRequest.
I hope this helps somebody.

I found my answer in the following post : https://stackoverflow.com/a/14405805/1935921
I initialised the methods listed in the answer in my GlobalParams class, which contains all the Global parameters and methods. I call the "saveCookies" method when the app does the login and the server sends the Cookies. Then these cookies are loaded every time I make any subsequent request by using the method "loadCookies". The Code looks as follows :
GlobalParams.h
#import <Foundation/Foundation.h>
#interface GlobalParams : NSObject
// Your property settings for your variables go here
// here's one example:
#property (nonatomic, assign) NSURL *baseUrl;
// This is the method to access this Singleton class
+ (GlobalParams *)sharedInstance;
//saving cookies
- (void)saveCookies;
//loading cookies
- (void)loadCookies;
#end
GlobalParams.m
#import "GlobalParams.h"
#implementation GlobalParams
#synthesize myUserData,baseUrl;
+ (GlobalParams *)sharedInstance
{
// the instance of this class is stored here
static GlobalParams *myInstance = nil;
// check to see if an instance already exists
if (nil == dronnaInstance) {
myInstance = [[[self class] alloc] init];
myInstance.baseUrl = [NSURL URLWithString:#"http://www.test.dronna.com/"];
}
// return the instance of this class
return myInstance;
}
- (void)saveCookies{
NSData *cookiesData = [NSKeyedArchiver archivedDataWithRootObject: [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject: cookiesData forKey: #"sessionCookies"];
[defaults synchronize];
}
- (void)loadCookies{
NSArray *cookies = [NSKeyedUnarchiver unarchiveObjectWithData: [[NSUserDefaults standardUserDefaults] objectForKey: #"sessionCookies"]];
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in cookies){
[cookieStorage setCookie: cookie];
}
}

Related

AFNetworking authentication with web service

Having an issue which is more of a design consideration than that of code.
My iOS app interfaces with a json web service. I am using AFNetworking and my issue is basically I need the init function (which authenticates the AFHTTPClient and retrieves a token) to complete entirely before I make any additional requests (that require said token).
From the code below, I would be interested in hearing design approaches to achieving this, I would prefer to keep all requests async an alternative solution would be to make the request in initWithHost:port:user:pass synchronous (not using AFNetworking) which I am aware is bad practice and want to avoid.
DCWebServiceManager.h
#import <Foundation/Foundation.h>
#import "AFHTTPClient.h"
#interface DCWebServiceManager : NSObject
{
NSString *hostServer;
NSString *hostPort;
NSString *hostUser;
NSString *hostPass;
NSString *hostToken;
AFHTTPClient *httpClient;
}
// Designated Initialiser
- (id)initWithHost:(NSString *)host port:(NSString *)port user:(NSString *)user pass:(NSString *)pass;
// Instance Methods
- (void)getFileList;
#end
DCWebServiceManager.m
#import "DCWebServiceManager.h"
#import "AFHTTPClient.h"
#import "AFHTTPRequestOperation.h"
#import "AFJSONRequestOperation.h"
#implementation DCWebServiceManager
- (id)initWithHost:(NSString *)host port:(NSString *)port user:(NSString *)user pass:(NSString *)pass
{
self = [super init];
if (self)
{
hostServer = host;
hostPort = port;
hostUser = user;
hostPass = pass;
NSString *apiPath = [NSString stringWithFormat:#"http://%#:%#/", hostServer, hostPort];
httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:apiPath]];
[httpClient setAuthorizationHeaderWithUsername:hostUser password:hostPass];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:#"authenticate.php" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject){
// Do operations to parse request token to be used in
// all requests going forward...
// ...
// ...
// Results in setting: hostToken = '<PARSED_TOKEN>'
NSLog(#"HostToken: >>%#<<", hostToken);
} failure:^(AFHTTPRequestOperation *operation, NSError *error){
NSLog(#"ERROR: %#", operation.responseString);
}];
[operation start];
}
return self;
}
- (void)getFileList
{
// *************************
// The issue is here, getFileList gets called before the hostToken is retrieved..
// Make the authenticate request in initWithHost:port:user:pass a synchronous request perhaps??
// *************************
NSLog(#"IN GETFILELIST: %#", hostToken); // Results in host token being nil!!!
NSString *queryString = [NSString stringWithFormat:#"?list&token=%s", hostToken];
NSMutableURLRequest *listRequest = [httpClient requestWithMethod:#"GET" path:queryString parameters:nil];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:listRequest success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON){
NSLog(#"SUCCESS!");
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON){
NSLog(#"ERROR!: %#", error);
}];
[operation start];
}
#end
ViewController.m
....
DCWebServiceManager *manager = [[DCWebServiceManager alloc] initWithHost:#"localhost" port:#"23312" user:#"FOO" pass:#"BAR"];
[manager getFileList];
// OUTPUTS
IN GETFILELIST: (nil)
HostToken: >>sdf5fdsfs46a6cawca6<<
....
...
I'd suggest subclassing AFHTTPClient and adding a +sharedInstance and property for the token.
+ (MyGClient *)sharedInstanceWithHost:(NSString *)host port:(NSString *)port user:(NSString *)user pass:(NSString *)pass {
static MyClient *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[... your code from the init ...]
});
return sharedInstance;
}
You can then override enqueueHTTPRequestOperationWithRequest:success:failure to check for the token before enqueueing further operations.
Additionally, you can collect the operations and enqueue them as soon as the token is set by overriding the setter for the property.
Like #patric.schenke said, you can subclass AFHTTPClient if you want to clean up some of your code, but the real issue is that you need to make another request to authenticate (if your token is nil) before making the request to getFileList.
I would recommend using blocks in the same way that AFNetworking is using blocks to remain asynchronous. Move your HTTP call into its own method and call it only when your hostToken is nil:
- (void)getFileList
{
if (self.token == nil) {
[self updateTokenThenWhenComplete:^(void){
// make HTTP call to get file list
}];
} else {
// make HTTP call to get file list
}
}
- (void)updateTokenThenWhenComplete:(void (^))callback
{
//... make HTTP request
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject){
self.token = responseObject.token;
callback();
} failure:^(AFHTTPRequestOperation *operation, NSError *error){
//...
}];
}

Saving ALAsset URL in NSUserDefaults

Hi I have my ALAsset URL save in NSMutableArray,
"ALAsset - Type:Photo, URLs:assets-library://asset/asset.JPG?id=119A0D2D-C267-4B69-A200-59890B2B0FE5&ex‌​t=JPG",
"ALAsset - Type:Photo, URLs:assets-library://asset/asset.JPG?id=92A7A24F-D54B-496E-B250-542BBE37BE8C&ex‌​t=JPG",
"ALAsset - Type:Photo, URLs:assets-library://asset/asset.JPG?id=77AC7205-68E6-4062-B80C-FC288DF96F24&ex‌​t=JPG
I wasnt able to save NSMutableArray in NSUserDefaults due to it having an error Note that dictionaries and arrays in property lists must also contain only property values.
Im thinking of using this :
- (void)encodeWithCoder:(NSCoder *)encoder {
//Encode properties, other class variables, etc
[encoder encodeObject:self.selectedPhotos forKey:#"selectedPhotos"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
//decode properties, other class vars
self.selectedPhotos = [decoder decodeObjectForKey:#"selectedPhotos"];
}
return self;
}
then save and retrieve it with this code:
- (void)saveCustomObject:(MyCustomObject *)obj {
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:obj];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:myEncodedObject forKey:#"myEncodedObjectKey"];
}
- (MyCustomObject *)loadCustomObjectWithKey:(NSString *)key {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [defaults objectForKey:key];
MyCustomObject *obj = (MyCustomObject *)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
return obj;
}
But I somehow dont quite get it, still crashes in my code. Dont know how. And I wasnt able to save it in NSUserDefaults. Hope someone help. Really been having problem with this a while. Hope someone guide me on the right path of saving and retrieving it the right way from NSUserDefaults. Then back to a NSMutableArray.
The NSUserDefaults only takes a restricted set of classes as objects. See the documentation. You must take care only to store values of these types (NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary, and of course it applies recursively) in the dictionary.
To store the URLs in the NSUserDefaults, store them as strings, then read them back as URLs. If you need to have the dictionary in the current format, you may have to transform it before saving it.
- (void) saveMyUrls
{
NSMutableArray* urls = [NSMutableArray arrayWithCapacity:self.myUrls.count];
for(NSURL* url in self.myUrls) {
[urls addObject:[url absoluteString]];
}
[[NSUserDefaults standardUserDefaults] setObject:urls forKey:#"myUrls"];
}
- (void) loadUrls
{
NSArray* urls = [[NSUserDefaults standardUserDefaults] objectForKey:#"myUrls"];
self.myUrls = [NSMutableArray arrayWithCapacity:urls.count];
for(NSString* urlString in urls) {
[self.myUrls addObject:[NSURL URLWithString:urlString]];
}
[[NSUserDefaults standardUserDefaults] setObject:urls forKey:#"myUrls"];
}
If you need to save more information than just the URL, let's say a user-specified label, you could save the object as a NSDictionary instead, e.g.
- (void) saveMyUrlsWithLabels
{
NSMutableArray* objs = [NSMutableArray arrayWithCapacity:self.myObjects.count];
for(MyObject* obj in self.myObjects) {
[objs addObject:[NSDictionary dictionaryWithKeys:#"url", #"label"
forObjects:obj.url.absoluteString, obj.userSpecifiedLabel];
}
[[NSUserDefaults standardUserDefaults] setObject:objs forKey:#"myObjects"];
}
Maybe you should do it like this:
- (MyCustomObject *)loadCustomObjectWithKey:(NSString *)key {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults synchronize]; // note this
NSData *myEncodedObject = [defaults objectForKey:key];
MyCustomObject *obj = nil;
// it would be even better
// to wrap this into #try-#catch block
if(myEncodedObject)
{
obj = (MyCustomObject *)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
}
return obj;
}
Also note that if you want to use NSKeyedArchiver and NSKeyedUNarchiver your MyCustomObject class has to conform to NSCoding protocol. Check NSCoding protocol reference and Archives and Serializations Programming Guide.
This is another way to do it and yes you can use NSUserDefaults. Basically you get asset URL, save it and then convert it back to an asset / image
//SET IT
ALAsset *asset3 = [self.assets objectAtIndex:[indexPath row]];
NSMutableString *testStr = [NSMutableString stringWithFormat:#"%#", asset3.defaultRepresentation.url];
//NSLog(#"testStr: %# ...", testStr);
[[NSUserDefaults standardUserDefaults] setObject:testStr forKey:#"userPhotoAsset"];
[[NSUserDefaults standardUserDefaults] synchronize];
//GET IT
NSString *assetUrlStr = [[NSUserDefaults standardUserDefaults] objectForKey:#"userPhotoAsset"];
NSURL* aURL = [NSURL URLWithString:assetUrlStr];
NSLog(#"aURL: %# ...", aURL);
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:aURL resultBlock:^(ALAsset *asset)
{
UIImage *copyOfOriginalImage = [UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage] scale:1.0 orientation:UIImageOrientationUp];
imgVwPortrait.image = copyOfOriginalImage;
}
failureBlock:^(NSError *error)
{
// error handling
NSLog(#"failure-----");
}];

Setting up connection to Facebook not working as expected (iPhone)

I'm trying to create a connection to Facebook however I'm having an issue with the handling of the openUrl.
In the past I've been able to add the following in my app delegate class:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [[viewController facebook] handleOpenURL:url];
}
Which has worked as I would expect. However, this time I have a slightly different situation in the sense that the viewController is loaded elsewhere in the app. To get around this problem I came up with an idea of creating a new class that is responsible for handling the connection, but can also be accessed from the class where I create the Facebook post.
To explain further here is the relevant code in my app delegate class
.m
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
FacebookConnectionHandler *fbConnHandler = [[FacebookConnectionHandler alloc] init];
return [[fbConnHandler facebook] handleOpenURL:url];
}
Then here is the code in the FacebookConnectionHandler class:
.h
#import <Foundation/Foundation.h>
#import "Other_ViewController.h"
#import "Facebook.h"
#interface FacebookConnectionHandler : NSObject <FBSessionDelegate>
{
Other_ViewController *otherView;
Facebook *facebook;
}
#property(nonatomic, strong)Other_ViewController *otherView;
#property(nonatomic, strong)Facebook *facebook;
+ (id)sharedManager;
#end
.m
#import "FacebookConnectionHandler.h"
#implementation FacebookConnectionHandler
#synthesize otherView;
#synthesize facebook;
static FacebookConnectionHandler *mySingleton = nil;
+ (id)sharedManager
{
#synchronized(self)
{
if (mySingleton == nil) mySingleton = [[self alloc] init];
}
return mySingleton;
}
- (void)fbDidLogin
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[facebook accessToken] forKey:#"FBAccessTokenKey"];
[defaults setObject:[facebook expirationDate] forKey:#"FBExpirationDateKey"];
[defaults synchronize];
// Allow the user to create a post
[self.otherView createFacebookPost];
}
#end
Finally... here is the relevant code in the Other_ViewController class (where the post is being created):
.h
#import "FBConnect.h"
#interface Other_ViewController : UIViewController <FBSessionDelegate>
{
Facebook *facebook;
}
#property(nonatomic, retain)Facebook *facebook;
- (void)createFacebookPost;
#end
.m
- (void)createFacebookPost
{
// Create the post
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
#"Blah", #"name",
#"", #"caption",
#"", #"description",
#"http://www.xyz.com", #"link",
#"", #"picture",
nil];
// Post it to the users feed
[facebook dialog:#"feed" andParams:params andDelegate:nil];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
switch (buttonIndex)
{
case kFacebookButton:
{
if (facebook == nil || ![facebook isSessionValid])
{
// Setup Facebook connection
facebook = [[Facebook alloc] initWithAppId:#"1111111111" andDelegate:self];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"]
&& [defaults objectForKey:#"FBExpirationDateKey"])
{
facebook.accessToken = [defaults objectForKey:#"FBAccessTokenKey"];
facebook.expirationDate = [defaults objectForKey:#"FBExpirationDateKey"];
}
// Set the connection handler
FacebookConnectionHandler *fbConnectionHandler = [[FacebookConnectionHandler alloc] init];
fbConnectionHandler.mapView = self;
fbConnectionHandler.facebook = self.facebook;
if (![facebook isSessionValid])
{
NSArray *permissions = [[NSArray alloc] initWithObjects:#"publish_actions", nil];
[facebook authorize:permissions];
}
}
else
{
// Create the post
[self createFacebookPost];
}
break;
}
default:
break;
}
}
I may be going about this in completely the wrong way and have completely overcomplicated the problem, however I'm new to the whole facebook SDK and at this point I'm really stumped. Please can someone offer a solution?
Note: To be clear, the issue is that the method fbDidLogin is not being called, thus the rest of the code doesn't get a chance to run.
You're not using your singleton in the app delegate, you're creating a new instance of your connect handler class :
Instead of
FacebookConnectionHandler *fbConnHandler = [[FacebookConnectionHandler alloc] init];
return [[fbConnHandler facebook] handleOpenURL:url];
try
return [[[FacebookConnectionHandler sharedManager] facebook] handleOpenURL:url];
You're also not using the singleton in your Other_ViewController class.
If you are going to have the singleton architecture pattern you have to remember to always use the sharedManager and never alloc/init a new one :)
I sometimes make init throw an exception to remind me that there is a singleton method.
static FacebookConnectionHandler *mySingleton = nil;
- (id)init {
#throw [NSException exceptionWithName:self.class.description reason:#"Please use the sharedManager, don't make a new one of these!" userInfo:nil];
}
- (id)initInternal {
// Put your real init stuff in here
}
+ (id)sharedManager
{
#synchronized(self)
{
if (mySingleton == nil) mySingleton = [[self alloc] initInternal];
}
return mySingleton;
}
PS Using a separate Facebook class is exactly the way I've done it in apps I've written before - your architecture is fine :) I would also consider making the Facebook connection hander class responsible for making it's own Facebook instance instead of the view controller having to do it :)

How to know if user has updated app or installed a fresh copy?

I will be sending out an update to my app with a new data structure, therefore if a user is updating my app I need to update their current data. So I was wondering how can I programatically tell if the user updated my app or installed a new copy (if a new copy is installed I don't need to update anything) ?
Checking the data structure is a solid solution. I began to worry in my own apps about folks who don't upgrade for several versions. I felt this would lead to a myriad of structure checks. The code I show below determines and stores the version and previous version in the NSUserDefaults. You could code for those varying version difference scenarios if needed.
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
BOOL versionUpgraded;
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"];
NSString *preVersion = [prefs stringForKey:#"appVersion"];
if ([prefs stringForKey:#"appVersion"] != nil) {
//see if version is the same as prior
//if not it is an Upgraded
versionUpgraded = !([preVersion isEqualToString: version]);
} else {
//nil means new install
//This needs to be YES for the case that
//"appVersion" is not set anywhere else.
versionUpgraded = YES;
}
if (versionUpgraded) {
[prefs setObject:version forKey:#"appVersion"];
[prefs setObject:preVersion forKey:#"prevAppVersion"];
[prefs synchronize];
}
That depends on the kind of data structure you're using.
In general, I would advise you against relying on checking your application version: a user using 2.0 might have just upgraded or it might be a new user.
I'd rather check if there's a data structure already, and act accordingly. Assuming that you're using a Sqlite-backed Core Data storage, you can either check whether the .sqlite file exists, or check if there are objects in your storage.
Just save the bundle version somewhere and check if it differs from
[[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleVersion"]]
on each app startup.
I have created a category for this. Just implement the two new delegate calls found in the header. It relies quite heavily on the obj-c runtime libraries, so make sure you are confident with them before using this.
.h
#import <UIKit/UIKit.h>
#protocol UIApplicationDelegate <UIApplicationDelegate>
#optional
- (void) application:(UIApplication *)application willUpdateToVersion: (NSString*) newVersion fromVersion: (NSString*) previousVersion;
- (void) application:(UIApplication *)application didUpdateToVersion: (NSString*) newVersion fromVersion: (NSString*) previousVersion;
#end
#interface UIApplication (Versioning)
#end
.m
#import "UIApplication+Versioning.h"
#import <objc/message.h>
#import <objc/runtime.h>
static NSString* UIApplicationVersionFileName = #"app.ver";
#implementation UIApplication (Versioning)
+ (void) load {
Method original, swizzled;
original = class_getInstanceMethod(self, #selector(setDelegate:));
swizzled = class_getInstanceMethod(self, #selector(swizzled_setDelegate:));
method_exchangeImplementations(original, swizzled);
}
- (void) swizzled_setDelegate: (id<UIApplicationDelegate>) delegate {
IMP implementation = class_getMethodImplementation([self class], #selector(swizzled_application:didFinishLaunchingWithOptions:));
class_addMethod([delegate class], #selector(swizzled_application:didFinishLaunchingWithOptions:), implementation, "B#:##");
Method original, swizzled;
original = class_getInstanceMethod([delegate class], #selector(application:didFinishLaunchingWithOptions:));
swizzled = class_getInstanceMethod([delegate class], #selector(swizzled_application:didFinishLaunchingWithOptions:));
method_exchangeImplementations(original, swizzled);
[self swizzled_setDelegate: delegate];
}
- (BOOL)swizzled_application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Check for a version change
NSError* error;
NSArray* directories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* versionFilePath = [[directories objectAtIndex: 0] stringByAppendingPathComponent: UIApplicationVersionFileName];
NSString* oldVersion = [NSString stringWithContentsOfFile: versionFilePath
encoding: NSUTF8StringEncoding
error: &error];
NSString* currentVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey: #"CFBundleVersion"];
switch (error.code) {
case NSFileReadNoSuchFileError:
{
//Delegate methods will not be called first time
oldVersion = [currentVersion copy];
[currentVersion writeToFile: versionFilePath
atomically: YES
encoding: NSUTF8StringEncoding
error: &error];
break;
}
default:
{
NSLog(#"Warning: An error occured will loading the application version file -> Recreating file");
[[NSFileManager defaultManager] removeItemAtPath: versionFilePath
error: nil];
oldVersion = [currentVersion copy];
[currentVersion writeToFile: versionFilePath
atomically: YES
encoding: NSUTF8StringEncoding
error: &error];
break;
}
}
if( ![oldVersion isEqualToString: currentVersion] ) {
if ([[application delegate] respondsToSelector: #selector(application:willUpdateToVersion:fromVersion:)]) {
objc_msgSend([application delegate], #selector(application:willUpdateToVersion:fromVersion:), currentVersion, oldVersion);
}
[currentVersion writeToFile: versionFilePath
atomically: YES
encoding: NSUTF8StringEncoding
error: &error];
if ([[application delegate] respondsToSelector: #selector(application:didUpdateToVersion:fromVersion:)]) {
objc_msgSend([application delegate], #selector(application:willUpdateToVersion:fromVersion:), currentVersion, oldVersion);
}
}
SEL realSelector = #selector(swizzled_application:didFinishLaunchingWithOptions:);
return (BOOL) objc_msgSend([application delegate], realSelector, application, launchOptions);
}
#end

NDictionary getting autoreleased even after retain or copy

I am using following method to get back an NSDictionary object in ViewDidAppear. But when I attempt to access it in CellForRowAtIndexPath() it is always nil. I have tried adding an extra retain and copy to it, but it still gets released. I have been pulling my hair for 3 hours now. Any help would be appreciated.
Excerpt :
#property(nonatomic, retain) NSDictionary* userInfoObj;
- (void) viewDidAppear:(BOOL)animated
{
[super viewWillAppear:animated];
**//The object has data in it at this point**
self.UserInfoObj = [self getUserInfo];
}
- (NSDictionary*)getUserInfo
{
JsonHelper *helper=[[JsonHelper alloc] autorelease];
NSString* apiURL = [self.appDelegate urlGetUserInfo];
apiURL = [apiURL stringByReplacingOccurrencesOfString:#"{user_id}" withString:[UserSettings lastLoginUserId]];
return [helper getJsonDictionaryFromWebMethod:apiURL];
}
- (NSDictionary*)getJsonDictionaryFromWebMethod :(NSString*) url
{
.....
.....
....
// Get JSON as a NSString from NSData response
NSString *json_string = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
// parse the JSON response into an object
// Here we're using NSArray since we're parsing an array of JSON status objects
dict = [[parser objectWithString:json_string error:nil] retain];
return dict;
}
Try putting self.UserInfoObj = [self getUserInfo]; in the viewDidLoad delegate method instead.