Using -mutableCopyWithZone: on custom class makes it immutable - objective-c

I've created a custom class which conforms to NSCopying and NSMutableCopying.
I've added an implementation for -copyWithZone: and -mutableCopyWithZone:, but whenever I call -mutableCopy on my object and try to call another method, it crashes because some of the ivars have become immutable, even though I call -mutableCopyWithZone: on the ivars.
Here's how I'm copying my class:
MyObject *flipped = [list mutableCopy];
[MyObject flip:flipped];
(the code fails on +flip:, because it tries to use removeObjectAtIndex: and addObject: on a NSMutableArray ivar)
Here's how I'm copying the class:
- (id)mutableCopyWithZone:(NSZone *)zone {
id instance = nil;
if ((instance = [[[self class] alloc] init])) {
[instance setArray:[self.array mutableCopyWithZone:zone]];
[instance setObjects:[self.objects mutableCopyWithZone:zone]];
[instance setItemCount:self.itemCount];
}
return instance;
}
I'm not sure why it's failing, but I really don't understand why it isn't making array and objects mutable.
Any help appreciated.

My last idea: if the setArray: and setObjects: methods are actually setters for properties declared as #property (copy), then they'll copy the arrays passed in - and copy always returns an immutable object. In this case, the easy way to fix this would be declaring them as (retain) instead of (copy).

Related

Is returning [self retain] in copyWithZone for immutable classes with mutable subclasses really safe / a good idea?

One often reads, that immutable classes can implement copyWithZone very efficiently in the following way:
- (id) copyWithZone:(NSZone*)zone
{
return [self retain];
}
The idea behind that implementation is obvious:
The original and the copy are both immutable instances and they will always have exactly the same content, so why not let both point to the same storage by retaining the original and avoid the overhead of copying.
However, what will happen if there is a mutable subclass?
With a clean architecture, where a subclass does not have to care about implementation details of its base class, the mutable subclass should be fine to implement copyWithZone in this way:
- (id) copyWithZone:(NSZone*)zone
{
MyClass* myCopy = [super copyWithZone:zone];
myCopy->myMember = [myMember copyWithZone:zone];
return myCopy;
}
But what does this mean with the above superclass implementation of copyWithZone?
The subclass is mutable, so although the copy still is immutable, the original now is mutable, but the subclass copyWithZone thanks to the superclass implementation operates on a retained instance of itself: self and myCopy both point the the same instance, so if I later change the value of mutableOriginal.myMember, then that will also change immutableCopy.myMember, which is just plain wrong.
So shouldn't immutable classes better implement copyWithZone in the following way?
- (id) copyWithZone:(NSZone*)zone
{
if([[self class] isMemberOfClass:[MyBaseClass class]])
return [self retain];
else
{
MyBaseClass* myCopy = [[self alloc] init];
myCopy->myBaseMember = [myBaseMember copyWithZone:zone];
return myCopy;
}
}
Your best option would be to have an initWithMyImmutableObject initialiser in your immutable superclass. Your subclass can then just implement NSCopying with
- (id) copyWithZone:(NSZone*)zone {
return [[[self superclass] alloc] initWithMyImmutableObject:self]
}
That way the actual copying of properties is done in a method of your superclass, which has access to all private members that need to be copied.

Find the Selector of a Class method

