call to setter gives unrecognized selector error - ios

I'm trying to subclass NSString to give it a property that is another NSString. No matter what I try with #synthesize or #dynamic or manually coding my setters and getters, I keep getting this "unrecognized selector sent to instance" error when I compile.
I know this has probably already been answered but after looking through about 15 other "unrecognized selector" questions on this site I am still at a loss. Sorry, I started coding VERY recently so I'm probably just missing something exceedingly simple.
Furigana.h
#interface Furigana : NSString
#property (strong, nonatomic) NSString *forKanji;
-(void)setForKanji:(NSString *)forKanji; //fails the same with or without this line
#end
Furigana.m
#import "Furigana.h"
#implementation Furigana
#synthesize forKanji = _forKanji;
//#dynamic forKanji; tried this
-(NSString *)forKanji { //also fails when custom setter/getters are left out
if(!_forKanji) _forKanji = [[NSString alloc] init];
return _forKanji;
}
-(void)setForKanji:(NSString *)forKanji {
if(!_forKanji) _forKanji = [[NSString alloc] init];
_forKanji = forKanji;
}
#end
Reibun.m
#import "Furigana.h"
-(NSArray *)parseFurigana:(NSArray *)unparsedData {
NSMutableArray *furigana = [[NSMutableArray alloc] init]; //of Furigana
for(unsigned int x = 0; x < [unparsedData count]; x++){
NSArray *components = [[NSArray alloc] init];
components = [[unparsedData objectAtIndex:x] componentsSeparatedByString:#":"];
Furigana *newFurigana = [[Furigana alloc] init];
newFurigana = [components objectAtIndex:1];
NSLog(#"stored string: %#",newFurigana); //so far so good
newFurigana.forKanji = [components objectAtIndex:0]; //crash
//[newFurigana setForKanji:[components objectAtIndex:0]]; //this fails too
[furigana addObject:newFurigana];
}
return furigana;
}

Here is the line that breaks your code:
newFurigana = [components objectAtIndex:1];
It replaces the object that you have allocated using [[Furigana alloc] init] with an NSString object, which does not respond to setForKanji: selector.
The assignment replaces newFurigana object, not the content of it. The content cannot be changed, because you derive from NSString, which is immutable.
Replace the init with initWithString, call [super initWithString:] from it, and make the initialization inside your loop look like this:
Furigana *newFurigana = [[Furigana alloc] initWithString:[components objectAtIndex:1]];

Related

Objective C Properties memory issue

i have to show the user details from NSUserDefaults in more than 5 view controllers. So i have created a NSObject subclass, which will load the user details from server when the first view controllers viewDidLoad is called.
Here is my First view controller viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// Getting the Current User Details
CurrentUserDetails *userDetails = [[CurrentUserDetails alloc]init];
[userDetails initializeTheCurrentUserData];
//CurrentUserDetails is my NSObject class
}
And
#import <Foundation/Foundation.h>
#interface CurrentUserDetails : NSObject
#property(strong,nonatomic) NSString *memberName;
#property(strong,nonatomic) NSString *designation;
#property(strong,nonatomic) NSString *memberType;
#property(strong,nonatomic) NSString *entreprenuer;
#property(strong,nonatomic) NSDate *expiryDate;
#property(strong,nonatomic) NSData *imageData;
- (void) initializeTheCurrentUserData;
#end
and implementation
#implementation CurrentUserDetails
- (void) initializeTheCurrentUserData{
NSData *data = [[NSUserDefaults standardUserDefaults] valueForKey:#"userDictionary"];
NSDictionary *retrievedDictionary = [NSKeyedUnarchiver unarchiveObjectWithData:data];
self.memberName = [retrievedDictionary valueForKey:#"Name"];
self.designation = [retrievedDictionary valueForKey:#"Designation"];
self.memberType = [[retrievedDictionary valueForKey:#"Member_type"] stringValue];
self.expiryDate = [retrievedDictionary valueForKey:#"Expiry"];
self.kanaraEntreprenuer = [retrievedDictionary valueForKey:#"CityName"];
NSString *imageUrl = [retrievedDictionary valueForKey:#"Member_image"];
self.imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#%#",[GlobalVariables getBaseURLForMemberImage],imageUrl]]];
}
And when iam trying to take the details from other class like this..
CurrentUserDetails *userDetails = [[CurrentUserDetails alloc]init];
memberName = userDetails.memberName;
designation = userDetails.designation;
memberType = userDetails.memberType;
dateFromServer = userDetails.expiryDate;
entreprenuer = userDetails.entreprenuer;
imageDataFromServer = userDetails.imageData;
I am getting nil values.
But if call initializeTheCurrentUserData method each time, i am getting the exact values. I though once a property is assigned with a value , we can use the property for entire program. I'm getting confusion.. Can anyone please tell me about this????. Do i need to call initializeTheCurrentUserData everytime when i want to use the values?
Once you set a property of an instance, that property remains for that instance. You, however, are creating new instances with [[CurrentUserDetails alloc] init]. Each new instance will be initialized with default values (nil for NSString).
Call -initializeTheCurrentUserData in -init so each instance will be initialized with the values from user defaults.
#implementation CurrentUserDetails
- (instancetype)init {
self = [super init];
if (self != nil) {
[self initializeTheCurrentUserData];
}
return self;
}
- (void)initializeTheCurrentUserData {
…
}

NSString : leak when assigning value to a property

Assuming we don't use ARC.
Suppose we have a very simple class in which we declare 2 NSString properties, like this :
#interface Foo : UIView {}
-(id)initWithArguments:(NSString*)mess title:(NSString*)tit;
#property(nonatomic, retain) NSString *message;
#property(nonatomic, retain) NSString *title;
#end
and in implementation :
#implementation Foo
#synthesize message, title;
-(id)initWithArguments:(NSString*)mess title:(NSString*)tit{
if((self = [super init])){
message = mess; // (1)
self.title = tit; // (2)
(...)
}
return self;
}
-(void)dealloc{
message = nil;
title = nil;
[super dealloc];
}
#end
Now if I call a method from another class, in which I create 2 NSString and an instance of Foo , like this :
-(void)someMethod{
NSString *string1 = [NSString stringWithFormat:#"some text with %d things", 5];
NSString *string2 = [NSString stringWithFormat:#"other text with %d things", 5];
Foo *foo = [[Foo alloc] initWithArguments:string1 title:string2];
}
The whole code works fine and doesn't crash, but, if I profile it with instruments,
it doesn't cause a leak when calling (1)("message = mess;")
it cause a leak when calling (2)("self.title = tit;")
It's very confusing, because stringWithFormat is an autoreleased object, isn't it ?
So, how an autoreleased object can cause a leak when assigning to a property ???
I read somewhere that it's almost always better to use the "self.text = value;" form instead of the "text = value;" form, because the second one may cause a leak.
Actually, in this code it's the contrary.
And... If I use a constant NSString like #"some text", instead of the values returned by [NSString stringWithFormat], there is no leak, of course.
Any idea ?
You have forgotten to invoke the (compiler-generated) setter methods in a few cases:
self.message = mess; // in init method
self.message = nil; // in dealloc method
self.title = nil; // ditto
It's crucial that you use the setter/getter methods in non-ARC code.

Strong and weak event.NSString [duplicate]

I wrote the following sample code to see how ARC works
#property (nonatomic, weak) NSString *myString;
#property (nonatomic, weak) NSObject *myObj;
#end
#implementation ViewController
#synthesize myString = _myString;
#synthesize myObj = _myObj;
- (void) viewDidAppear:(BOOL)animated
{
NSLog(#"Appearing Obj: !%#!",self.myObj);
NSLog(#"Appearing String: !%#!",self.myString);
}
- (void)viewDidLoad
{
self.myObj = [[NSObject alloc] init];
self.myString = [[NSString alloc] init];
NSLog(#"Loading Obj %#",self.myObj);
NSLog(#"Loading String: !%#!",self.myString);
}
However surprisingly I got these results:
2012-06-19 15:08:22.516 TESTER[4041:f803] Loading Obj (null)
2012-06-19 15:08:22.517 TESTER[4041:f803] Loading String: !!
2012-06-19 15:08:22.533 TESTER[4041:f803] Appearing Obj: !(null)!
2012-06-19 15:08:22.535 TESTER[4041:f803] Appearing String: !!
As you can see, Obj got released properly but my string (which is also a weak property) does not print out null...Why not?
NSString uses all sorts of internal trickery to reuse objects and avoid unnecessary allocations and copies. It can do this because NSString instances are immutable. In this case there is probably a shared instance to represent an empty string which is being returned by [[NSString alloc] init], and this shared instance will be retained somewhere else as a singleton.
[[NSString alloc] init] always returns identical value. You can check it by yourself.
NSString *string1 = [[NSString alloc] init];
NSString *string2 = [[NSString alloc] init];
NSString *string3 = [[NSString alloc] init];
NSLog(#"string1 = %p, string2 = %p, string3 = %p", string1, string2, string3)
This code returns three identical addresses. In my case, output was:
string1 = 0x3e8dd74c, string2 = 0x3e8dd74c, string3 = 0x3e8dd74c
That means [[NSString alloc] init] returns Singleton. Singletons usually can't be released.
Making strings with other methods (like initWithFormat:) makes usual 'non-singleton' objects, which usually can be released, with some exceptions.
Further:
Looking source code (Assembler):
-[NSPlaceholderString init]:
00040ea4 f64b009c movw r0, 0xb89c
00040ea8 f2c00016 movt r0, 0x16
00040eac 4478 add r0, pc
00040eae 4770 bx lr
it would be something like this (in objectiveC)
-(id)init
{
return SOME_CONSTANT_VALUE;
}
It might be kCFEmptyString, but I'm not sure.

Using NSMutableArray to keep Objects

I think that I'm missing some fundamental knowledge on Xcode Objective C programming standards. Unfortunately I couldn't find the appropriate solution to my problem.
The problem is that when I try to keep data in an array of objects it becomes impossible to keep them separately. Adding new objects overwrites the previous objects in array. Here is some code about that:
CustomObject.m file:
#import "CustomObject.h"
NSString * title;
NSString * detail;
#implementation CustomObject
- (void) initCustomObjectWithValues : (NSString *) iTitle : (NSString *) iDetail {
title = [NSString stringWithString:iTitle];
detail = [NSString stringWithString:iDetail];
}
- (NSString *) getTitle {
return title;
}
- (NSString *) getDetail {
return detail;
}
#end
viewDidLoad function in ViewController.m file:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
myMutableArray = [[NSMutableArray alloc] init];
for (int i=0; i<10; i++) {
NSString * tempTitle = [#"title " stringByAppendingString:[NSString stringWithFormat:#"%d",i]];
CustomObject * myCustomObject = [[CustomObject alloc] init];
[myCustomObject initCustomObjectWithValues :[NSString stringWithFormat:#"%#",tempTitle]
:[#"detail " stringByAppendingString:[NSString stringWithFormat:#"%d",i]]];
[myMutableArray addObject:myCustomObject];
}
for (int i=0; i<10; i++) {
NSLog(#"%#",[[myMutableArray objectAtIndex:i] getTitle]);
NSLog(#"%#",[[myMutableArray objectAtIndex:i] getDetail]);
NSLog(#"----------------------------");
}
}
Here, myMutableArray is defined at the top of the ViewController.m file. (To make it global and can be used in other functions in future)
Here what I've got in the logs:
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
As far as I understand each new added object overwrites the olds. First I thought that they are referring to same allocated memory but in debug tool myMutableArray seems like this:
Printing description of myMutableArray:
<__NSArrayM 0x8d8cb60>(
<CustomObject: 0x8d8e990>,
<CustomObject: 0x8d8dd40>,
<CustomObject: 0x8d8d2e0>,
<CustomObject: 0x8d8d470>,
<CustomObject: 0x8d8d350>,
<CustomObject: 0x8d8ddf0>,
<CustomObject: 0x8d8df00>,
<CustomObject: 0x8d8df40>,
<CustomObject: 0x8d8dff0>,
<CustomObject: 0x8d8e0c0>
)
Does anyone have an idea about the solution. It should be something very basic but I can't catch the problem.
Thank you all in advance
using
NSString * title;
NSString * detail;
outside of the #interface part creates global variables. When you assign a variable to title or detail you don't set an instance variable of your object, you change those global variables. And since they are global, they are the same for all objects that reference them.
Turn those global variables into instance variables, or even better use #property.
Your code is bad objective-c overall.
You should not use get in getters that return variables. You should not have methods that start with init and don't return self. You should only call init in [[Foo alloc] init...] situations. You should avoid unnamed parameters in your methods.
And there is no need to create strings from strings from strings.
Here is how I would write it:
// CustomObject.h
#interface CustomObject : NSObject
#property (copy, nonatomic) NSString * title;
#property (copy, nonatomic) NSString * detail;
- (id)initWithTitle:(NSString *)title detail:(NSString *)detail
#end
// CustomObject.m
#import "CustomObject.h"
#implementation CustomObject
- (id)initWithTitle:(NSString *)title detail:(NSString *)detail {
self = [super init];
if (self) {
// use stringWithString: to create #"" strings when title is nil
// if nil is a valid value for those variables you should use
// _title = [title copy];
_title = [NSString stringWithString:title];
_detail = [NSString stringWithString:detail];
}
return self;
}
#end
for (int i=0; i<10; i++) {
NSString *tempTitle = [NSString stringWithFormat:#"title %d",i];
NSString *tempDetail = [NSString stringWithFormat:#"detail %d",i];
CustomObject * myCustomObject = [[CustomObject alloc] initWithTitle:tempTitle detail:tempDetail];
[myMutableArray addObject:myCustomObject];
}
for (int i=0; i<10; i++) {
CustomObject *object = myMutableArray[i];
NSLog(#"%#", object.title);
// or NSLog(#"%#", [object title]); if you don't like dot-notation.
NSLog(#"%#", object.detail);
NSLog(#"----------------------------");
}

Setting up a plist to store application data (not settings) for an iPhone game

I am writing an iPhone game and have it working well with my level data hard coded but I would like to store the level data in a plist a load it at launch. I have never used plists and am having trouble understanding how I should set the plist up based on my data model.
Here is how I have it hard coded now:
(in my appDelegate)
- (void)loadLevels {
//setup NSNumber objects to load into sequences
NSNumber * rID = [[[NSNumber alloc] initWithInt:0] autorelease];
NSNumber * bID = [[[NSNumber alloc] initWithInt:1] autorelease];
NSNumber * gID = [[[NSNumber alloc] initWithInt:2] autorelease];
NSNumber * yID = [[[NSNumber alloc] initWithInt:3] autorelease];
NSNumber * rbID = [[[NSNumber alloc] initWithInt:4] autorelease];
NSNumber * rgID = [[[NSNumber alloc] initWithInt:5] autorelease];
NSNumber * ryID = [[[NSNumber alloc] initWithInt:6] autorelease];
NSNumber * bgID = [[[NSNumber alloc] initWithInt:7] autorelease];
NSNumber * byID = [[[NSNumber alloc] initWithInt:8] autorelease];
NSNumber * gyID = [[[NSNumber alloc] initWithInt:9] autorelease];
//Level One's Sequence
NSMutableArray * aSequence = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];
[aSequence addObject: rID];
[aSequence addObject: bID];
[aSequence addObject: gID];
[aSequence addObject: yID];
[aSequence addObject: rbID];
[aSequence addObject: rgID];
[aSequence addObject: ryID];
[aSequence addObject: bgID];
[aSequence addObject: byID];
[aSequence addObject: gyID];
// Load level One
_levels = [[[NSMutableArray alloc] init] autorelease];
Level *level1 = [[[Level alloc] initWithLevelNum:1 levelSpeed:1.0 levelSequence:aSequence] autorelease];
[_levels addObject:level1];
//do the same thing for subsequent levels//
}
(this is how I have my Level Class implemented)
#import "Level.h"
#implementation Level
#synthesize levelNum = _levelNum;
#synthesize levelSpeed = _levelSpeed;
#synthesize levelSequence = _levelSequence;
- (id)initWithLevelNum:(int)levelNum levelSpeed:(float)levelSpeed levelSequence:(NSMutableArray *)levelSequence {
if ((self = [super init])) {
self.levelNum = levelNum;
self.levelSpeed = levelSpeed;
self.levelSequence = [[[NSMutableArray alloc] initWithArray:levelSequence] autorelease];
}
return self;
}
- (void)dealloc {
[_levelSequence release];
_levelSequence = nil;
[super dealloc];
}
#end
I'm just not getting how to set up a plist to store my data to match my model. Can anyone give me some advise please?
ADDITION:
(here is how I think I need to setup the plist - the data model is simply the three variables initializing my level (above). If you look at my current plist it might be more clear how it is setup, but each level is comprised of: a level number, a level speed, and an array of numbers denoting the required sequence for that level.)
Now, if I do have this setup correctly how do I load the values into my program?
Add your plist file as a resource file in your project. Say, it's name is config.plist
Get the path for the resource file. (Though, be careful of [NSBundle mainBundle] as it will not return the unit test bundle in a unit test.)
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"config" ofType:#"plist"];
Your root object is an array of levels. Load it in a NSArray.
// this is autoreleased. you can retain it if needed
NSArray *levelArray = [NSArray arrayWithContentsOfFile:plistPath];
Now you have the array of levels. Each level is a dictionary. Load the level i (counting from 0).
NSDictionary *level = [levelArray objectAtIndex:i];
Now you can get the objects from level dictionary by using objectForKey method. For example, to get the sequence array:
NSArray *seq = [level objectForKey:#"levelSequence"];
You can loop through the levelArray to alloc, init and add to levels array for all your levels.
Hope it helps. Please note that I have not compiled the code, so there might be some typos.
What you should do is create an NSDictionary with all of the applicable data that you want, and read it from and write it to NSUserDefaults.

Resources