Class Factory Method with Multiple Parameters - objective-c

I am practicing my Objective C skills and have come across a small issue, although I can't seem to find a straight answer to this issue anywhere I look. In the Apple developer guides I am reading, there is nothing in there telling me how to use a class factory method with multiple parameters (say 3 parameters) and return the initialized object via the overridden init method.
Here I have a simple class called XYZPerson.
#implementation XYZPerson
// Class Factory Method
+ (id)person:(NSString *)firstName with:(NSString *)lastName andWith:(NSDate *)dateOfBirth {
// need to return [ [self alloc] init with the 3 paramaters]; here
// Or some other way to do so..
}
// Overridden init method
- (id)init:(NSString *)firstName with:(NSString *)lastName andWIth:(NSDate *)dateOfBirth {
self = [super init];
if (self) {
_firstName = firstName;
_lastName = lastName;
_dateOfBirth = dateOfBirth;
}
return self;
}
// Use the initialized instance variables in a greeting
- (void)sayHello {
NSLog(#"Hello %# %#", self.firstName, self.lastName);
}
And then in my main I am instantiating an XYZPerson object
XYZPerson *person = [XYZPerson person:#"John" with:#"Doe" andWith:[NSDate date]];
[person sayHello];
Can anybody give me a small pointer on how to do this correctly?

If I understand your question, you want the following:
+ (id)person:(NSString *)firstName with:(NSString *)lastName andWith:(NSDate *)dateOfBirth {
XYZPerson *result = [[self alloc] init:firstName with:lastName andWith:dateOfBirth];
return result;
}
If you aren't using ARC, add an autorelease to the return.
BTW - change the return type of the init method to instancetype instead of id.

#implementation XYZPerson
// Class Factory Method
+ (instanceType ) person:(NSString *)firstName with:(NSString *)lastName andWith:(NSDate *)dateOfBirth {
return [[[self class] alloc]init: firstName with: lastName andWith:dateOfBirth];
}
- (instanceType ) init:(NSString *)firstName with:(NSString *)lastName andWIth:(NSDate *)dateOfBirth {
self = [super init];
if (self) {
_firstName = [firstName copy];
_lastName = [lastName copy];
_dateOfBirth = [dateOfBirth copy];
//nb added copy to each of these, we do not own these objects, they could be lost to us..
/// or you could do this instead..
//assuming you synthesised getters/setters for (strong) properties..
[self setFirstName:firstName];
[self setLastName:lastName];
[self setDateOfBirth: dateOfBirth];
}
return self;
}

Related

NSMutableDictionary crashes with "mutating message sent to immutable object"

I have a class that has a NSMutableDictionary as a property:
#interface Alibi : NSObject <NSCopying>
#property (nonatomic, copy) NSMutableDictionary * alibiDetails;
#end
With the following constructor:
- (Alibi *)init
{
self = [super init];
_alibiDetails = [NSMutableDictionary dictionary];
return self;
}
and copy method:
- (Alibi *)copyWithZone:(NSZone *)zone
{
Alibi *theCopy = [[Alibi alloc] init];
theCopy.alibiDetails = [self.alibiDetails mutableCopy];
return theCopy;
}
When I try to call setObject:ForKey: I get a runtime error mutating method sent to immutable object.
I have the Alibi object declared in the view controller as #property (copy, nonatomic) Alibi * theAlibi; and I initialize it with self.theAlibi = [[Alibi alloc] init]; in viewDidLoad.
The line which crashes is:
NSString * recipient;
recipient = #"Boss";
[self.theAlibi.alibiDetails setObject:recipient forKey:#"Recipient"];
Please let me know what I am doing wrong here. I am coding for iOS 5 on iPhone.
You have a 'copy' property, which means exactly that - your NSMutableDictionary will get the -copy method called and return a regular NSDictionary before being assigned to the synthesized instance variable. This thread provides some information on some of your options as to solving this.
For the sake of completing this thread I will include my revised Alibi class below, this works as I require it to. If anyone notices any memory leaks or other issues, that would be appreciated.
#implementation Alibi
NSMutableDictionary *_details;
- (Alibi *)init
{
self = [super init];
_details = [NSMutableDictionary dictionary];
return self;
}
- (NSMutableDictionary *)copyDetails
{
return [_details mutableCopy];
}
- (NSMutableDictionary *)setDetails:(NSMutableDictionary *)value
{
_details = value;
return value;
}
- (void)addDetail:(id)value forKey:(id)key
{
[_details setObject:value forKey:key];
}
- (id)getDetailForKey:(id)key
{
return [_details objectForKey:key];
}
- (Alibi *)copyWithZone:(NSZone *)zone
{
Alibi *theCopy = [[Alibi alloc] init];
theCopy.serverId = [self.serverId copyWithZone:zone];
theCopy.user = [self.user copyWithZone:zone];
theCopy.startTime = [self.startTime copyWithZone:zone];
theCopy.endTime = [self.endTime copyWithZone:zone];
[theCopy setDetails:[self copyDetails]];
return theCopy;
}
#end

iOS NSKeyedUnarchiver calling singleton getter

I have a singleton class that will initialize it's data from a web service then save itself to a file (using NSCoding and NSKeyedUnarchiver) so I can just initialize this file instead of pulling all the data from the web again. Because it's a singleton instance that is being saved, I want to be able to call the singleton getter like you normally would, check to see if there's an archived copy and if not, pull the data down and archive it. Archiving is working fine, but when I try to call [[NSKeyedUnarchiver unarchiveObjectWithFile: filePath] retain] it calls the sharedInstance getter before sharedInstance is initialized. This causes init to be called and the app then downloads all the data again, just to be subsequently overwritten by the unarchiver.
Am I doing something wrong with my setup, or is there another way of Serializing this data?
Here's some (simplified) code:
#implementation Helmets
#synthesize helmetList, helmetListVersion;
//Class Fields
static Helmets *sharedInstance = nil;
// Get the shared instance and create it if necessary.
+ (Helmets *)sharedInstance {
//if(sharedInstance == nil){
//[Helmets unarchive]; //This does not work! Calls sharedInstance() again (recursion)
if(sharedInstance == nil){
sharedInstance = [[super allocWithZone:NULL] init]; //Pull from web service
[Helmets archive]; //Save instance
//}
//}
return sharedInstance;
}
- (id)init {
self = [super init];
if (self) {
helmetList = [[NSMutableArray alloc]init];
//Get our data from the web service and save it (excluded)
}
}
return self;
}
//This works!
+(void)archive{
if([NSKeyedArchiver archiveRootObject:sharedInstance toFile:[Helmets getFilePath]]){
NSLog(#"Archiving Successful");
}
else{
NSLog(#"Archiving Failed");
}
}
//This works, but calls getInstance causing data to be downloaded anyways!
+(void)unarchive{
// Check if the file already exists
NSFileManager *filemgr = [NSFileManager defaultManager];
NSString *filePath = [Helmets getFilePath];
if ([filemgr fileExistsAtPath: filePath])
{
sharedInstance = [[NSKeyedUnarchiver unarchiveObjectWithFile: filePath] retain];
}
[filemgr release];
}
Instance is initialized like:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
...
[Helmets unarchive]; //This calls sharedInstance() too soon!
[Helmets sharedInstance];
}
The class implements NSCoding in the .h and overrides initWithCoder and encodeWithCoder (Archiving is working).
Thanks in advance!
In the end you need a private method to set your shared instance in addition to the one you have, and you need a different init, again private to the implementation.
- (id)initAndFetch:(BOOL)fetch
{
if((self = [super init])) {
...
if(fetch) { do the web fetch };
...
}
}
In the +sharedInstance method, you will pass YES.
Then your decode will look like:
- (id)initWithCoder:(NSCoder *)decoder
{
if((self = [self initAndFetch:NO])) {
title = [decoder decodeObjectForKey:#"title"];
...
sharedInstance = self;
}
return self;
}
I have got to make it work the following manner.
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (!self) {
return nil;
}
UserInfo *user =[UserInfo sharedInstance];
return user;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
UserInfo *user =[UserInfo sharedInstance];
[aCoder encodeObject: user.phoneNumber forKey:#"phoneNumber"];
}
-(void)save
{
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:[UserInfo sharedInstance]] forKey:#"USER_DATA"];
[[NSUserDefaults standardUserDefaults]synchronize];
}
-(UserInfo*)currentUser
{
NSData *data =[[NSUserDefaults standardUserDefaults] objectForKey:#"USER_DATA"];
UserInfo *savedUser =[NSKeyedUnarchiver unarchiveObjectWithData:data];
UserInfo *user =[UserInfo sharedInstance];
return user;
}

Custom Object becoming _NSCFString upon entry into NSMutableArray

I'm having issues placing a custom object (WSWCMPost) into an NSMutableArray and then accessing the data stored in it later. Below is the relevant code.
Here is "WSWCMPost.h"
#import <Foundation/Foundation.h>
#interface WSWCMPost : NSObject
{
NSString *postBody;
NSString *postTitle;
NSString *postID;
}
#property (nonatomic, retain) NSString *postBody, *postTitle, *postID;
- init;
- (id)initWithID: (NSString*)ID AndBody: (NSString*)body AndTitle: (NSString*)title;
- (NSString*)postBody;
- (NSString*)postTitle;
- (NSString*)postID;
Here is "WSWCMPost.m"
#import "WSWCMPost.h"
#implementation WSWCMPost
#synthesize postBody, postTitle, postID;
- (id)init {
self = [super init];
if(self) {
postID = #"none";
postBody = #"none";
postTitle = #"none";
}
}
- (id)initWithID: (NSString*)ID AndBody: (NSString*)body AndTitle: (NSString*)title {
postTitle = title;
postID = ID;
postBody = body;
}
#end
And here is the "viewDidLoad" method that is causing my issues
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.detailViewController = (WSWCMDetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
// getting an NSString
NSLog(#"Pulling saved blogs...");
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:#"wswcmt1"];
if (dataRepresentingSavedArray != nil)
{
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if (oldSavedArray != nil)
_objects = [[NSMutableArray alloc] initWithArray:oldSavedArray];
else
_objects = [[NSMutableArray alloc] init];
}
NSLog(#"Pulled saved blogs...");
NSLog(!_objects ? #"Yes" : #"No");
#try {
NSLog(#"_objects description: %#",[_objects description]);
NSLog(#"_objects[0] postID: %#",[[_objects objectAtIndex:0] postID]);
}
#catch (NSException *exception) {
NSLog(#"Caught exception %#", exception);
NSLog(#"Objects doesnt exist, allocating memory...");
_objects = [[NSMutableArray alloc] init];
WSWCMPost *testPost = [[WSWCMPost alloc] initWithID:#"noID" AndBody:#"noBody" AndTitle:#"noTitle"];
[_objects insertObject:testPost atIndex:0];
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_objects] forKey:#"wswcmt1"];
}
if (!_objects ) {
NSLog(#"Objects doesnt exist...");
_objects = [[NSMutableArray alloc] init];
WSWCMPost *testPost = [[WSWCMPost alloc] initWithID:#"dne" AndBody:#"Dne" AndTitle:#"DNe"];
[_objects insertObject:testPost atIndex:0];
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_objects] forKey:#"wswcmt"];
}
[self refreshButton:nil];
}
And finally, here is the output
2012-06-25 22:39:49.345 WSWCM[4406:907] Pulling saved blogs...
2012-06-25 22:39:49.352 WSWCM[4406:907] Pulled saved blogs...
2012-06-25 22:39:49.355 WSWCM[4406:907] Yes
2012-06-25 22:39:49.356 WSWCM[4406:907] _objects description: (null)
2012-06-25 22:39:49.358 WSWCM[4406:907] _objects[0] postID: (null)
2012-06-25 22:39:49.360 WSWCM[4406:907] Objects doesnt exist...
2012-06-25 22:39:49.363 WSWCM[4406:907] Refresh Triggered...
I think that is all of the relevant code. If i forgot anything let me know please. This issue has been bothering me for hours...
While I'm not positive why it's giving you NSStrings instead of just blowing up normally, the problem seems to stem from the fact that your custom class, WSWCMPost, does not conform to the NSCoding protocol. Make sure that your custom objects implement this protocol if you want to store them in NSUserDefaults, since it doesn't know how to serialize the data otherwise.
To be more exact, you'll have to add these methods to your class implementation:
- (id)initWithCoder:(NSCoder *)coder {
self = [self initWithID:[coder decodeObjectForKey:#"id"] AndBody:[coder decodeObjectForKey:#"body"] AndTitle:[coder decodeObjectForKey:#"title"]];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[encoder encodeObject:postID forKey:#"id"];
[encoder encodeObject:postBody forKey:#"body"];
[encoder encodeObject:postTitle forKey:#"title"];
}
This will allow the data to be serialized by NSCoder. Once you've done this, you should clear all the information currently stored by NSUserDefaults to make sure that it doesn't contain any more NSStrings, but then everything should work properly. Of course, you'll have to update these two methods if you change the data stored by your WSWCMPost object.
Another thing to mention, you're having collisions with your getters/setters and their respective instance variables. So your implementation is:
interface
#interface WSWCMPost : NSObject
{
NSString *postBody; // don't need to do these anymore for properties
NSString *postTitle;
NSString *postID;
}
#property (nonatomic, retain) NSString *postBody, *postTitle, *postID;
implementation
#implementation WSWCMPost
#synthesize postBody, postTitle, postID;
- (id)init {
self = [super init];
if(self) {
postID = #"none"; // not prefixing your variables with 'self' so they are not getting retained
postBody = #"none";
postTitle = #"none";
}
}
#end
Here's how you should be writing those out:
interface
/** NOTE: No need to specify your instance variables here anymore, just the properties */
#interface WSWCMPost : NSObject
#property (nonatomic, retain) NSString *postID;
#property (nonatomic, retain) NSString *postTitle;
#property (nonatomic, retain) NSString *postBody;
implementation
#implementation WSWCMPost
/** Now you specify the corresponding instance variable name alongside the property name */
#synthesize postBody=_postBody, postTitle=_postTitle, postID=_postID;
- (id)init {
self = [super init];
if(self) {
self.postID = #"none"; //getting retained
self.postBody = #"none";
self.postTitle = #"none";
}
}
That would definitely cause data to be released too soon.
So the previous way you could type in self.postID or postID and the compiler wouldn't complain. The difference is when you type postID it is actually setting the member variable and not retaining it... where self.postID will release whatever it is currently set to and retain the new value if it's different.
By declaring your properties the new way, you have to either call the setter as self.postID or set the underlying instance variable as _postID. A lot of early iPhone books had you bang out properties that way and it just ends up causing all sorts of memory issues.
Hope this helps!
UPDATE!!!
You forgot to return self in your constructor ;) I bet that's it
- (id)init {
self = [super init];
if(self) {
self.postID = #"none"; //getting retained
self.postBody = #"none";
self.postTitle = #"none";
}
return self; // THIS IS WHY, you're constructor doesn't return an instance of the class... add this please
}
- (id)initWithID: (NSString*)ID AndBody: (NSString*)body AndTitle: (NSString*)title {
if(( self = [super init] ))
{
self.postTitle = title;
self.postID = ID;
self.postBody = body;
}
return self;
}
Your output definitely shows what was wrong in your code.
2012-06-25 21:51:07.691 WSWCM[4049:907] -[__NSCFString postID]: unrecognized selector sent to instance 0x1d003e80
2012-06-25 21:51:07.696 WSWCM[4049:907] Caught exception -[__NSCFString postID]: unrecognized selector sent to instance 0x1d003e80
These two lines tell you that NSString object does not recognize selector postID. This hint should be enough to find out where you need to see in depth.
See this Storing custom objects in an NSMutableArray in NSUserDefaults for more information.

Singleton pattern for sharing data between objects

I'm trying to pass data around objects using singleton pattern. Here is my code
SearchData.m
#implementation SearchData
#synthesize theName = _theName;
-(id)init
{
if(self = [super init])
{
_theName = #"Default";
}
return self;
}
static SearchData *sharedSingleton = NULL;
+(SearchData *)sharedSearchData
{
#synchronized(self)
{
if (sharedSingleton == NULL)
{
sharedSingleton = [[self alloc]init];
}
return sharedSingleton;
}
}
#end
FirstView.m
...
-(id)init
{
if (self = [super init])
{
SearchData *data = [SearchData sharedSearchData];
self.aName = [data theName];
}
return self;
}
...
The problem is that I get
Incompatible pointer types sending NSString to parameter of type NSStream.
What is wrong here ?
How to pass data to aName ivar ?
In your declaration of aName, did you mistype NSString as NSStream? Stranger things have happened.
Because searchdata (NSStream) doesn't go into an NSString.
Try:
[[NSString alloc] initWithData:searchData ...

Objective-C: why my NSString is not retaining its value?

The problem lies within the 'initWithCoder' method. When I want to retrieve "Coins_Key" from where I saved it by calling the 'saveData' method in my 'main' class and I pass in the key "self.keyName," the value of keyName is 0.
//Class coins.h
#property (retain) NSString* keyName;
#property (retain) NSString* keyValue;
//Class coins.m
#synthesize keyName;
-(void) saveData:(NSString *)number: (NSString *)keyID
{
self.keyName = keyID;
self.keyValue = number;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
NSLog(#"Encoded keyName: %#", keyName);
[encoder encodeObject:keyValue forKey:keyName];
}
- (id)initWithCoder:(NSCoder *)decoder {
self.keyValue = [decoder decodeObjectForKey:self.keyName];
NSLog(#"Decoded Coins: %#", self.keyValue);
return self;
}
//Class main
[Coins *coin3 = [[Coins alloc] init];
[coin3 saveData:#"6" :#"Coins_Key"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:coin3];
coin3 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
You're not quite grasping the encoder/decoder workflow.
Using the encodeObjectForKey: and decodeObjectForKey: methods properly, you should be passing as an argument the key that should be used to store the value. This key must remain constant.
You should also not require callers to provide the key your Coin object uses to store data. Take this simple example as a more correct/efficient method (assuming I understand the purpose of your class):
// Class Coins.h
#property (assign) int numberOfCoins;
// Class Coins.m
#define NUM_COINS_KEY #"NUM_COINS_KEY"
#synthesize numberOfCoins;
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) { // Use [super initWithCoder:decoder] here if your superclass supports it
self.numberOfCoins = [decoder decodeIntForKey:NUM_COINS_KEY];
NSLog(#"Decoded Coins: %d", self.numberOfCoins);
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
NSLog(#"Encoded keyName: %#", keyName);
[encoder encodeInt:self.numberOfCoins forKey:NUM_COINS_KEY];
}
// Class main
Coins *coin = [[Coins alloc] init];
coin.numberOfCoins = 6;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:coin];
[coin release]; // If you're just playing around, this is probably overkill, but a good habit
coin = [NSKeyedUnarchiver unarchiveObjectWithData:data];