Trouble with NSString and copy attribute - objective-c

I'm learning Objective C with the book "Programming with Objective C", but I'm stuck with the exercises 3 on page 66.
I have the following properties:
#property (copy, readonly) NSString *lastName;
#property (copy, readonly) NSString *firstName;
the following methods:
+ (id) personWithLastName:(NSString *)aLastName andFirstName:(NSString *)aFirstName {
return [[self alloc] initWithLastName:aLastName andFirstName:aFirstName;
}
- (id) initWithLastName:(NSString *)aLastName andFirstName:(NSString *)aFirstName {
self = [super init];
if (self) {
_lastName = aLastName;
_firstName = aFirstName;
}
return self;
}
- (void) sayHello {
NSString *greeting = [NSString stringWithFormat:#"Hello, %# %#!", [self firstName], [self lastName]];
[self saySomething:greeting];
}
and the following code in the main:
NSMutableString *firstName = [NSMutableString stringWithString:#"Steve"];
NSString *lastName = #"Jobs";
XYZPerson* firstPerson = [XYZPerson personWithLastName:lastName andFirstName:firstName];
[firstPerson sayHello];
which correctly print "Hello, Steve Jobs!".
Later, I added the following lines:
[firstName setString:#"John"];
[firstPerson sayHello];
which edit the property firstName in the object firstPerson, and print "Hello, John Jobs!".
However, firstName have the copy attribute, so why this happen?

Well let's break this down...
This property says that we want only an accessor to be generated (the readonly part) plus we take ownership of the object via copy upon assignment (which in this case is never gonna happen because of the readonly)
#property (copy, readonly) NSString *lastName;
So a better form would be (unless you're interested in redeclaration of the property as readwrite in a class extension, like in this answer):
#property (readonly) NSString *lastName;
and now we copy inside our init method before assignment to the backing ivars:
- (id) initWithLastName:(NSString *)aLastName andFirstName:(NSString *)aFirstName {
self = [super init];
if (self) {
_lastName = [aLastName copy];
_firstName = [aFirstName copy];
}
return self;
}
and of course the same applies to your other property also.

The firstName and Lastname you are using in your main method are not the properties you declared, but instead new instance variables. To access the properties you want you can use _firstName to use the ivar or self.firstName to use the property's setter and getter methods.
EDIT: Disregard the above. Because you are using the ivar directly instead of calling the accessor method, the copy is not being made -- you have to call copy yourself.

Related

Obj-C: Clone object with a mutated property

I would like to a create convenience initiator by cloning an existing object with a mutation, while keeping the original object intact.
For example:
Given a Person object person1 with a name (Tom) and age (10)
I would like to clone the person1 object, but with 0 age.
I have following code in Obj-C, but not sure if there's a better way to do it:
Person.h
#interface Person : NSObject
#property (nonatomic, readonly) NSString *name;
#property (nonatomic, readonly) NSUInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;
- (instancetype)cloneWithZeroAge;
#end
Person.m
#implementation Person
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age
{
if (self = [super init]) {
_name = name;
_age = age;
}
return self;
}
- (instancetype)cloneWithZeroAge
{
if (self) {
// mutate age to 0
return [self initWithName:_name age:0];
}
return self;
}
#end
Let's start with the constructor. For you specific scenario it doesn't make much difference, but in order to be functionally independent, you better ensure that the name gets a copy of the data passed. You also want to specify this part as memory storage modifier of the property, so the contract is apparent to the client code:
#interface Person : NSObject
#property (copy, nonatomic, readonly) NSString *name;
...
#end
#implementation Person
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age
{
if (self = [super init]) {
_name = [name copy];
_age = age;
}
return self;
}
Now for the actual "copy" method. In order to be more consistent with existing NSCopying protocol and Objective-C naming convention, and, which is more important, the memory management ownership convention, you should start the method name with the word "copy", so the calling side knows it's responsible for releasing the object. The most important part now, is that if you want to keep the original object untouched, you have to allocate and create a new object. In your implementation, however, you just change the self into the new object entirely. Here is how I would implement such a method:
- (instancetype)copyWithZeroAge {
Person *copy = [[Person alloc] initWithName:_name age:0];
return copy;
}
If you prefer to keep the name property memory modifier strong instead of copy, don't forget to copy the instance:
- (instancetype)copyWithZeroAge {
Person *copy = [[Person alloc] initWithName:[_name copy]
age:0];
return copy;
}

Passing NSLocalizedString as a parameter

I have a class MyOperation that has a property localizedTitle that should return a localized title but the title is being set from outside:
#interface MyOperation
- (instancetype)initWithTitle:(NSString*)title;
#property (readOnly) NSString *localizedTitle;
#end
#interface PTImageOperationSession() {
NSString* _operationTitle;
}
#implementation MyOperation
- (instancetype)initWithTitle:(NSString*)title
{
[self retainSelf];
self = [super init];
if (self)
{
_operationTitle = title;
}
return self;
}
- (NSString *)localizedTitle {
return _operationTitle;
}
#end
- (void)foo {
MyOperation *operation = [[MyOperation alloc] initWithTitle:NSLocalizedString(#"MyLocalizedTitle", nil)]; // Good.
NSSting *titleString = [self titleString]; // Can be different strings at runtime.
MyOperation *operation1 = [[MyOperation alloc] initWithTitle: titleString]; // Bad because the title is not localized.
}
Question: Is it possible to insure that the class will always be initialised with localised title? My responsibility as a class owner is to return a localized title, but it is being set from outside so I have no control on it. Would you tag it as a "design issue"?
Passing variable to NSLocalizedString macro shouldn't be any problem, here is what I did for testing purpose:
Localizable.strings
"MyString"="My sample localized string";
And my TestClass
#interface TestClass : NSObject
#property (readonly) NSString *localizedTitle;
-(instancetype)initWithTitle:(NSString *)title;
#end
#implementation TestClass {
NSString *_title;
}
-(instancetype)initWithTitle:(NSString *)title {
self = [super init];
if (self) {
_title = NSLocalizedString(title, nil);
}
return self;
}
-(NSString *)localizedTitle {
return _title;
}
#end
Init my test class, then access its property
TestClass *myClass = [[TestClass alloc] initWithTitle:#"MyString"];
NSLog(#"%#", myClass.localizedTitle);
This is what I have in console:
2018-11-05 19:16:36.268393 TestApp[1471:352553] My sample localized string
Update
The macro:
#define NSLocalizedString(key, comment) \
[NSBundle.mainBundle localizedStringForKey:(key) value:#"" table:nil]
Is the shortcut to localizedStringForKey:value:table: function & according to the document:
Return Value
A localized version of the string designated by key in
table tableName. This method returns the following when key is nil or
not found in table:
If key is nil and value is nil, returns an empty string.
If key is nil and value is non-nil, returns value.
If key is not found and value is nil or an empty string, returns key.
If key is not found and value is non-nil and not empty, return value.
If you pass a non-existence key to the macro, you will receive the key itself. That's the designated behavior.
By the way, in common sense, you can't ask for a localized string if it isn't defined, right?

Why is NSString property not seeing changes to an NSMutableString assigned to it?

I have this class:
#interface XYZPerson : NSObject{
NSString *_myNonPropertyInstanceVariable;
}
#property NSString *firstName;
#property NSString *lastName;
#property NSDate *dateOfBirth;
#property (readonly) NSString* fullName;
#property (nonatomic)NSString* experimental;
-(void)sayHello;
- (void)saySomething:(NSString *)greeting;
+(id)person;
- (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName
dateOfBirth:(NSDate *)aDOB;
#end
Implementation:
#import "XYZPerson.h"
#implementation XYZPerson {
NSString *_myNonPropertyInstanceVariable2;
}
#synthesize experimental = _experimental;
-(id)init{
self = [self initWithFirst:#"John" andLastName:#"Doe"];
return self;
}
-(id) initWithFirst:(NSString*)first andLastName:(NSString*)last{
return [self initWithFirstName:first lastName:last dateOfBirth:nil];
}
- (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName
dateOfBirth:(NSDate *)aDOB{
self = [super init];
if(self){
_firstName = aFirstName;
_lastName = aLastName;
_dateOfBirth = aDOB;
}
return self;
}
-(void)sayHello{
NSString* greet = [NSString stringWithFormat:#"Hello, World! %#",self.fullName ];
[self saySomething:greet];
}
-(void)saySomething:(NSString *)greeting {
NSLog(#"%#", greeting);
}
+(id)person{
return [[self alloc]init];
}
-(NSString*)fullName{
return [NSString stringWithFormat:#"%# %#",_firstName,_lastName];
}
-(void) setExperimental:(NSString *)anExperimental{
_experimental = anExperimental;
}
-(NSString*) experimental{
return #"Balls to you";
}
In my main method, I do:
NSMutableString* firstName = [NSMutableString stringWithString:#"IntheBeggining"];
NSString* secondName = #"MyLastName";
XYZPerson* newP = [[XYZPerson alloc]initWithFirstName:firstName lastName:secondName dateOfBirth:nil];
firstName = [NSMutableString stringWithFormat:#"Awesome"];
[newP sayHello];
Now I expected that since firstName is a mutable string, when I change it to "Awesome", it should be reflected in the sayHello invocation... but instead the output is:
Hello, World! IntheBeggining MyLastName
It should have said "Hello,World! Awesome MyLastName" ..Why didn't the new value of the NSMutableString* be reflected in the output?
You doesn't change the content of the mutable string of the first line in your last snippet. You just change the reference firstName is pointing to. To change the content use setString::
[firstName setString:#"Awesome"];
This will change the first name of the person, too.
BTW: it is not a good design if the content of properties can be changed from outside it's maintaining class. It's better to use copied properties, which will copy the content of mutable strings to immutable strings on assignments:
#property (copy) NSString *firstName;
You can change the first name of a XYZPerson by invoking its setter:
newP.firstName = #"Awesome";
This has the advantage that the maintaining can keep control over the value. Suppose if you want store the full name in a new (hidden) property. If code outside of the class are changing the value of firstName by setString:, the class has no chance to observe this change and update the full name. If you forbid the assignment of mutable string, you just need to overwrite the setter to keep the full name up-to-date.

Objective-c readonly copy properties and ivars

I'm try to grok properties declared as both copy and readonly in objective-c, and specifically, whether I have to do the copy myself. In my init methods. Evidence suggests I do:
#interface A : NSObject
#property(nonatomic, copy, readonly) NSData *test;
- (instancetype)initWithData:(NSData *)data;
#end
#implementation A
- (instancetype)initWithData:(NSData *)data {
if ((self = [super init]) != nil) {
_test = data;
}
return self;
}
#end
int main (void) {
NSData *d1 = [NSMutableData dataWithBytes:"1234" length:5];
A *a = [[A alloc] initWithData:d1];
NSLog(#"%lx", (unsigned long)d1);
NSLog(#"%lx", (unsigned long)a.test);
return 0;
}
I had thought I could do self.test = data in my init method, but that is not permitted because it's readonly (not unexpectedly). Of course, self.test = [data copy] ensures two different objects.
So: Is there a way to create a readonly property in objective-c that copies the incoming value, or is it sufficiently an edge case that the combination is pointless and I have to do any copying myself manually anyway?
A #property declaration is merely shorthand for some accessor/mutator method declarations, and (in some cases) synthesized implementations for said accessor/mutator methods.
In your case, the #property(nonatomic, copy, readonly) NSData *test declaration expands to this equivalent code:
#interface A : NSObject
{
NSData* _test;
}
- (NSData*)test;
#end
#implementation A
- (NSData*)test
{
return _test;
}
#end
There is no setTest: mutator method because the property is declared as readonly, so the copy attribute has no effect.
You can implement your own mutator method:
- (void)setTest:(NSData*)newValue
{
_test = [newValue copy];
}
Or, you can have the compiler synthesize a mutator method for you by declaring a read/write property in a private class extension in your implementation file:
// A.m:
#interface A()
#property (nonatomic, copy) NSData* test;
#end
Both cases would allow you to use the test mutator method to copy a value to the _test instance variable:
- (instancetype)initWithData:(NSData *)data {
if ((self = [super init]) != nil) {
self.test = data;
}
return self;
}
The end result is:
#interface A : NSObject
#property(nonatomic, copy, readonly) NSData* test;
- (instancetype)initWithData:(NSData*)data;
#end
#interface A()
#property (nonatomic, copy) NSData* test;
#end
#implementation A
- (instancetype)initWithData:(NSData*)data {
if ((self = [super init]) != nil) {
self.test = data;
}
return self;
}
#end
In addition to what Darren said, the copy attribute describes what semantics the properties setter has. In your initializer, you're not using the setter, you're directly assigning to the instance variable.
It's maybe a bit hard to grok, but the instance variable is not the same thing as the property. It is used to implement the property in this case. But, assigning to the instance variable is not the same as setting the property.
If you want your initializer to also have the semantics that it copies the passed-in data, that's a separate design decision (although a good idea to go with the property's semantics). You could implement that by using a private setter as Darren suggests, but you could also just do:
_test = [data copy];
in the initializer.

Unable to update singleton properties

I've encountered a stupid problem, and I've tried almost everything (bought 3 books, went through the whole google :)) but nothing helped. And it seems to me like the solution should be extremely simple...
I need to declare a singleton in Objective-C (for an iOS app, if that matters), and it should have some properties that I need to update from other classes. But I can't do that - the properties just won't update, they have the same values set in the "init" method.
I've created a simple app to test out this problem. That's what I've done:
First, I've declared a sample class and its subclass that I'm going to use as a singleton's property:
#interface Entity : NSObject
#property (nonatomic, strong, readwrite) NSMutableString * name;
#end
#implementation Entity
#synthesize name;
#end
#interface Company : Entity
#property (nonatomic, strong, readwrite) NSMutableString * boss;
#property (nonatomic) int rating;
#end
#implementation Company
#synthesize boss, rating;
#end
Then I declare the singleton itself based on the method described in the "iOS Programming Guide by Big Nerd Ranch" book. I'm using both my custom class and a standard NSMutableString as properties, just for clarity's sake:
#class Company;
#interface CompanyStore : NSObject
{
NSMutableString * someName;
}
#property (nonatomic, strong, readwrite) Company * someCompany;
#property (nonatomic, strong, readwrite) NSMutableString * someName;
+ (CompanyStore *) store;
- (void) modifyCompanyProperties;
#end
#implementation CompanyStore
#synthesize someCompany, someName;
// Declaring the shared instance
+ (CompanyStore *) store
{
static CompanyStore * storeVar = nil;
if (!storeVar) storeVar = [[super allocWithZone:nil] init];
return storeVar;
}
// Replacing the standard allocWithZone method
+ (id) allocWithZone:(NSZone *)zone
{
return [self store];
}
Then I initialize all the properties with initial values:
- (id) init
{
self = [super init];
if (self) {
someCompany = [[Company alloc] init];
[someCompany setBoss:[NSMutableString stringWithFormat:#"John Smith"]];
[someCompany setName:[NSMutableString stringWithFormat:#"Megasoft"]];
[someCompany setRating:50];
someName = [[NSMutableString alloc] initWithString:#"Bobby"];
}
return self;
}
And from another class (view controller that displays the contents in a view):
1. I get the values of the singleton's properties. Everything's okay - I get "John Smith", "Megasoft", "Bobby" and 50 for my int value. The values from my init method.
2. I change the singleton's properties from that view controller (using several ways - I'm not sure now which one is right):
- (IBAction)modify2Button:(id)sender {
CompanyStore * cst = [CompanyStore store];
NSMutableString * name = [[NSMutableString alloc] initWithString:#"Microcompany"];
NSMutableString * boss = [[NSMutableString alloc] initWithString:#"Larry"];
[[[CompanyStore store] someCompany] setName:name];
cst.someCompany.boss = boss;
NSMutableString * strng = [[NSMutableString alloc] initWithString:#"Johnny"];
[cst setSomeName:strng];
}
... and then I'm trying to get the values again. I'm still getting the old set - "John Smith", "Megasoft" etc. even though when I set a breakpoint at one of the strings, I can see that singleton's name property is "Microcompany" and not "Megasoft" at the time of the break... But it doesn't seem to be assigned.
3. Then I'm trying another thing - I'm calling from the view controller a singleton's private method, which assigns another set of values to the properties. This is that method in the singleton:
- (void) modifyCompanyProperties
{
NSMutableString * boss = [[NSMutableString alloc] initWithString:#"George"];
NSMutableString * name = [[NSMutableString alloc] initWithString:#"Georgeland"];
[someCompany setBoss:boss];
[someCompany setName:name];
[someCompany setRating:100000];
[someName setString:#"Nicholas"];
}
4. I'm trying to get the updated property values from the view controller again... and still get those "John Smith", "Megasoft"... Nothing changes.
It seems like the properties of the singleton are set only once and then I can't change them, even though their attributes are declared as "readwrite".
It looks like I don't understand something simple.
If someone could explain how to correctly declare and update properties in singletons, I would be very grateful.
First thing I noticed was that you are declaring "storeVar" in the body of the store method. And this looks like terribly wrong to me because every time you call this you'll re-initialize the singleton. You should declare the variable like this:
static CompanyStore * storeVar = nil;
#implementation CompanyStore
#synthesize someCompany, someName;
// Declaring the shared instance
+ (CompanyStore *) store
{
if (!storeVar) storeVar = [[super allocWithZone:nil] init];
return storeVar;
}
Also your init method is not exactly complete because you don't want to call init again after the singleton has been initialized so you have to check this and if it has been initialized you should simply return it:
- (id) init
{
if (storeVar!=nil) {
return storeVar;
}
self = [super init];
if (self) {
someCompany = [[Company alloc] init];
[someCompany setBoss:[NSMutableString stringWithFormat:#"John Smith"]];
[someCompany setName:[NSMutableString stringWithFormat:#"Megasoft"]];
[someCompany setRating:50];
someName = [[NSMutableString alloc] initWithString:#"Bobby"];
}
return self;
}
Also, this is not a mistake, just a mere suggestion - you can ditch #synthesize because since ios 6 because the compiler generates it automatically. But again, not a mistake to use it. Hope it helps