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?

Wienke
  • 3,723
  • 27
  • 40
  • 1
    Maybe this will help: http://robnapier.net/blog/implementing-nscopying-439 – CodaFi Mar 18 '12 at 17:09
  • 1
    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. – Wienke Mar 18 '12 at 17:59
  • Your question is excellent. +1 – CodaFi Mar 18 '12 at 18:28

2 Answers2

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.

Kurt Revis
  • 27,695
  • 5
  • 68
  • 74
  • 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. – Wienke Mar 18 '12 at 20:52
  • 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. – Kurt Revis Mar 18 '12 at 21:09
  • 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 ``, they left it out. – Kurt Revis Mar 18 '12 at 21:18
  • 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. – Wienke Mar 18 '12 at 21:32
  • 3
    BTW, iOS6 added the NSCopying protocol to UIColor. I'm assuming it returns the same object, like an immutable NSString would. – Trashpanda Oct 26 '12 at 17:31
  • 1
    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. – big_m May 14 '14 at 16:29
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.

Nick Ager
  • 1,227
  • 14
  • 15
  • 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. – Wienke May 16 '13 at 20:29