Is the object retained when setting a property's ivar directly in -init? - objective-c

My understanding is that instance variables should be accessed directly from inside the init method. For example:
#interface ABC : NSObject
#property (strong, nonatomic) NSString *name;
#end
#implementation ABC
- (id)init
{
if ((self = [super init]) != nil)
{
_name = #"some name";
}
}
// another init example
- (id)initWithName:(NSString*)n
{
if ((self = [super init]) != nil)
{
_name = n;
}
}
#end
I am wondering about the _name variable. In both init examples, is _name retained? For this example, I am using ARC.

Whether _name is retained in this code depends on whether you have ARC turned on. If you do, ARC will retain the object for you (since that is ARC's job). If you don't have ARC turned on, you need to retain it yourself, which would look like:
- (id)initWithName:(NSString*)n
{
if ((self = [super init]) != nil)
{
_name = [n retain];
}
}
(It's also worth pointing out that NSStrings should usually be copied rather than retained, so you would make the property #property (copy, nonatomic) NSString *name; and the assignment would be _name = [n copy].)

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;
}

How can I pass a NSString parameter to a function?

I want to initialize an object. The problem is how to pass the NSString correctly.
Object code:
#import "ClaseHoja.h"
#implementation ClaseHoja
#synthesize pares;
#synthesize nombre;
-(id)init
{
self=[super init];
if(self){
}
return self;
}
-(id)initWithValues:(NSString*)nom par:(int)par
{
if([super init]){
pares=par;
nombre=nom;
}
return self;
}
When I call the function I do this:
NSString *nombre="Hello";
int par=20;
ClaseHoja *ch = [ClaseHoja alloc] initWithValues:nombre par:numPares]];
I would suggest:
Add the missing # to #"Hello" and fix the [] in your alloc/init call.
If you're using Xcode, I'd let the compiler synthesize the properties for you. No #synthesize is needed. If you're using a stand-alone LLVM on some other platform, though, you might need it, but by convention, you'd specify an ivar with a preceding _.
I'd define nombre to be copy property and explicitly copy the nombre value passed to your init method. You don't want to risk having a NSMutableString being passed to your method and having it unwittingly mutated without your knowledge.
I'd suggest renaming the initWithValues:par: to be initWithNombre:pares:, to eliminate any doubt about what properties are being updated.
You don't need init without parameters. You can just rely on the one provided by NSObject.
You'd generally use NSInteger rather than int.
In your custom init method, you want to make sure to do if ((self = [super init])) { ... }
Thus:
// ClaseHoja.h
#import Foundation;
#interface ClaseHora: NSObject
#property (nonatomic, copy) NSString *nombre;
#property (nonatomic) NSInteger pares;
- (id)initWithNombre:(NSString*)nombre pares:(NSInteger)pares;
#end
And
// ClaseHoja.m
#import "ClaseHoja.h"
#implementation ClaseHoja
// If you're using modern Objective-C compiler (such as included with Xcode),
// you don't need these lines, but if you're using, for example stand-alone
// LLVM in Windows, you might have to uncomment the following lines:
//
// #synthesize nombre = _nombre;
// #synthesize pares = _pares;
- (id)initWithNombre:(NSString*)nombre pares:(NSInteger)pares {
if ((self = [super init])) {
_pares = pares;
_nombre = [nombre copy];
}
return self;
}
#end
And you'd use it like so:
NSString *nombre = #"Hello";
NSInteger pares = 20;
ClaseHoja *ch = [[ClaseHoja alloc] initWithNombre:nombre pares:pares];
You need to pass like this. Another thing you miss # sign before the string.
NSString *nombre = #"Hello"; int par=20;
ClaseHoja *ch = [[ClaseHoja alloc]initWithValues:nombre par:par];

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.

Set property on an allocated UIViewController

