I am saving the AccessToken that I got from one Social Networking website.when I save this then I come to know that We can't directly save the non property values in iOS SDK.
Then from tutorial I came to know that I should implement the NSCoding class. Then I did this.
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:self.accessToken];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:myEncodedObject forKey:#"myEncodedObjectKey"];
[defaults synchronize];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [defaults objectForKey:#"myEncodedObjectKey"];
LOAToken *obj = (LOAToken *)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
I implemented the NSCoding delegate but I don't know how to implement the delegate methods. Then when I run this code I get the error
`"-[LOAToken encodeWithCoder:]: unrecognized selector sent to instance 0xa2bb970"
I am unable to implement the NSCoding along with my code. Any Suggestions? Also Is there any other way to store the non-property values like AccessToken for further use.
Edit:
I am getting this access token of LinkedIn and want to store like this:
self.accessToken = [[LOAToken alloc] initWithHTTPResponseBody:responseBody];
// The accessToken Printed....Here I have the AccessToken Value.
NSLog(#"Access===%#",self.accessToken);
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:self.accessToken];
NSLog(#"myEncodedObject===%#",myEncodedObject);
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:myEncodedObject forKey:#"myEncodedObjectKey"];
[defaults synchronize];
This was getting crashed since I have not used NSCoding implementation which I did in LOAToken class which you suggested.
I made one variable value in LAToken class and implemented these two methods.
-(void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:value forKey:#"Value"];
}
-(id)initWithCoder:(NSCoder *)decoder
{
self.value = [decoder decodeObjectForKey:#"Value"];
return self;
}
Then while retrieving I am using this.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [defaults objectForKey:#"myEncodedObjectKey"];
LOAToken *obj = (OAToken *)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
When I Printed the data both at the time of archiving and Unarchiving then the data printed is same.This may be due to two reasons.
The data which I have unarchived is right and the way of retrieving is wrong which you can suggest through my code above.
The data which I am saving at the time of Archiving is null.So the null is saved.
Is there any limitation in saving the Access Token.May be this can be a reason for null output.
The output which I got is this:
oauth_token "(null)" oauth_token_secret "(null)" oauth_verifier "(null)"
The text "oauth_token" "oauth_token_secret" "oauth_verifier" is coming from AccessToken but their values are null.
Edit 2: This is OAToken Class of LinkedIn where I am getting the Access Token.The same token I am passing in encoding method
OAToken.h
#import <Foundation/Foundation.h>
#interface OAToken : NSObject {
#protected
NSString *key;
NSString *secret;
NSString *session;
NSString *verifier;
NSNumber *duration;
NSMutableDictionary *attributes;
NSDate *created;
BOOL renewable;
BOOL forRenewal;
OAToken *value;
}
#property(retain, readwrite) NSString *key;
#property(retain, readwrite) NSString *secret;
#property(retain, readwrite) NSString *session;
#property(retain, readwrite) NSString *verifier;
#property(retain, readwrite) NSNumber *duration;
#property(retain, readwrite) NSMutableDictionary *attributes;
#property(readwrite, getter=isForRenewal) BOOL forRenewal;
#property (nonatomic,retain) OAToken *value;
- (id)initWithKey:(NSString *)aKey secret:(NSString *)aSecret;
- (id)initWithKey:(NSString *)aKey
secret:(NSString *)aSecret
session:(NSString *)aSession
verifier:(NSString *)aVerifier
duration:(NSNumber *)aDuration
attributes:(NSMutableDictionary *)theAttributes
created:(NSDate *)creation
renewable:(BOOL)renew;
- (id)initWithHTTPResponseBody:(NSString *)body;
- (id)initWithUserDefaultsUsingServiceProviderName:(NSString *)provider prefix:(NSString *)prefix;
- (int)storeInUserDefaultsWithServiceProviderName:(NSString *)provider prefix:(NSString *)prefix;
- (BOOL)isValid;
- (void)setAttribute:(NSString *)aKey value:(NSString *)aValue;
- (NSString *)attribute:(NSString *)aKey;
- (void)setAttributesWithString:(NSString *)aAttributes;
- (NSString *)attributeString;
- (BOOL)hasExpired;
- (BOOL)isRenewable;
- (void)setDurationWithString:(NSString *)aDuration;
- (void)setVerifierWithUrl:(NSURL *)aURL;
- (BOOL)hasAttributes;
- (NSMutableDictionary *)parameters;
- (BOOL)isEqualToToken:(OAToken *)aToken;
+ (void)removeFromUserDefaultsWithServiceProviderName:(const NSString *)provider prefix:(const NSString *)prefix;
#end
OAToken.m
#import "NSString+URLEncoding.h"
#import "OAToken.h"
#interface OAToken (Private)
+ (NSString *)settingsKey:(const NSString *)name provider:(const NSString *)provider prefix:(const NSString *)prefix;
+ (id)loadSetting:(const NSString *)name provider:(const NSString *)provider prefix:(const NSString *)prefix;
+ (void)saveSetting:(NSString *)name object:(id)object provider:(const NSString *)provider prefix:(const NSString *)prefix;
+ (NSNumber *)durationWithString:(NSString *)aDuration;
+ (NSMutableDictionary *)attributesWithString:(NSString *)theAttributes;
#end
#implementation OAToken
#synthesize key, secret, session, verifier, duration, attributes, forRenewal;
#synthesize value;
#pragma mark Encode
-(void)encodeWithCoder:(NSCoder *)encoder
{
// This prints the value....
NSLog(#"value===%#",self.value);
[encoder encodeObject:self.value forKey:#"Value"];
}
-(id)initWithCoder:(NSCoder *)decoder
{
OAToken *hell= [decoder decodeObjectForKey:#"Value"];
// This don't have the value.It is null.
NSLog(#"hell===%#",hell);
return self;
}
#pragma mark init
- (id)init {
return [self initWithKey:nil secret:nil];
}
- (id)initWithKey:(NSString *)aKey secret:(NSString *)aSecret {
return [self initWithKey:aKey secret:aSecret session:nil verifier:nil duration:nil
attributes:nil created:nil renewable:NO];
}
- (id)initWithKey:(NSString *)aKey
secret:(NSString *)aSecret
session:(NSString *)aSession
verifier:(NSString *)aVerifier
duration:(NSNumber *)aDuration
attributes:(NSMutableDictionary *)theAttributes
created:(NSDate *)creation
renewable:(BOOL)renew
{
[super init];
self.key = aKey;
self.secret = aSecret;
self.session = aSession;
self.verifier = aVerifier;
self.duration = aDuration;
self.attributes = theAttributes;
created = [creation retain];
renewable = renew;
forRenewal = NO;
return self;
}
- (void)setVerifierWithUrl:(NSURL *)aURL
{
NSString *query = [aURL query];
NSArray *pairs = [query componentsSeparatedByString:#"&"];
for (NSString *pair in pairs)
{
NSArray *elements = [pair componentsSeparatedByString:#"="];
if ([[elements objectAtIndex:0] isEqualToString:#"oauth_verifier"])
{
self.verifier = [elements objectAtIndex:1];
}
}
}
- (id)initWithHTTPResponseBody:(const NSString *)body
{
NSString *aKey = nil;
NSString *aSecret = nil;
NSString *aSession = nil;
NSString *aVerifier = nil;
NSNumber *aDuration = nil;
NSDate *creationDate = nil;
NSMutableDictionary *attrs = nil;
BOOL renew = NO;
NSArray *pairs = [body componentsSeparatedByString:#"&"];
for (NSString *pair in pairs)
{
NSArray *elements = [pair componentsSeparatedByString:#"="];
if ([[elements objectAtIndex:0] isEqualToString:#"oauth_token"])
{
aKey = [elements objectAtIndex:1];
}
else if ([[elements objectAtIndex:0] isEqualToString:#"oauth_token_secret"])
{
aSecret = [elements objectAtIndex:1];
}
else if ([[elements objectAtIndex:0] isEqualToString:#"oauth_verifier"])
{
aVerifier = [elements objectAtIndex:1];
}
else if ([[elements objectAtIndex:0] isEqualToString:#"oauth_session_handle"])
{
aSession = [elements objectAtIndex:1];
}
else if ([[elements objectAtIndex:0] isEqualToString:#"oauth_token_duration"])
{
aDuration = [[self class] durationWithString:[elements objectAtIndex:1]];
creationDate = [NSDate date];
}
else if ([[elements objectAtIndex:0] isEqualToString:#"oauth_token_attributes"])
{
attrs = [[self class] attributesWithString:[[elements objectAtIndex:1] decodedURLString]];
}
else if ([[elements objectAtIndex:0] isEqualToString:#"oauth_token_renewable"])
{
NSString *lowerCase = [[elements objectAtIndex:1] lowercaseString];
if ([lowerCase isEqualToString:#"true"] || [lowerCase isEqualToString:#"t"]) {
renew = YES;
}
}
}
value=[self initWithKey:aKey
secret:aSecret
session:aSession
verifier:aVerifier
duration:aDuration
attributes:attrs
created:creationDate
renewable:renew];
return [self initWithKey:aKey
secret:aSecret
session:aSession
verifier:aVerifier
duration:aDuration
attributes:attrs
created:creationDate
renewable:renew];
}
- (id)initWithUserDefaultsUsingServiceProviderName:(const NSString *)provider prefix:(const NSString *)prefix {
[super init];
self.key = [OAToken loadSetting:#"key" provider:provider prefix:prefix];
self.secret = [OAToken loadSetting:#"secret" provider:provider prefix:prefix];
self.session = [OAToken loadSetting:#"session" provider:provider prefix:prefix];
self.verifier = [OAToken loadSetting:#"verifier" provider:provider prefix:prefix];
self.duration = [OAToken loadSetting:#"duration" provider:provider prefix:prefix];
self.attributes = [OAToken loadSetting:#"attributes" provider:provider prefix:prefix];
created = [OAToken loadSetting:#"created" provider:provider prefix:prefix];
renewable = [[OAToken loadSetting:#"renewable" provider:provider prefix:prefix] boolValue];
if (![self isValid]) {
[self autorelease];
return nil;
}
return self;
}
#pragma mark dealloc
- (void)dealloc {
self.key = nil;
self.secret = nil;
self.duration = nil;
self.attributes = nil;
[super dealloc];
}
#pragma mark settings
- (BOOL)isValid {
return (key != nil && ![key isEqualToString:#""] && secret != nil && ![secret isEqualToString:#""]);
}
- (int)storeInUserDefaultsWithServiceProviderName:(const NSString *)provider prefix:(const NSString *)prefix {
[OAToken saveSetting:#"key" object:key provider:provider prefix:prefix];
[OAToken saveSetting:#"secret" object:secret provider:provider prefix:prefix];
[OAToken saveSetting:#"created" object:created provider:provider prefix:prefix];
[OAToken saveSetting:#"duration" object:duration provider:provider prefix:prefix];
[OAToken saveSetting:#"session" object:session provider:provider prefix:prefix];
[OAToken saveSetting:#"verifier" object:verifier provider:provider prefix:prefix];
[OAToken saveSetting:#"attributes" object:attributes provider:provider prefix:prefix];
[OAToken saveSetting:#"renewable" object:renewable ? #"t" : #"f" provider:provider prefix:prefix];
[[NSUserDefaults standardUserDefaults] synchronize];
return(0);
}
#pragma mark duration
- (void)setDurationWithString:(NSString *)aDuration {
self.duration = [[self class] durationWithString:aDuration];
}
- (BOOL)hasExpired
{
return created && [created timeIntervalSinceNow] > [duration intValue];
}
- (BOOL)isRenewable
{
return session && renewable && created && [created timeIntervalSinceNow] < (2 * [duration intValue]);
}
#pragma mark attributes
- (void)setAttribute:(const NSString *)aKey value:(const NSString *)aAttribute {
if (!attributes) {
attributes = [[NSMutableDictionary alloc] init];
}
[attributes setObject: aAttribute forKey: aKey];
}
- (void)setAttributes:(NSMutableDictionary *)theAttributes {
[attributes release];
if (theAttributes) {
attributes = [[NSMutableDictionary alloc] initWithDictionary:theAttributes];
}else {
attributes = nil;
}
}
- (BOOL)hasAttributes {
return (attributes && [attributes count] > 0);
}
- (NSString *)attributeString {
if (![self hasAttributes]) {
return #"";
}
NSMutableArray *chunks = [[NSMutableArray alloc] init];
for(NSString *aKey in self->attributes) {
[chunks addObject:[NSString stringWithFormat:#"%#:%#", aKey, [attributes objectForKey:aKey]]];
}
NSString *attrs = [chunks componentsJoinedByString:#";"];
[chunks release];
return attrs;
}
- (NSString *)attribute:(NSString *)aKey
{
return [attributes objectForKey:aKey];
}
- (void)setAttributesWithString:(NSString *)theAttributes
{
self.attributes = [[self class] attributesWithString:theAttributes];
}
- (NSMutableDictionary *)parameters
{
NSMutableDictionary *params = [[[NSMutableDictionary alloc] init] autorelease];
if (key)
{
[params setObject:key forKey:#"oauth_token"];
if ([self isForRenewal])
{
[params setObject:session forKey:#"oauth_session_handle"];
}
}
else
{
if (duration)
{
[params setObject:[duration stringValue] forKey: #"oauth_token_duration"];
}
if ([attributes count])
{
[params setObject:[self attributeString] forKey:#"oauth_token_attributes"];
}
}
if (verifier)
{
[params setObject:verifier forKey:#"oauth_verifier"];
}
return params;
}
#pragma mark comparisions
- (BOOL)isEqual:(id)object {
if([object isKindOfClass:[self class]]) {
return [self isEqualToToken:(OAToken *)object];
}
return NO;
}
- (BOOL)isEqualToToken:(OAToken *)aToken {
/* Since ScalableOAuth determines that the token may be
renewed using the same key and secret, we must also
check the creation date */
if ([self.key isEqualToString:aToken.key] &&
[self.secret isEqualToString:aToken.secret]) {
/* May be nil */
if (created == aToken->created || [created isEqualToDate:aToken->created]) {
return YES;
}
}
return NO;
}
#pragma mark class_functions
+ (NSString *)settingsKey:(NSString *)name provider:(NSString *)provider prefix:(NSString *)prefix {
return [NSString stringWithFormat:#"OAUTH_%#_%#_%#", provider, prefix, [name uppercaseString]];
}
+ (id)loadSetting:(NSString *)name provider:(NSString *)provider prefix:(NSString *)prefix {
return [[NSUserDefaults standardUserDefaults] objectForKey:[self settingsKey:name
provider:provider
prefix:prefix]];
}
+ (void)saveSetting:(NSString *)name object:(id)object provider:(NSString *)provider prefix:(NSString *)prefix {
[[NSUserDefaults standardUserDefaults] setObject:object forKey:[self settingsKey:name
provider:provider
prefix:prefix]];
}
+ (void)removeFromUserDefaultsWithServiceProviderName:(NSString *)provider prefix:(NSString *)prefix {
NSArray *keys = [NSArray arrayWithObjects:#"key", #"secret", #"created", #"duration", #"session", #"verifier", #"attributes", #"renewable", nil];
for(NSString *name in keys) {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:[OAToken settingsKey:name provider:provider prefix:prefix]];
}
}
+ (NSNumber *)durationWithString:(NSString *)aDuration {
NSUInteger length = [aDuration length];
unichar c = toupper([aDuration characterAtIndex:length - 1]);
int mult;
if (c >= '0' && c <= '9') {
return [NSNumber numberWithInt:[aDuration intValue]];
}
if (c == 'S') {
mult = 1;
} else if (c == 'H') {
mult = 60 * 60;
} else if (c == 'D') {
mult = 60 * 60 * 24;
} else if (c == 'W') {
mult = 60 * 60 * 24 * 7;
} else if (c == 'M') {
mult = 60 * 60 * 24 * 30;
} else if (c == 'Y') {
mult = 60 * 60 * 365;
} else {
mult = 1;
}
return [NSNumber numberWithInt: mult * [[aDuration substringToIndex:length - 1] intValue]];
}
+ (NSMutableDictionary *)attributesWithString:(NSString *)theAttributes {
NSArray *attrs = [theAttributes componentsSeparatedByString:#";"];
NSMutableDictionary *dct = [[NSMutableDictionary alloc] init];
for (NSString *pair in attrs) {
NSArray *elements = [pair componentsSeparatedByString:#":"];
[dct setObject:[elements objectAtIndex:1] forKey:[elements objectAtIndex:0]];
}
return [dct autorelease];
}
#pragma mark description
- (NSString *)description {
return [NSString stringWithFormat:#"oauth_token \"%#\" oauth_token_secret \"%#\" oauth_verifier \"%#\"", key, secret, verifier];
}
#end
You don't need any delegate methods. Instead, LOAToken should implement the initWithCoder: and encodeWithCoder: methods to actually save and restore the information that it contains. This is the meaning of implementing the NSCoding protocol.
You may want to have a read of the archiving docs here.
Once you have initially created your token from a web response or similar you should be using storeInUserDefaultsWithServiceProviderName: to store it into user defaults. Then, when you want to use it again call initWithUserDefaultsUsingServiceProviderName:. You don't need to do any encoding and decoding yourself.
To unarchive data
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:#"key"];
NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithData:data];
to archive data
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
[defaults setValue:data forKey:#"key"];
[defaults synchronize];
Related
How can I parse a mailto request ?
'mailto:someone#example.com?cc=someone_else#example.com&subject=This%20is%20the%20subject&body=This%20is%20the%20body'
From this NSURL, I want to extract the recipient, the subject and the body. How should I do ?
Thanks
Here is some code that will parse any URL and return a dictionary with the parameters and the associated objects in a dictionary. It works for mailto URLs, too.
Please note: This code assumes you're using ARC!
#interface NSString (URLDecoding)
- (NSString *) URLDecodedString;
#end
#implementation NSString (URLDecoding)
- (NSString *) URLDecodedString {
NSString *result = (__bridge_transfer NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (__bridge CFStringRef)self, CFSTR(""), kCFStringEncodingUTF8);
return result;
}
#end
- (NSDictionary *) parameterDictionaryFromURL:(NSURL *)url {
NSMutableDictionary *parameterDictionary = [[NSMutableDictionary alloc] init];
if ([[url scheme] isEqualToString:#"mailto"]) {
NSString *mailtoParameterString = [[url absoluteString] substringFromIndex:[#"mailto:" length]];
NSUInteger questionMarkLocation = [mailtoParameterString rangeOfString:#"?"].location;
[parameterDictionary setObject:[mailtoParameterString substringToIndex:questionMarkLocation] forKey:#"recipient"];
if (questionMarkLocation != NSNotFound) {
NSString *parameterString = [mailtoParameterString substringFromIndex:questionMarkLocation + 1];
NSArray *keyValuePairs = [parameterString componentsSeparatedByString:#"&"];
for (NSString *queryString in keyValuePairs) {
NSArray *keyValuePair = [queryString componentsSeparatedByString:#"="];
if (keyValuePair.count == 2)
[parameterDictionary setObject:[[keyValuePair objectAtIndex:1] URLDecodedString] forKey:[[keyValuePair objectAtIndex:0] URLDecodedString]];
}
}
}
else {
NSString *parameterString = [url parameterString];
NSArray *keyValuePairs = [parameterString componentsSeparatedByString:#"&"];
for (NSString *queryString in keyValuePairs) {
NSArray *keyValuePair = [queryString componentsSeparatedByString:#"="];
if (keyValuePair.count == 2)
[parameterDictionary setObject:[[keyValuePair objectAtIndex:1] URLDecodedString] forKey:[[keyValuePair objectAtIndex:0] URLDecodedString]];
}
}
return [parameterDictionary copy];
}
And here is how you use it:
NSURL *mailtoURL = [NSURL URLWithString:#"mailto:foo#example.com?cc=bar#example.com&subject=Greetings%20from%20Cupertino!&body=Wish%20you%20were%20here!"];
NSDictionary *parameterDictionary = [self parameterDictionaryFromURL:mailtoURL];
NSString *recipient = [parameterDictionary objectForKey:#"recipient"];
NSString *subject = [parameterDictionary objectForKey:#"subject"];
NSString *body = [parameterDictionary objectForKey:#"body"];
EDIT:
I updated the code to work with any URL and recipients are now in the dictionary for mailto URLs.
I would pull the email from that like this:
NSString * mailToString = #"'mailto:someone#example.com?cc=someone_else#example.com&subject=This%20is%20the%20subject&body=This%20is%20the%20body'";
NSArray *tempArray = [mailToString componentsSeparatedByString:#"?"];
//get email address from array
NSString * emailString = [[tempArray objectAtIndex:0]description];
//clean up string
emailString = [emailString stringByReplacingOccurrencesOfString:#"'mailto:" withString:#""];
//and here is your email string
NSLog(#"%#",emailString);
Since iOS 7 this is easily doable with NSURLComponents. You can create that object with:
if let components = NSURLComponents(URL: url, resolvingAgainstBaseURL:false) { ...
Then you can get the recipient accessing the path property of NSURLComponents; and the parameters with the queryItems property. For instance, if we wanted to get the subject, something like this would do our job
let queryItems = components.queryItems as? [NSURLQueryItem]
let subject = queryItems?.filter({$0.name == "subject"}).first?.value
NSURL category for just mailto: This method also has a fix for a crash bug in Fabian's answer above when mailto: url doesn't contain a ?. It also doesn't require the URLDecodedString category method.
#implementation NSURL (Additions)
- (NSDictionary *) parameterDictionaryForMailTo {
NSMutableDictionary *parameterDictionary = [[NSMutableDictionary alloc] init];
NSString *mailtoParameterString = [[self absoluteString] substringFromIndex:[#"mailto:" length]];
NSUInteger questionMarkLocation = [mailtoParameterString rangeOfString:#"?"].location;
if (questionMarkLocation != NSNotFound) {
[parameterDictionary setObject:[mailtoParameterString substringToIndex:questionMarkLocation] forKey:#"recipient"];
NSString *parameterString = [mailtoParameterString substringFromIndex:questionMarkLocation + 1];
NSArray *keyValuePairs = [parameterString componentsSeparatedByString:#"&"];
for (NSString *queryString in keyValuePairs) {
NSArray *keyValuePair = [queryString componentsSeparatedByString:#"="];
if (keyValuePair.count == 2)
[parameterDictionary setObject:[[keyValuePair objectAtIndex:1] stringByRemovingPercentEncoding] forKey:[[keyValuePair objectAtIndex:0] stringByRemovingPercentEncoding]];
}
}
else {
[parameterDictionary setObject:mailtoParameterString forKey:#"recipient"];
}
return [parameterDictionary copy];
}
- (NSDictionary *) parameterDictionaryFromURL:(NSURL *)url {
NSMutableDictionary *parameterDictionary = [[NSMutableDictionary alloc] init];
NSURLComponents * urlComponents = [NSURLComponents componentsWithString:url.absoluteString];
for (NSURLQueryItem *item in urlComponents.queryItems) {
parameterDictionary[item.name] = item.value;
}
if ([url.scheme isEqualToString:#"mailto"]) {
NSUInteger questionMarkLocation = [url.resourceSpecifier rangeOfString:#"?"].location;
if (questionMarkLocation == NSNotFound) {
parameterDictionary[#"recipient"] = url.resourceSpecifier;
} else {
parameterDictionary[#"recipient"] = [url.resourceSpecifier substringToIndex:questionMarkLocation];
}
}
return [parameterDictionary copy];
}
Here I got from JSON
[{"photo":null}]
and I use this code
NSMutableArray *jPhoto = [NSMutableArray arrayWithArray:(NSArray *)[jsonDict valueForKey:#"photo"]];
How can I check it if I want to use if() ??
edit
here is JSON Data
[{"photo":
[{"image":"http:\/\/www.yohyeh.com\/upload\/shisetsu\/13157\/photo\/1304928459.jpg","title":"test picture","content":"this is description for test picture.\r\n\u8aac\u660e\u6587\u306a\u306e\u306b\u30fb\u30fb\u30fb\u30fb\u30fb\u30fb\u30fb\u30fb\u30fb\u30fb\u30fb\u30fb"}
,{"image":"http:\/\/www.yohyeh.com\/upload\/shisetsu\/13157\/photo\/1304928115.jpg","title":"nothing","content":"iMirai"}
,{"image":"http:\/\/www.yohyeh.com\/upload\/shisetsu\/13157\/photo\/1303276769.jpg","title":"iMirai","content":"Staff"}]}
]
and here is my JSON parser
NSError *theError = nil;
NSString *URL = [NSString stringWithFormat:#"http://www.yohyeh.com/apps/get_sub_detail.php?id=%#&menu=photo",g_id];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:URL]];
NSURLResponse *theResponse =[[[NSURLResponse alloc]init] autorelease];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&theResponse error:&theError];
NSMutableString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *jsonDict = [string JSONValue];
Thank for help
I believe most JSON parsers represent null as [NSNull null].
Considering jsonDict points to that single element in the array, then the following should work:
if ([jsonDict objectForKey:#"photo"] == [NSNull null]) {
// it's null
}
Edit based on comment: so jsonDict, despite its name, is an array. In that case, rename jsonDict to jsonArray to avoid further confusion. Then, considering jsonArray points to an array similar to the example posted in the question:
NSArray *photos = [jsonArray valueForKey:#"photo"];
for (id photo in photos) {
if (photo == [NSNull null]) {
// photo is null
}
else {
// photo isn't null
}
}
Further edit based on OP’s modified question:
NSArray *jsonArray = [string JSONValue];
NSArray *photos = [jsonArray valueForKey:#"photo"];
for (id photo in photos) {
if (photo == [NSNull null]) {
// photo is null
}
else {
// photo isn't null. It's an array
NSArray *innerPhotos = photo;
…
}
}
Macros can be helpful if payload is complex JSON structure having possible values.
#define SET_IF_NOT_NULL(TARGET, VAL) if(VAL != [NSNull null]) { TARGET = VAL; }
and macro can be referenced like
SET_IF_NOT_NULL(myRecord.name, [jsonData objectForKey:#"name"]);
There's no easy way of dealing with this, but the way I do it is to make a category on NSObject:
#interface NSObject (NotNull)
- (instancetype)notNull;
#end
Implemented like so:
#implementation NSObject (NotNull)
- (instancetype)notNull
{
return self;
}
#end
#implementation NSNull (NotNull)
- (instancetype)notNull
{
return nil;
}
#end
Then you can send notNull to any optional object in a JSON dict and you'll get nil
back if it's NSNull. Otherwise you get the original object. For example:
self.parentIdentifier = [dictionary[#"parent_id"] notNull];
try to check for [jPhoto count] or [NSNull null]
Hope so this helps.
-(NSMutableDictionary *)jsonCheckforNull:(NSMutableDictionary *)json{
NSMutableDictionary* strongjson=[json mutableCopy];
for (NSString *ktr in json) {
NSObject *str=[json objectForKey:ktr];
if ([str isKindOfClass:[NSArray class]]) {
if (!(str==[NSNull null])) {
NSArray *temp = [json allKeysForObject:str];
str=[[self ArrayCheckforNull:(NSMutableArray*)str]mutableCopy];
NSString *key = [temp objectAtIndex:0];
[strongjson removeObjectForKey:key];
[strongjson setObject:str forKey:key];
}
else
{
NSArray *temp = [strongjson allKeysForObject:str];
NSString *key = [temp objectAtIndex:0];
[strongjson removeObjectForKey:key];
[strongjson setObject:#"-----" forKey:key];
}
}
else if ([str isKindOfClass:[NSDictionary class]]) {
if (!(str==[NSNull null])) {
str=[[self jsonCheckforNull:str]mutableCopy];
NSArray *temp = [strongjson allKeysForObject:str];
NSString *key = [temp objectAtIndex:0];
[strongjson removeObjectForKey:key];
[strongjson setObject:str forKey:key];
}
else
{
NSArray *temp = [strongjson allKeysForObject:str];
NSString *key = [temp objectAtIndex:0];
[strongjson removeObjectForKey:key];
[strongjson setObject:#"-----" forKey:key];
}
}
else {
if (str ==[NSNull null]) {
NSArray *temp = [strongjson allKeysForObject:str];
NSString *key = [temp objectAtIndex:0];
[strongjson removeObjectForKey:key];
[strongjson setObject:#"----" forKey:key];
}
}
}
return strongjson;
}
-(NSMutableArray *)ArrayCheckforNull:(NSMutableArray *)arr{
NSObject *str;
NSMutableArray* strongArray=[[[NSMutableArray alloc]initWithArray:arr]mutableCopy];
for (str in arr)
{
if ([str isKindOfClass:[NSArray class]]) {
if (!(str==[NSNull null])) {
str=[[self ArrayCheckforNull:(NSMutableArray *)str]mutableCopy];
[strongArray removeObjectAtIndex:0];
[strongArray addObject:str];
}
else
{
[strongArray removeObject:str];
[strongArray addObject:#"----"];
}
}
else if ([str isKindOfClass:[NSDictionary class]]) {
if (!(str==[NSNull null])) {
str=[[self jsonCheckforNull:(NSMutableDictionary*)str]mutableCopy];
[strongArray removeObjectAtIndex:0];
[strongArray addObject:str];
}
else
{
[strongArray removeObject:str];
[strongArray addObject:#"----"];
}
}
else {
if (str ==[NSNull null]) {
[strongArray removeObject:str];
[strongArray addObject:#"----"];
}
}
}
return strongArray;
}
I have a singleton object called PoolManager that loads and saves some data in a plist. Throughout my program when something needs to know about my pool, it asks the [PoolManager sharedPoolManager] for it's properties. I have a single view that's responsible for setting these properties and all others just read from it. It was all working fine, and then for no reason I can tell, it started crashing. I set NSZombieEnabled = YES and can see that when I access one of the two NSString properties, they appear to have been released. The debugger message is: *** -[CFString respondsToSelector:]: message sent to deallocated instance 0x5a336d0
I tried going back to a previous snapshot where everything worked and it still does this. I even used TimeMachine to go back to the project from yesterday and it does it too. I'm baffled.
Here is the singleton object code... It's the surface and shape strings that are apparently zombies. Sorry for all the NSLogs
// MyPoolSingleton.h
#import <Foundation/Foundation.h>
#define kFileName #"data.plist"
#interface PoolManager : NSObject {
float volume;
float length;
float width;
float depth;
NSString *surface;
NSString *shape;
BOOL isMetric;
int fcTarget;
int cyaTarget;
int taTarget;
int chTarget;
int saltTarget;
}
#property float volume;
#property float length;
#property float width;
#property float depth;
#property (nonatomic, retain) NSString *surface;
#property (nonatomic, retain) NSString *shape;
#property BOOL isMetric;
#property int fcTarget;
#property int cyaTarget;
#property int taTarget;
#property int chTarget;
#property int saltTarget;
+ (PoolManager*)sharedPoolManager;
- (void)retrieveState;
- (void)saveState;
- (NSString*)dataFilePath;
#end
// MyPoolSingleton.m
#import "PoolManager.h"
#implementation PoolManager
#synthesize volume;
#synthesize length;
#synthesize width;
#synthesize depth;
#synthesize surface;
#synthesize shape;
#synthesize isMetric;
#synthesize fcTarget;
#synthesize cyaTarget;
#synthesize taTarget;
#synthesize chTarget;
#synthesize saltTarget;
static PoolManager* _sharedPoolManager = nil;
+ (PoolManager*)sharedPoolManager {
#synchronized([PoolManager class])
{
if (!_sharedPoolManager)
[[self alloc] init];
return _sharedPoolManager;
}
return nil;
}
+ (id)alloc {
#synchronized([PoolManager class])
{
NSAssert(_sharedPoolManager == nil, #"Attempted to allocate a second instance of a singleton.");
_sharedPoolManager = [super alloc];
return _sharedPoolManager;
}
return nil;
}
- (id)init {
self = [super init];
return self;
}
- (void)retrieveState {
NSLog(#"--retrieveState");
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSLog(#" fileExistsAtPath: reading array from plist");
NSArray *array = [[NSArray alloc] initWithContentsOfFile:filePath];
volume = [[array objectAtIndex:0] floatValue];
NSLog(#" reading array: volume = %1.1f", volume);
length = [[array objectAtIndex:1] floatValue];
NSLog(#" reading array: length = %1.1f", length);
width = [[array objectAtIndex:2] floatValue];
NSLog(#" reading array: width = %1.1f", width);
depth = [[array objectAtIndex:3] floatValue];
NSLog(#" reading array: depth = %1.1f", depth);
self.surface = [array objectAtIndex:4];
NSLog(#" reading array: surface = %#", surface);
self.shape = [array objectAtIndex:5];
NSLog(#" reading array: shape = %#", shape);
isMetric = [[array objectAtIndex:6] boolValue];
NSLog(#" reading array: isMetric = %d", isMetric);
fcTarget = [[array objectAtIndex:7] intValue];
NSLog(#" reading array: fcTarget = %d", fcTarget);
cyaTarget = [[array objectAtIndex:8] intValue];
NSLog(#" reading array: cyaTarget = %d", cyaTarget);
taTarget = [[array objectAtIndex:9] intValue];
NSLog(#" reading array: taTarget = %d", taTarget);
chTarget = [[array objectAtIndex:10] intValue];
NSLog(#" reading array: chTarget = %d", chTarget);
saltTarget = [[array objectAtIndex:11] intValue];
NSLog(#" reading array: saltTarget = %d", saltTarget);
[array release];
}
else {
NSLog(#" !fileExistsAtPath: intitializing values to nil/zero");
volume = 0.0;
length = 0.0;
width = 0.0;
depth = 0.0;
surface = #"";
shape = #"";
isMetric = NO;
fcTarget = 0.0;
cyaTarget = 0.0;
taTarget = 0.0;
chTarget = 0.0;
saltTarget = 0.0;
}
}
- (void)saveState {
NSLog(#"--saveState");
NSMutableArray *array = [[NSMutableArray alloc] init];
NSLog(#" building array: volume = %1.1f", volume);
[array addObject:[NSNumber numberWithFloat:volume]];
NSLog(#" building array: length = %1.1f", length);
[array addObject:[NSNumber numberWithFloat:length]];
NSLog(#" building array: width = %1.1f", width);
[array addObject:[NSNumber numberWithFloat:width]];
NSLog(#" building array: depth = %1.1f", depth);
[array addObject:[NSNumber numberWithFloat:depth]];
NSLog(#" building array: surface = %#", surface);
[array addObject:surface];
NSLog(#" building array: shape = %#", shape);
[array addObject:shape];
NSLog(#" building array: isMetric = %d", isMetric);
[array addObject:[NSNumber numberWithBool:isMetric]];
NSLog(#" building array: fcTarget = %d", fcTarget);
[array addObject:[NSNumber numberWithInt:fcTarget]];
NSLog(#" building array: cyaTarget = %d", cyaTarget);
[array addObject:[NSNumber numberWithInt:cyaTarget]];
NSLog(#" building array: taTarget = %d", taTarget);
[array addObject:[NSNumber numberWithInt:taTarget]];
NSLog(#" building array: chTarget = %d", chTarget);
[array addObject:[NSNumber numberWithInt:chTarget]];
NSLog(#" building array: saltTarget = %d", saltTarget);
[array addObject:[NSNumber numberWithInt:saltTarget]];
[array writeToFile:[self dataFilePath] atomically:YES];
[array release];
}
- (NSString*)dataFilePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:kFileName];
}
- (void)dealloc {
[shape release], shape = nil;
[surface release], surface = nil;
[super dealloc];
}
#end
objectAtIndex: gives an autoreleased object. You should either retain it, or use the property accessor self.surface = ... and self.shape = ... when setting those.
Personally I prefer the following pattern for singletons:
+ (id) sharedPoolManager
{
static id sharedPoolManager = nil;
#synchronized (self) {
if (sharedPoolManager == nil) {
sharedPoolManager = [self new];
}
}
return sharedPoolManager;
}
- (id) init
{
if ((self = [super init]) != nil) {
// Initialize ... nothing special here ...
}
return self;
}
(Note that self in a class method is equivalent to [SomeClass class])
The above is more concise and I prefer to keep any singleton code outside of init and alloc since I can then also create multiple instances if needed. For example in unit tests.
Personally I don't think you have to protect the programmer from creating multiple instances. The contract is clear: sharedPoolManager returns a singleton instance. If that is what you want/need then use that. Otherwise create instances the usual way.
My issue is then I retrieve my NSArray of Store objects, all my NSString properties are causing BadAccess errors. The int and double properties work fine!
store.h
#interface Store : NSObject<NSCoding> {
NSString *Name;
NSString *Address;
NSString *Phone;
double GeoLong;
double GeoLat;
int ID;
}
#property (nonatomic, retain) NSString *Name;
#property (nonatomic, retain) NSString *Address;
#property (nonatomic, retain) NSString *Phone;
#property (nonatomic) double GeoLat;
#property (nonatomic) double GeoLong;
#property (nonatomic) int ID;
#end
store.m
#implementation Store
#synthesize Name;
#synthesize ID;
#synthesize Address;
#synthesize Phone;
#synthesize GeoLat;
#synthesize GeoLong;
/** Implentation of the NSCoding protocol. */
-(void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeInt:ID forKey:#"ID"];
[encoder encodeDouble:GeoLat forKey:#"GeoLat"];
[encoder encodeDouble:GeoLong forKey:#"GeoLong"];
NSLog(#"Name in encode: %#", Name); //WORKS!
[encoder encodeObject:Name forKey:#"Name"];
[encoder encodeObject:Phone forKey:#"Phone"];
[encoder encodeObject:Address forKey:#"Address"];
}
-(id)initWithCoder:(NSCoder *)decoder
{
// Init first.
if(self = [self init]){
ID = [decoder decodeIntForKey:#"ID"];
GeoLat = [decoder decodeDoubleForKey:#"GeoLat"];
GeoLong = [decoder decodeDoubleForKey:#"GeoLong"];
Name = [decoder decodeObjectForKey:#"Name"];
NSLog(#"Name in decode: %#", Name); //WORKS! logs the name
Address = [decoder decodeObjectForKey:#"Address"];
Phone = [decoder decodeObjectForKey:#"Phone"];
}
return self;
}
- (void)dealloc
{
[Name release];
[ID release];
[Address release];
[Phone release];
[super dealloc];
}
#end
Here is my code for storing and retriving the array.
//streams contains the data i will populate my array with.
for (ndx = 0; ndx < streams.count; ndx++) {
NSDictionary *stream = (NSDictionary *)[streams objectAtIndex:ndx];
Store *item = [[Store alloc] init] ;
item.Name = [stream valueForKey:#"Name"];
item.Address = [stream valueForKey:#"Address"];
item.Phone = [stream valueForKey:#"Phone"];
item.GeoLat = [[stream valueForKey:#"GeoLat"] doubleValue];
item.GeoLong = [[stream valueForKey:#"GeoLong"] doubleValue];
item.ID = [[stream valueForKey:#"ID"] intValue];
[listToReturn addObject:item];
}
}
//test to check if it works
for(int i = 0; i < [listToReturn count]; i++){
Store *item = (Store *)[listToReturn objectAtIndex:i];
NSLog(#"Name: %#", item.Name); //works
}
//save
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:listToReturn] forKey:#"stores"];
// retrieve
NSMutableArray *stores = [NSMutableArray new];
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:#"stores"];
if (dataRepresentingSavedArray != nil)
{
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if (oldSavedArray != nil)
stores = [[NSMutableArray alloc] initWithArray:oldSavedArray];
else
stores = [[NSMutableArray alloc] init];
}
if ([stores count] > 0) {
NSMutableArray * annotations = [[NSMutableArray alloc] init];
for(int i = 0;i< [stores count]; i++){
Store *store = [stores objectAtIndex: i];
CLLocationCoordinate2D location;
if(store.GeoLat != 0 && store.GeoLong != 0){
location.latitude = store.GeoLat;
location.longitude = store.GeoLong; //works
NSLog(#"Adding store: %#", store.Name); //DONT WORK!! <-- MAIN PROBLEM
}
}
}
Feels like I tried everything but can't figure out how it works in the decode but not when in loop the array after I put it into a array.
Anyone have any ideas?
You're not retaining the properties in initWithCoder.
Name = [decoder decodeObjectForKey:#"Name"];
is not using the setter of the (retaining) property you've defined. You're just setting the ivar. That means you don't acquire ownership and it can be deallocated.
Here are two ways you can retain the properties in your case:
self.Name = [decoder decodeObjectForKey:#"Name"];
Name = [[decoder decodeObjectForKey:#"Name"] retain];
My coding contains a memory leak, and somehow I can't find the leak.
Leaks points me in the direction of the way I create "ReportDetailItems"
e.g. areaContainer = [[[ReportDetailItem alloc] init] autorelease];
I've been looking at this for hours and I am at a total loss, the objects reported leaking are "ReportDetailItem", and the NSMutableDictionary contained in those objects.
Please advice.
------[ReportDetailItem.h
#interface ReportDetailItem : NSObject
{
NSNumber *total;
NSMutableDictionary *items;
}
#property (nonatomic, retain) NSNumber *total;
#property (nonatomic, retain) NSMutableDictionary *items;
- (NSString *)description;
#end
------[ReportDetailItem.m
#synthesize items, total;
- (id)init {
if (self = [super init]) {
self.items = [NSMutableDictionary dictionaryWithCapacity:0];
DLog("Alloc: %d", [items retainCount]);
}
return self;
}
- (NSString *)description {
return #"ReportDetailItem";
}
- (void)release {
[super release];
}
- (void)dealloc {
[self.items release];
[self.total release];
items = nil;
total = nil;
[super dealloc];
}
#end
------[Leaking code
NSError *error;
NSArray *data = [self.managedObjectContext executeFetchRequest:request error:&error];
if (data == nil || [data count] == 0) {
DLog(#"No data.")
} else {
for (int i=0; i < [data count]; i++) {
TaskEntity *task = [data objectAtIndex:i];
NSString *areaKey = task.activity.project.area.title.text;
NSString *projectKey = task.activity.project.title.text;
NSString *activityKey = task.activity.title.text;
ReportDetailItem *areaContainer;
if (![dataSource objectForKey:areaKey]) {
areaContainer = [[[ReportDetailItem alloc] init] autorelease];
} else {
areaContainer = [dataSource objectForKey:areaKey];
}
areaContainer.total = [NSNumber numberWithInt:([task.seconds intValue] + [areaContainer.total intValue])];
[dataSource setObject:areaContainer forKey:areaKey];
ReportDetailItem *projectContainer;
if (![areaContainer.items objectForKey:projectKey]) {
projectContainer = [[[ReportDetailItem alloc] init] autorelease];
} else {
projectContainer = [areaContainer.items objectForKey:projectKey];
}
projectContainer.total = [NSNumber numberWithInt:([task.seconds intValue] + [projectContainer.total intValue])];
[areaContainer.items setObject:projectContainer forKey:projectKey];
ReportDetailItem *activityContainer;
if (![projectContainer.items objectForKey:activityKey]) {
activityContainer = [[[ReportDetailItem alloc] init] autorelease];
} else {
activityContainer = [projectContainer.items objectForKey:activityKey];
}
activityContainer.total = [NSNumber numberWithInt:([task.seconds intValue] + [activityContainer.total intValue])];
[projectContainer.items setObject:activityContainer forKey:activityKey];
}
}
I found it, the leak was located in the way I allocated the "dataSource"
---[Leak
- (void)viewDidLoad {
[super viewDidLoad];
self.dataSource = [[NSMutableDictionary alloc] init];
[self fetchData];
}
---[No leak
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
self.dataSource = dict;
[dict release];
[self fetchData];
}
I'm pretty skeptic about the two ways u assign pointers to the ReportDetailItem. Why are you trying to autorelease the object in the first place? If not try this
ReportDetailItem *projectContainer;
if (![areaContainer.items objectForKey:projectKey]) {
projectContainer = [[ReportDetailItem alloc] init];
} else {
projectContainer = [[areaContainer.items objectForKey:projectKey] retain];
}
projectContainer.total = [NSNumber numberWithInt:([task.seconds intValue] + [projectContainer.total intValue])];
[areaContainer.items setObject:projectContainer forKey:projectKey];
if(projectContainer) {
[projectContainer release];
projectContainer = nil;
}