I'm quite a newbie in Objective C, though I have some background in Java reflection.
Here, I have a classic class method findAll that find all the domain objects from the database. The class Univers directly inherits from DomainObject
#interface DomainObject : NSObject
- (NSString *) execute : (NSString*) method withJson:(NSString*)json;
+ (NSString*)findAll: (NSString*)json;
#end
#implementation DomainObject
- (NSString *) execute: (NSString*) method withJson:(NSString*)json{
method = [NSString stringWithFormat:#"%#%#", method, #":"];
//method is 'findAll:'
NSString* result = [ self performSelector:
NSSelectorFromString(method) withObject:json];// Error here
return result;
}
#end
The code was working when findAll was NOT a class method (ie -findAll declaration), but now I have the error : NSInvalidArgumentException -[Univers findAll:]
It clearly seems that the runtime is looking for an instance method.
Any idea to find my class method ?
Instead of calling
NSString* result = [self performSelector:NSSelectorFromString(method) withObject:json];
you need to call
NSString* result = [[self class] performSelector:NSSelectorFromString(method) withObject:json];
for class methods.
After all it's the object instance's class that supposed to be calling the method, not the instance itself.
Short explanation: NSObject implements - (Class)class; (not to be mistaken with + (Class)class of similar effect, which NSObject implements, too!) which returns the Class object of your instance object. Keep in mind that in Objective-C in addition to plain instance objects, Classes are actual objects, too: objects of type Class, that is (vs. id, NSObject, …).
See the documentation for the -class method here.
Btw, you should probably wrap your method call into an conditional block to prevent exceptions caused by calls to missing methods.
SEL selector = NSSelectorFromString(method);
if ([[self class] respondsToSelector:selector]) {
NSString* result = [[self class] performSelector:selector withObject:json];
}
In general it's a common pattern in Objective-C to call an object's class method by receiving the class object via [object class].
Consider this case of a class called Foo implementing a convenience method for returning an autporeleased instance of itself (to be called via: Foo *newFoo = [Foo foo];):
While it would certainly be possible to implement said method like this (after all we know the object's class name, right?):
+ (id)foo {
return [[[Foo alloc] init] autorelease];
}
the correct way is this:
+ (id)foo {
return [[[self alloc] init] autorelease];
}
As the first one would cause problems with polymorphism in subclasses (Such as a subclass called FooBar, for which it should clearly be [FooBar alloc] …, not [Foo alloc] …. Luckily [[self class] alloc] solves this dynamically).
While this is clearly not the right place for a thorough explanation of this (rather offtopic one might say) it's certainly worth noting/warning about, imho.

Best practices for initialising Objective-C properties

I understand that this may not necessarily apply to just #properties, but they would be the most common use case. If there is, for example:
#property (strong) NSObject *object;
...
#synthesize object = _object;
It is possible to initialise it in the init method of the class it is declared in like so:
- (id)init {
self = [super init];
if (self) {
_object = [[NSObject alloc] init];
}
}
or override the getter and initialise it upon first use:
- (NSObject *)object {
if (!_object) {
_object = [[NSObject alloc] init];
}
return _object;
}
Which of these is it better to use? Does this depend on the use scenario (e.g. does the object the property is declared in have multiple initialisers, or the type of the property, how it's used, etc.)?
The real advantage I see in overriding the getter is that the property will only be allocated when it is needed, but a disadvantage would be that the first access would be slower.
On a side note, when accessing properties in the init method, is it better to access them as self.object or _object?
Contrary to the accepted answer, Advanced Memory Management Programming Guide says you should use instance variables in the initializers and in the dealloc method. See 'Don’t Use Accessor Methods in Initializer Methods and dealloc'.
personally i find initializing in the init method is better, the life expectancy of the object is then more clear and also consider if the init fails for the object, isn't it better to get that at the init than when you do a get?
i also prefer to use self.object for properties because it uses the getter and setter of the object and the "self." makes it clear and to avoid situations where a retain is needed or not. sure in some cases like in your example it may cause a couple more lines of code but i rely on the compiler to optimize the code.
e.g.
yourobjclass* tmp = [[yourobjclass alloc] init];
self.object = tmp;
[tmp release];

How does adding a variable as a property affect it?

What difference does it make in memory management to define a variable as a property? For instance:
#interface foo {
NSString *theStr;
}
#end
#implementation foo
- (void)bar {
NSLog(theStr);
}
#end
Versus:
#interface foo {
NSString *theStr;
}
#property(retain) NSString *theStr;
#end
#implementation foo
#synthesize theStr;
- (void)bar {
NSLog(theStr);
}
#end
It seems like the first is autoreleased or something similar, while the second is retained throughout the life of the class. Is that the case, or what is the difference?
If you define a variable just in the interface without defining it as a property (as in your first example) means that you'll have to take care of everything related to memory management yourself. Assigning something to that variable will not retain it automatically, not will setting the variable to something else release the previous value.
Defining it as a property creates getter and setter methods under the hood. Most importantly, if you use it with the "retain" keyword, your setter method will retain the new value (and release the old one if there was one).
Note that the setter method will only be invoked if you use the dot notation, e.g., self.myStr = #"new string", or the method call, e.g., [self setMyStr:#"new string"]. If you just call myStr = #"new string" the setter method will not be called and you need to release the old value yourself and retain the new one.
I don't think the first case shows an autoreleased object, it would all depend on how you managed the creation and the destruction of that particular object. If for instance when you create that object you call:
//This string will indeed be autoreleased
theStr=[NSString stringWithString:#"Jibber jabber"];
//Or even
theStr=#"Jibber jabber";
But you have to take charge of the memory management if you create it in the following way:
//Manage my memory
theStr=[[NSString alloc] init];
//You have to release this property on the dealloc method
-(void)dealloc{
[theStr release];
[super dealloc];
}
On your second example, you create a setter and a getter method for the property theStr and by adding the nonatomic attribute, you make your property not thread safety, meaning that a thread can begin to modify your property while another one is already editing it. And by setting the retain attribute to your property, the setter method will be synthesized the following way:
- (void) setTheStr:(NSString *) newString {
[newString retain];
[theStr release];
theStr = newSupervisor;
}
You can consult more about this in one of my favorite books, Learning Objective-C 2.0 in chapter 12.

How do I extend an NSArray?

Here's my try:
H file:
#interface Strings : NSArray
#end
M file:
#implementation Strings
- (id) init
{
[self initWithObjects:
#"One.",
nil];
return self;
}
#end
When I run I get this:
'NSInvalidArgumentException', reason: '* -[NSArray initWithObjects:count:]: method only defined for abstract class. Define -[Strings initWithObjects:count:]!'
This is what I did instead:
H file:
#interface Strings : NSObject
+ (NSArray*) getStrings;
#end
M file:
#implementation Strings
+ (NSArray*) getStrings
{
NSArray* strings = [[NSArray alloc] initWithObjects:
#"One.",
nil];
return strings;
}
#end
NSArray is a class cluster (link to Apple's documentation). This means that when you try to create an NSArray, the system creates some private subclass of NSArray. The NSArray class just defines an interface; subclasses of NSArray provide implementations of the interface.
You can write your own subclass of NSArray, but you have to provide your own storage for the objects in the array. You have to initialize that storage yourself. The error message is telling you this, by saying that you need to override initWithObjects:count: in your subclass. Your override needs to put the objects into whatever storage you allocate as part of your class implementation.
The NSArray implementation of the variadic initWithObjects: method is just a wrapper around initWithObjects:count:, so you don't have to implement initWithObjects:.
Deriving from NSArray is something you should avoid. From the documentation:
Remember that NSArray is the public interface for a class cluster and what this entails for your subclass. The primitive methods of NSArray do not include any designated initializers. This means that you must provide the storage for your subclass and implement the primitive methods that directly act on that storage.
What this means is that when you initialize an array, you don't get an instance of NSArray. You'll get an instance of a totally different class that merely has the same interface. That is why subclassing doesn't work the way you think it works: you'll have to completely implement the storage yourself. This is why the documentation further states:
Any subclass of NSArray must override the primitive instance methods count and objectAtIndex:. These methods must operate on the backing store that you provide for the elements of the collection. For this backing store you can use a static array, a standard NSArray object, or some other data type or mechanism. You may also choose to override, partially or fully, any other NSArray method for which you want to provide an alternative implementation.
Last but not least you would have had the initializing wrong anyway. You would have needed to call super:
- (id)init
{
self = [super initWithObjects:#"One", #"Two", nil];
if (!self) return nil;
return self;
}
But as I just said, it just doesn't work that easily. You'll get the same exception again. So you should simply avoid doing deriving from NSArray.
What you can do is add a category to add methods to all NSArray instances.
NSArray doesn't support being subclassed in this way. You can add a category, though, although that's not universally recommended.
See Objective C - Subclassing NSArray for more thoughts.
perhaps
self = [super initWithObjects:...];
You need to assign self, and call your superclass' init method.
if (self = [super initWithObjects:...]) {
...
}
return self;