I created a simple UIViewController with a custom init method like this:
-(id)initWithGroupNumber:(NSString *)groupNumber {
if ((self = [super init]) == nil) {
return nil;
}
self.levelGroup = groupNumber;
return self; }
levelGroup is a property written in the .h file
#property (nonatomic, retain) NSString *levelGroup;
When I call the method above this way:
LevelsViewController *lvc = [[LevelsViewController alloc]initWithGroupNumber:#"5"];
the controller is allocated but all the property inside are set to nil. I can't understand why.
First of all when you deal with classes that have subclass of type mutable (e.g. NSMutableString), use copy.
So, your property should become:
#property (nonatomic, copy) NSString *levelGroup;
Then, inside the UIViewController synthesize the property
#synthesize levelGroup;
and in init do the following:
-(id)initWithGroupNumber:(NSString *)groupNumber {
if (self = [super init]) {
levelGroup = [groupNumber copy];
}
return self;
}
As written in the memory management guide you should not use self. inside init and in dealloc.
Use your property self.levelGroup to get or set the value.
Remember to release in dealloc:
[levelGroup release];
Hope it helps.
Replace your
self.levelGroup = [[NSString alloc]init];
with
self.levelGroup = groupNumber; // actually uses your init value.

Is that the correct way to deal with ivars?

I have read a lot of topics about getters and setters. I know what they are and why are useful. Different source claim's different ways to release the ivars. Here begins my confusion
#interface CoolClass : NSObject
{
NSString *name;
}
#property (nonatomic, copy) NSString *name;
#end
#implementation CoolClass
#synthesize name = _name;
-(id)init
{
if(super = [self super])
{
self.name = #"Jo";
}
return self;
}
-(void)dealloc
{
[self.name release], self.name = nil;
}
#end
Is that the correct way to release/free ivars ?
You'll want to use accessors most of the time, but not in partially constructed states because they can have negative side-effects. Here's how it's done:
- (id)init
{
if((self = [super init])) {
// self.name = #"Jo"; << don't use accessors in initializer
_name = [#"Jo" copy]; << good
}
return self;
}
// added for another variation:
- (id)initWithName:(NSString *)inName
{
if((self = [super init])) {
_name = [inName copy];
}
return self;
}
- (void)dealloc
{
// don't use accessors in dealloc
// don't release the result of a getter (release the result of the retained or copied result)
// [self.name release], self.name = nil;
// instead:
[_name release], _name = nil;
[super dealloc]; << compiler should have warned you about this one
}
Note: In the case of init, the string literal is an immortal and it won't matter if you copy it because the copy just returns itself. My preference is to just 'copy' the immortal for clarity, although it's unnecessary.
Here is what I would advise:
#interface CoolClass : NSObject
#property (nonatomic, copy) NSString *name;
#end
#implementation CoolClass
#synthesize name = _name;
-(id)init
{
if(super = [self super])
{
self.name = #"Jo";
}
return self;
}
-(void)dealloc
{
[_name release];
[super dealloc];
}
#end
Notes:
There is no need to explicitly declare ivars inside { ... } in your header. They will be created automatically when you synthesise your property. Explicit ivars are a legacy concept that are no longer needed since about iOS 3.
You should not use self.name in the dealloc as this calls the getter method, which may do additional work beyond merely fetching the ivar. Normally it's good practice to use the getter method, but in the dealloc you should release the ivar directly
It is good practice to set ivars to nil after releasing them, but again in the dealloc this in not necessary because no code is ever executed after dealloc, so the pointer won't be referenced again.
Normally (outside of the dealloc), if you wish to release an ivar you should set it to nil using the setter like this: self.name = nil; that will automatically release it and set it to nil. This is equivalent to [_name release], _name = nil;
#interface CoolClass : NSObject
{
NSString *name;
}
You declared here an instance variable 'name'; Nowadays there is no need to declare ivars in the header file. Just use properties and make the compiler to synthesize ivar for you.
#property (nonatomic, copy) NSString *name;
Here we have a property declaration that specifies that a copy of the object should be used for assignment and that a previous value is sent a release message.
In implementation you want to synthesize your property:
#synthesize name = _name;
This code tells the compiler to generate a getter and setter for property called 'name' and use instance variable called '_name' to store value. So you have now two ivars - 'name' and '_name'.
That how init method should like like:
-(id)init
{
if(self = [super init])
{
name = #"This is ivar declared between {}";
_name = #"synthesized ivar";
}
return self;
}
And the dealloc:
-(void)dealloc
{
[name release];
[_name release];
[super dealloc];
}