Why don’t iOS classes adopt copyWithZone protocol to encourage active mem mgt?
Asked Answered
D

2

5

Recently turning to iOS after having worked with Cocoa, I was startled to get a SIGABRT with the following error: “-[UIDeviceRGBColor copyWithZone:]: unrecognized selector sent to instance…” I had called “copy” on a UIColor.

I looked at the class references and, zounds, UIColor does not adopt any protocols, in contrast to NSColor.

Now, this is not a big deal. I was just attempting to be more efficient by taking active ownership of a color instance so as to discard it immediately after use. But I thought the purpose behind Apple’s omitting a garbage collector in iOS was to encourage developers to do exactly what I was doing, to keep a lean memory profile on the memory-starved, battery-challenged portable devices.

Any ideas on Apple’s rationale, or is there some error in my assumptions?

Darksome answered 18/3, 2012 at 16:9 Comment(3)
Maybe this will help: robnapier.net/blog/implementing-nscopying-439Sinistrorse
Thanks for the heads up. Although your blog entry doesn't answer this particular question, it anticipates the much more serious questions I would have had if I decided to write deep-copy overrides of classes that do implement copying protocols. Excellent explanation.Darksome
Your question is excellent. +1Sinistrorse
T
5

I don't understand why you think implementing the NSCopying protocol would "encourage active memory management".

Since UIColor is immutable (it implements no methods that change its internal state), there is no point making a copy. Just retain it if you want to keep it around, and release it when you're done. There is no need for anything else.

If you really wanted, you could add copying in a category:

@implementation UIColor (Copying) <NSCopying>

- (id)copyWithZone:(NSZone *)zone
{
    return [self retain];
}

@end

But obviously that doesn't actually give you any new functionality. Apparently Apple didn't think it was worth the time when they implemented that class.

Topple answered 18/3, 2012 at 19:56 Comment(6)
Thanks for the suggestion; in this particular instance, I can indeed accomplish the same thing by balancing a retain with a release. But then why does NSColor implement copyingWithZone:? NSColor is also immutable. Perhaps a better class with which to raise my question is UIImage. UIImage is mutable six ways to Sunday, but does not conform to NSCopying — while its Cocoa counterpart, NSImage, does. So your thought-provoking response adds a new question, and revises the original one.Darksome
AppKit and UIKit, and NSColor and UIColor, were written by different teams, at least a decade apart. They aren't perfectly consistent -- and in most cases the UIKit versions are smaller and cleaner. (NSImage, in particular, was a complete disaster.) Also, check UIImage again -- I don't see any mutating methods.Topple
In general the UIKit design goal was "AppKit, but without all the junk, so we can fit it on a tiny underpowered phone with very little memory -- and with as little API as we can get away with providing, since whatever we provide now, we'll have to support it forever". Since there was no point in making UIColor and UIImage support <NSCopying>, they left it out.Topple
You're right. All the UIImage properties I was assuming had setters (size, scale, images, etc., etc.) are read-only. OK, so the inconsistency is by evolution, not design. And I guess memory management can always hang its hat on retain-release. Mysteries solved.Darksome
BTW, iOS6 added the NSCopying protocol to UIColor. I'm assuming it returns the same object, like an immutable NSString would.Allison
UIColor is immutable, but a subclass might not be. For the same reason you copy an NSString or NSArray, if you retain a UIColor that is passed to you, it really should be a copy. Within an app, you may know that you never subclass UIColor, but libraries shouldn't make such assumptions. Secondly, implementing NSCopying let's you use UIColor as a dictionary key. I have many immutable value classes that support NSCopying for this reason alone. So I, for one, am glad to see that it has been added in iOS 6.Fast
P
3

My app needs to work on both iOS5 (UIColor>>#copyWithZone doesn't exist) and iOS6+ (UIColor>>#copyWithZone exists) so I came up with the following:

@implementation UIColor(ios5CopyWithZone)

+ (void)initialize
{
    // iOS5 dosn't include UIColor>>#copyWithZone so add it with class_addMethod.
    // For iOS6+ class_addMethod fails as UIColor>>#copyWithZone already exists. 
    Class klass = [UIColor class];
    Method methodToInstall = class_getInstanceMethod(klass, @selector(ios5CopyWithZone:));
    class_addMethod(klass, @selector(copyWithZone:), method_getImplementation(methodToInstall), method_getTypeEncoding(methodToInstall));
}

// UIImage is immutable so can just return self.
// #retain to ensure we follow mem-management conventions
-(id)ios5CopyWithZone:(NSZone *)__unused zone
{
    return [self retain];
}
@end

The code attempts to adds UIColor>>#copyWithZone using the runtime's class_addMethod. I don't know if this is any better than implementing UIColor>>#copyWithZone directly in a category, however reading Apple's Avoid Category Method Name Clashes implies that it is bad practice to reimplement an existing framework method (that is UIColor>>#copyWithZone in iOS6). However I realise that +initialize could potentially trample on a framework's +initialize.

Piddling answered 14/5, 2013 at 20:10 Comment(1)
Thanks for introducing me to class_addMethod, which looks like a particularly elegant way of bridging os versions. I do think you should be cautious about overriding +initialize, especially since UIColor is a class cluster. Personally, as a non-expert, I would go messy but safe, and call a separate category method that does this work, rather than incorporate it into an init method.Darksome

© 2022 - 2024 — McMap. All rights reserved.