I am very new to iOS development and programming in Objective-C. I have been doing the exercises on the app dev library.
This is the current exercise that I am trying to understand.
3. Test what happens if you set a mutable string as the person’s first name, then mutate that string before calling your modified sayHello method. Change the NSString property declarations by adding the copy attribute and test again.
I attempt to do this however, the NSString that I modify does in fact change despite the use of the copy property attribute.
Here are my declarations and implementations as well as my test code.
XYZPerson.h
#import <Foundation/Foundation.h>
#interface XYZPerson : NSObject
#property (copy) NSString *firstName;
#property NSString *lastName;
#property NSDate *dob;
- (void)sayHello;
- (void)saySomething:(NSString *)greeting;
+ (id)init;
+ (id)personWithFirstName:(NSString *)firstName lastName:(NSString *)lastName dob:(NSDate *)dateOfBirth;
#end
//XYZPerson.m
#import "XYZPerson.h"
#implementation XYZPerson
#synthesize firstName = _firstName;
#synthesize lastName = _lastName;
#synthesize dob = _dob;
- (void)sayHello {
[self saySomething:#"Hello World!"];
NSLog(#"This is %# %#", self.firstName, self.lastName);
}
- (void)saySomething:(NSString *)greeting {
NSLog(#"%#", greeting);
}
+ (id)init {
return [self personWithFirstName:#"Yorick" lastName:#"Robinson" dob:8/23/1990];
}
+ (id)personWithFirstName:(NSString *)firstName lastName:(NSString *)lastName dob:(NSDate *)dateOfBirth{
XYZPerson *person = [[self alloc] init];
person.firstName = firstName;
person.lastName = lastName;
person.dob = dateOfBirth;
return person;
}
#end
//Test code
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "XYZPerson.h"
#import "XYZShoutingPerson.h"
int main(int argc, char *argv[])
{
#autoreleasepool {
XYZPerson *guy = [XYZPerson init];
[guy sayHello];
//I thought that this change would never be made, but it is everytime I run the code.
guy.firstName = #"Darryl";
[guy sayHello];
XYZShoutingPerson *girl = [XYZShoutingPerson init];
[girl sayHello];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
Consider this shorter example (which runs in CodeRunner btw):
#import <Foundation/Foundation.h>
#interface Person : NSObject
#property (nonatomic,strong) NSString *name; // strong should be copy
#end
#implementation Person
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
Person *p = [Person new];
NSMutableString *name = [[NSMutableString alloc] initWithString:#"Alice"];
p.name = name;
NSLog(#"%#",p.name); // prints Alice
[name appendString:#"xxx"];
NSLog(#"%#",p.name); // prints Alicexxx
}
}
I'm pointing the name to a mutable string, then appending some characters. As a result, the name has changed inside the object. However, if you replace strong with copy when declaring the property, a new immutable string will be created just for the Person object.
The moral of the story is, using copy prevents side effects when someone passes an object and then that object changes.
The message -[NSString copy] results in a copy when a mutable string is passed (NSMutableString) or retain when it is immutable (NSString). Therefore, always copy when declaring NSString properties:
#property (nonatomic,copy) NSString *string; // OK
#property (nonatomic,strong) NSString *string; // strong should be copy
I ran into this problem when I was doing the same book. I added copy and the exact same thing happened, it kept mutating when I appending something to the NSMutableString variable that I used for firstName. Then I read this section:
If you need to set a copy property’s instance variable directly, for example in an initializer method, don’t forget to set a copy of the original object:
-(id)initWithSomeOriginalString:(NSString *)aString {
self = [super init];
if (self) {
_instanceVariableForCopyProperty = [aString copy];
}
return self;
}
So, I went back into my XYZPerson.m and looked at my init code.
I changed:
- (id)initWithFirstName:(NSMutableString *)aFirstName lastName:(NSString *)aLastName
dateOfBirth:(NSDate *)aDate {
self = [super init];
if (self) {
_firstName = aFirstName;
_lastName = aLastName;
_dateOfBirth = aDate;
}
return self;
}
To:
- (id)initWithFirstName:(NSMutableString *)aFirstName lastName:(NSString *)aLastName
dateOfBirth:(NSDate *)aDate {
self = [super init];
if (self) {
_firstName = [aFirstName copy];
_lastName = aLastName;
_dateOfBirth = aDate;
}
return self;
}
And presto-chango: it worked the correct way! It made a copy of the NSMutableString that I had used that did not mutate when I appended something to the end of it before the method call.
I think you are misunderstanding of what copy does.
NSMutableString *string = [NSMutableString stringWithString:#"test"];
XYZPerson *guy = [XYZPerson init];
guy.firstName = string;
guy.lastName = string;
[string replaceCharactersInRange:NSMakeRange(1, 1) withString:#"x"];
[guy sayHello];
Output
This is test txst
In this example, firstName is copy do it doesn't change when string is changed, lastName is not copy so it value is changed when the mutable string string is changed.
What happened here is lastName and string are the same object, so when string is changed lastName is changed as a side effect. This is considered very bad and you never want this behavior. Using copy makes sure firstName and string are different objects and changes to string cannot effect firstName.
Related
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.
I am trying to subclass NSMutableData to add the ability to subdata without copying. Here is code
#interface myMutableData : NSMutableData
- (NSData *)subdataWithNoCopyingAtRange:(NSRange)range;
#end
#interface myMutableData()
#property (nonatomic, strong) NSData *parent;
#end
#implementation myMutableData
- (NSData *)subdataWithNoCopyingAtRange:(NSRange)range
{
unsigned char *dataPtr = (unsigned char *)[self bytes] + range.location;
myMutableData *data = [[myMutableData alloc] initWithBytesNoCopy:dataPtr length:range.length freeWhenDone:NO];
data.parent = self;
return data;
}
#end
But the problem is when I try to instantiate myMutableData, I got this error
"-initWithCapacity: only defined for abstract class. Define -[myMutableData initWithCapacity:]!'"
Why? So inheritance does not work? Thanks
NSData and NSMutableData are part of a class cluster. That means you need to do more work when subclassing to ensure that your subclass is fully valid.
In other words, don't subclass...
It's much easier for you to do what you want using a category, a wrapper or a helper / utility class. The best option is probably a wrapper which can return either the internal data directly or a specified range of the data.
This calls for a category. However, a category cannot by default have properties and instance variables. Hence you need to #import <objc/runtime.h> and use associated objects to get and set value of parent.
#interface NSMutableData(myMutableData)
- (NSData *)subdataWithNoCopyingAtRange:(NSRange)range;
#property (nonatomic, strong) NSData *parent;
#end
#implementation NSMutableData(myMutableData)
- (NSData *)subdataWithNoCopyingAtRange:(NSRange)range
{
unsigned char *dataPtr = (unsigned char *)[self bytes] + range.location;
NSMutableData *data = [[NSMutableData alloc] initWithBytesNoCopy:dataPtr length:range.length freeWhenDone:NO];
data.parent = self;
return data;
}
-(NSData*)parent
{
return objc_getAssociatedObject(self, #selector(parent));
}
-(void)setParent:(NSData *)parent
{
objc_setAssociatedObject(self, #selector(parent), parent, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#end
I am a beginner in Objective-C, so i try to accomplish Apple Exercises from this post: Apple Objective-C guide
I trying to create class properties with custom getter and setter, one with "weak" attribute, and one with "copy" attribute:
#property (weak, getter=getFirstName, setter=setFirstName:) NSString *firstName;
#property (copy, getter=getFirstName, setter=setFirstName:) NSString *secondName;
Then, i test these properties like this:
NSString *name = #"John";
NSMutableString *surname = [NSMutableString stringWithString:#"Doe"];
MyPerson *person = [MyPerson createWithFirstName:name secondName:surname];
[person tellName];
name = nil;
[person tellName];
[NSThread sleepForTimeInterval:1.0f];
[surname appendString:#"dze"];
[person tellName];
So I expect then after name set to nil, weak property also became a nil; and property marked as "copy" will handle their own copy of initial string, and appending some values to initial strings wouldn't cause any consequences in MyPerson object. But, when i call log methods, i have original value for firstName and changed value in secondName, just like there is no "weak" and "copy" attributes.
This is a log what i gor:
First name: John; Second name: Doe; BirthDate: (null)
First name: John; Second name: Doe; BirthDate: (null)
First name: John; Second name: Doedze; BirthDate: (null)
And I am expecting something like this:
First name: John; Second name: Doe; BirthDate: (null)
First name: Not specified; Second name: Doe; BirthDate: (null)
First name: Not specified; Second name: Doe; BirthDate: (null)
I understand that in second line GC can not destroy value from firstName, but third call to log method executes after 1-second sleep. I think 1 second is enough time for GC to collect unused strings.
Any idead why "weak" and "copy" attributes can be ignored?
Thanks for help.
Full code:
main.m:
#import <Foundation/Foundation.h>
#import "MyPerson.h"
int main(int argc, const char * argv[])
{
#autoreleasepool
{
NSString *name = #"John";
NSMutableString *surname = [NSMutableString stringWithString:#"Doe"];
MyPerson *person = [MyPerson createWithFirstName:name secondName:surname];
[person tellName];
name = nil;
[person tellName];
[NSThread sleepForTimeInterval:1.0f];
[surname appendString:#"dze"];
[person tellName];
}
return 0;
}
MyPerson.h:
#import <Foundation/Foundation.h>
#interface MyPerson : NSObject
#property (weak, getter=getFirstName, setter=setFirstName:) NSString *firstName;
#property (copy, getter=getSecondName, setter=setSecondName:) NSString *secondName;
#property (getter=getBirthDate, setter=setBirthDate:) NSDate *birthDate;
-(void)setFirstName:(NSString *)firstName;
-(NSString *)getFirstName;
-(void)setSecondName:(NSString *)secondName;
-(NSString *)getSecondName;
-(void)setBirthDate:(NSDate *)birthDate;
-(NSDate *)getBirthDate;
-(void) tellName;
+(id)create;
+(id)createWithFirstName:(NSString *)firstName;
+(id)createWithFirstName:(NSString *)firstName secondName:(NSString *)secondName;
+(id)createWithFirstName:(NSString *)firstName secondName:(NSString *)secondName birthDate:(NSDate *)birthDate;
-(id)initWithFirstName:(NSString *)firstName secondName:(NSString *)secondName birthDate:(NSDate *)birthDate;
#end
MyPerson.m:
#import "MyPerson.h"
#implementation MyPerson
#synthesize firstName = m_firstName;
#synthesize secondName = m_secondName;
#synthesize birthDate = m_birthDate;
-(void)tellName
{
NSMutableString *stringBuilder = [NSMutableString string];
[stringBuilder appendString:#"First name: "];
[self appendIfNotNil:self.firstName toBuilder:stringBuilder];
[stringBuilder appendString:#"; "];
[stringBuilder appendString:#"Second name: "];
[self appendIfNotNil:self.secondName toBuilder:stringBuilder];
[stringBuilder appendString:#"; "];
[stringBuilder appendString:#"BirthDate: "];
[stringBuilder appendFormat:#"%#", [m_birthDate descriptionWithLocale:NSLocaleLanguageDirectionUnknown]];
NSLog(#"%#", stringBuilder);
}
-(void)appendIfNotNil:(NSString*)str toBuilder:(NSMutableString *)stringBuilder
{
[stringBuilder appendString:str == nil ? #"Not specified" : str];
}
-(void)setFirstName:(NSString *)firstName
{
NSLog(#"Setter called for first name: %#", firstName);
m_firstName = firstName;
}
-(NSString *)getFirstName
{
NSLog(#"Getter called for first name");
return m_firstName;
}
-(void)setSecondName:(NSString *)secondName
{
NSLog(#"Setter called for second name: %#", secondName);
m_secondName = secondName;
}
-(NSString *)getSecondName
{
NSLog(#"Getter called for second name");
return m_secondName;
}
-(id)initWithFirstName:(NSString *)firstName secondName:(NSString *)secondName birthDate:(NSDate *)birthDate
{
self = [super init];
if (self)
{
m_firstName = firstName;
m_secondName = secondName;
m_birthDate = birthDate;
}
return self;
}
+(id)createWithFirstName:(NSString *)firstName secondName:(NSString *)secondName birthDate:(NSDate *)birthDate
{
MyPerson *person = [MyPerson alloc];
person = [person initWithFirstName:firstName secondName:secondName birthDate:birthDate];
return person;
}
+(id)createWithFirstName:(NSString *)firstName secondName:(NSString *)secondName
{
return [MyPerson createWithFirstName:firstName secondName:secondName birthDate:nil];
}
+(id)createWithFirstName:(NSString *)firstName
{
return [MyPerson createWithFirstName:firstName secondName:nil birthDate:nil];
}
+(id)create
{
return [MyPerson createWithFirstName:nil secondName:nil birthDate:nil];
}
#end
Any ideas why "weak" and "copy" attributes can be ignored?
Any idead why "weak" and "copy" attributes can be ignored? Thanks for help.
They are not ignored. Your tests are flawed.
To test "weak", you would need to use something other than an NSString, because strings have special memory management. Use an NSObject with no variable reference to it; it will vanish in puff of smoke one line later.
self.ob = [NSObject new];
NSLog(#"%#", self.ob); // nil!
To test "copy", you would need to start with an NSMutableString, keep a reference to it, assign it to the property, and then mutate the mutable string; the assigned string would not change, proving that the property is not holding another reference to the same mutable string.
To test memory management attributes (like copy), as well as threading attributes (like atomic), do not write your own setter! These are instructions to the compiler on how it should write the setter (synthesis). If you write the setter manually, you do not get synthesis, and at that point your attributes are meaningless (except insofar as they inform clients of your API).
My code was compiling and running great until I upgraded to Xcode 6.
Definition shows a Warning : Auto property synthesis will not synthesize property 'hash' because it is 'readwrite' but it will be synthesized 'readonly' via another property
#property (nonatomic, strong) NSString *hash; // (get/compute) hash code of the place (master hash of images)
Implementation shows error whenever I access to _hash: Use of undeclared identifier '_hash'
-(NSString *)hash {
if (_hash) return _hash;
// If place id, take it as the hash code
NSString *poiID = self.info[#"id"];
if (poiID) {
_hash = [NSString stringWithFormat:#"id-%lu",(unsigned long)[self.address hash]];
}
else if (CLLocationCoordinate2DIsValid(self.location.coordinate)) {
NSString *seed = [NSString stringWithFormat:#"%f,%f", self.location.coordinate.latitude, self.location.coordinate.longitude];
_hash = [NSString stringWithFormat:#"location-%lu",(unsigned long)[seed hash]];
}
else if (self.address) {
NSString *seed = self.address;
_hash = [NSString stringWithFormat:#"address-%lu",(unsigned long)[seed hash]];
}
else {
_hash = #"POI-unknownIDLocationOrAddress";
}
return _hash;
}
It doesn't compile because hash is already part of NSObject:
See:
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Protocols/NSObject_Protocol/index.html#//apple_ref/occ/intfm/NSObject/hash
You need to add the following line to auto generate the setter method:
#synthesize hash = _hash;
If you don't want a setter method and only want a read-only property:
#property (nonatomic, strong, readonly) NSString *hash;
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(#"----------------------------");
}