Register FAQ / Rules Forum Spy Search Today's Posts Mark Forums Read
Go Back   MacRumors Forums > Apple Systems and Services > Programming > Mac Programming

Reply
 
Thread Tools Search this Thread Display Modes
Old Jun 25, 2011, 05:53 PM   #1
MrFusion
macrumors 6502a
 
Join Date: Jun 2005
Location: West-Europe
good code practice: NSColor - UIColor - CGColorRef or cocoa - appkit - corefoundation

The dashes in the title were supposed to be "vs", but I ran out of space.

First a general question: in your expert view, what is good coding practice when sharing code between Mac OS X and iOS projects?

Sometimes I see this on websites:
#if TARGET_OS_IPHONE (or similar)
do something
#else
do something else
#endif
Is this the best approach? To me it seems like every other line would duplicate itself until it's a gigantic mess of #if statements
Also, how or where do you tell xCode when to set TARGET_OS_IPHONE to 0 or 1?


Since the mac has cocoa, iOS has appkit and both have core graphics, custom drawing can also be done with core graphics. That should promote code sharing between a mac and iOS project, or not?

The code below can be found in the documentation or on google and it seems to work just fine. It deals with converting between CGColorRef and NSColor or UIColor. By sticking the conversion into a separate file and keeping the drawing code generic by using CGColorRef, it should minimize code duplication in the drawing routines of both projects, or not?

My question is about the last line:
Code:
return (CGColorRef)[(id)CGColorCreate(colorSpace, components) autorelease];
A corefoundation object is cast to an NSObject (?) and then autoreleased. Is this valid code or good practice?
I thought corefoundation objects could not be autoreleased.

Thanks for sharing your insight!



Code:
@implementation NSUserDefaults (UserDefaultsExtensions)

-(void) setColor:(NSColor *) aColor 
		  forKey:(NSString *) aKey
{
    NSData *theData = [NSArchiver archivedDataWithRootObject:aColor];
    [self setObject:theData 
			 forKey:aKey];
}

-(NSColor *) colorForKey:(NSString *) aKey
{
    NSColor *theColor = nil;
    NSData *theData = [self dataForKey:aKey];
    if (theData != nil)
        theColor = (NSColor *)[NSUnarchiver unarchiveObjectWithData:theData];
    return theColor;
}

@end


#pragma mark -
#pragma mark -

@implementation NSColor (CGColor)

+(NSColor *) colorWithCGColor:(CGColorRef) aColorRef
{
	NSColorSpace *colorSpace = [[NSColorSpace alloc] initWithCGColorSpace:CGColorGetColorSpace(aColorRef)];	
	NSColor *color = [NSColor colorWithColorSpace:colorSpace
									   components:CGColorGetComponents(aColorRef)
											count:CGColorGetNumberOfComponents(aColorRef)];
	[colorSpace release];
	return [color autorelease];
}

-(CGColorRef) CGColor
{
    const NSInteger numberOfComponents = [self numberOfComponents];
    CGFloat components[numberOfComponents];
    CGColorSpaceRef colorSpace = [[self colorSpace] CGColorSpace];
	
    [self getComponents:(CGFloat *)&components];
	
    return (CGColorRef)[(id)CGColorCreate(colorSpace, components) autorelease];
}

@end
__________________
24" iMac, 13" MacBook, iPod Touch. iPod mini and PowerPC Mac Mini gathering dust somewhere.
MrFusion is offline   0 Reply With Quote
Old Jun 25, 2011, 06:18 PM   #2
chown33
macrumors 603
 
Join Date: Aug 2009
Quote:
Originally Posted by MrFusion View Post
Sometimes I see this on websites:
#if TARGET_OS_IPHONE (or similar)
do something
#else
do something else
#endif
Is this the best approach? To me it seems like every other line would duplicate itself until it's a gigantic mess of #if statements
If you find your code starting to do that, then it's time to refactor it.

Instead of line-by-line #if/#else, factor out a common (and presumably portable) class. Then in the implementation, you can put the #if/#else junk. All anyone else sees is a nice clean @interface.

And honestly, if the implementation of the portable class is too ugly as line-by-line #if/#else, factor that out, too. Example:
Code:
-someMethod
{
#if YOUR_CONSTANT_HERE
  blah;
  NSblah.blah;
  [blah blah:NSblahBlah];
#else
  blah;
  UIblah.fnord;
  [blurbish blah:UIblurbBlurb];
#endif
}
You can also make a private function that contains the conditional code, and the method then calls a series of those private functions to accomplish its overall task. The method stays nice and clean, and the purpose of the functions is clear because they have meaningful names (right?), but the gobbledy-gook of the specific implementation is put in one place, instead of being haphazardly interwoven like yak-hair into silk.

You can also use other tricks, such as conditional C macros. The name is common, e.g. MyPortableColor, but the expansion of the macro is target-dependent. That's more effective when the operations are simpler or the types are more primitive. It's usually not so nice when things get more complex. For an example of conditional macros that appear as if they're functions, look at NSAssert.
chown33 is offline   0 Reply With Quote
Old Jun 25, 2011, 07:00 PM   #3
jiminaus
macrumors 65816
 
Join Date: Dec 2010
Location: Sydney
Quote:
Originally Posted by MrFusion View Post
My question is about the last line:
Code:
return (CGColorRef)[(id)CGColorCreate(colorSpace, components) autorelease];
A corefoundation object is cast to an NSObject (?) and then autoreleased. Is this valid code or good practice?
I thought corefoundation objects could not be autoreleased.
Actually I don't think this is valid.

Some CoreFoundation types are toll-free bridged with their Cocoa counterparts. For example, a CFStringRef can be freely type cast to a NSString* and vice versa. So the following would be valid:
Code:
return (CFStringRef)
  [(id)CFStringCreateWithCStringNoCopy(
                kCFAllocatorDefault, 
                "Hello world",
                kCFStringEncodingASCII,
                kCFAllocatorNull) 
    autorelease];
But I can't find any documentation to say CGColorRef is toll-free bridged to any Cocoa type, so its bits are not guaranteed to be laid out compatibly with id. Although CGColorRef does "inherit" from CFTypeRef, perhaps this is a feature of CFTypeRef that I'm missing.

I also cannot find any documentation to say NSAutoreleasePool supports releasing CoreFoundation types, that is calling the CFRelease function instead of sending a release message.

Given all this I'm stumped on how to implement this with the correct object-ownership semantics. You can't add an instance variable to NSColor to hold the CGColorRef so you can release it later. I only think of implementing it as a create function so that the caller is responsible for freeing the resulting CGColorRef.

Code:
CGColorRef CGColorCreateFromNSColor(NSColor* anNSColor)
{
    const NSInteger numberOfComponents = [anNSColor numberOfComponents];
    CGFloat components[numberOfComponents];
    CGColorSpaceRef colorSpace = [[anNSColor colorSpace] CGColorSpace];
	
    [self getComponents:(CGFloat *)&components];
	
    return CGColorCreate(colorSpace, components);
}

Actually, I thought did occur to me. Wrap the pointer in a custom object like so.

Code:
@interface CGColorRefReleaser : NSObject
{
  CGColorRef* theColorRef;
}
- (id)initWithCGColorRef:(CGColorRef*)aColorRef;
@end

@implementation CGColorRefReleaser
- (id)initWithCGColorRef:(CGColorRef*)aColorRef
{
  self = [super init];
  if (self) {
    theColorRef = aColorRef;
  }
  return self;
}
- (void)dealloc
{
  CGColorRelease(theColorRef);
  [super dealloc];
}
@end

@implementation NSColor (CGColor)
-(CGColorRef) CGColor
{
    const NSInteger numberOfComponents = [self numberOfComponents];
    CGFloat components[numberOfComponents];
    CGColorSpaceRef colorSpace = [[self colorSpace] CGColorSpace];
	
    [self getComponents:(CGFloat *)&components];

    CGColorRef* colorRef = CGColorCreate(colorSpace, components);
    CGColorRefReleaser* releaser = 
      [[[CGColorRefReleaser alloc] initWithCGColorRef:colorRef] autorelease]
    return colorRef;
}
@end

Last edited by jiminaus; Jun 25, 2011 at 07:11 PM. Reason: Releaser idea
jiminaus is offline   0 Reply With Quote
Old Jun 25, 2011, 07:37 PM   #4
MrFusion
Thread Starter
macrumors 6502a
 
Join Date: Jun 2005
Location: West-Europe
Quote:
Originally Posted by jiminaus View Post
Actually I don't think this is valid.
Neither do I, but I found it several times with google. Probably everyone copying from everyone.

Quote:
But I can't find any documentation to say CGColorRef is toll-free bridged to any Cocoa type, so its bits are not guaranteed to be laid out compatibly with id. Although CGColorRef does "inherit" from CFTypeRef, perhaps this is a feature of CFTypeRef that I'm missing.

I also cannot find any documentation to say NSAutoreleasePool supports releasing CoreFoundation types, that is calling the CFRelease function instead of sending a release message.
That correspond to my (limited) understanding of corefoundation, so I agree with you.

Quote:
Actually, I thought did occur to me. Wrap the pointer in a custom object like so.

Code:
@interface CGColorRefReleaser : NSObject
{
  CGColorRef* theColorRef;
}
- (id)initWithCGColorRef:(CGColorRef*)aColorRef;
@end

@implementation CGColorRefReleaser
- (id)initWithCGColorRef:(CGColorRef*)aColorRef
{
  self = [super init];
  if (self) {
    theColorRef = aColorRef;
  }
  return self;
}
- (void)dealloc
{
  CGColorRelease(theColorRef);
  [super dealloc];
}
@end

@implementation NSColor (CGColor)
-(CGColorRef) CGColor
{
    const NSInteger numberOfComponents = [self numberOfComponents];
    CGFloat components[numberOfComponents];
    CGColorSpaceRef colorSpace = [[self colorSpace] CGColorSpace];
	
    [self getComponents:(CGFloat *)&components];

    CGColorRef* colorRef = CGColorCreate(colorSpace, components);
    CGColorRefReleaser* releaser = 
      [[[CGColorRefReleaser alloc] initWithCGColorRef:colorRef] autorelease]
    return colorRef;
}
@end
Ah, so maybe this is how UIColor can have a CGColorRef method that is autoreleased (i.e. doesn't have to be released by the user). A from view hidden autoreleased NSObject subclass. But won't this immediately release the "releaser"? Nobody is holding on to it. The CGColorRef continues to be used, but not the "releaser" instance.
__________________
24" iMac, 13" MacBook, iPod Touch. iPod mini and PowerPC Mac Mini gathering dust somewhere.
MrFusion is offline   0 Reply With Quote
Old Jun 25, 2011, 07:42 PM   #5
MrFusion
Thread Starter
macrumors 6502a
 
Join Date: Jun 2005
Location: West-Europe
Quote:
Originally Posted by chown33 View Post
If you find your code starting to do that, then it's time to refactor it.

Instead of line-by-line #if/#else, factor out a common (and presumably portable) class. Then in the implementation, you can put the #if/#else junk. All anyone else sees is a nice clean @interface.

And honestly, if the implementation of the portable class is too ugly as line-by-line #if/#else, factor that out, too. Example:
Code:
-someMethod
{
#if YOUR_CONSTANT_HERE
  blah;
  NSblah.blah;
  [blah blah:NSblahBlah];
#else
  blah;
  UIblah.fnord;
  [blurbish blah:UIblurbBlurb];
#endif
}
You can also make a private function that contains the conditional code, and the method then calls a series of those private functions to accomplish its overall task. The method stays nice and clean, and the purpose of the functions is clear because they have meaningful names (right?), but the gobbledy-gook of the specific implementation is put in one place, instead of being haphazardly interwoven like yak-hair into silk.

You can also use other tricks, such as conditional C macros. The name is common, e.g. MyPortableColor, but the expansion of the macro is target-dependent. That's more effective when the operations are simpler or the types are more primitive. It's usually not so nice when things get more complex. For an example of conditional macros that appear as if they're functions, look at NSAssert.
The more I program, the longer my comments and method names become.

I like your suggestion of hiding the nasty details in private categories. Not so much the macros. Those can be tricky and difficult to debug, so I have read.

Thanks for the insight.
__________________
24" iMac, 13" MacBook, iPod Touch. iPod mini and PowerPC Mac Mini gathering dust somewhere.
MrFusion is offline   0 Reply With Quote
Old Jun 25, 2011, 07:52 PM   #6
jiminaus
macrumors 65816
 
Join Date: Dec 2010
Location: Sydney
Quote:
Originally Posted by MrFusion View Post
But won't this immediately release the "releaser"? Nobody is holding on to it. The CGColorRef continues to be used, but not the "releaser" instance.
Yes, if you turn garbage-collection on. This technique is an anti-pattern under garbage-collection.

If garbage-collection is off, then the "releaser" won't be released until the nearest NSAutoreleasePool is drained. At that point, the "releaser" will be released, its retain count will become zero, it's dealloc to be called, and CGColorRelease will be called on the CGColorRef. This is exactly the same semantics as the original code you posted, but more technically correct.

As per usual, if you wanted to keep the CGColorRef around, you need to call CFRetain and then later call CFRelease yourself. If you find yourself always wanting to do this, then save the overhead, and use the create function I posted instead.
jiminaus is offline   0 Reply With Quote
Old Jun 25, 2011, 07:58 PM   #7
chown33
macrumors 603
 
Join Date: Aug 2009
Quote:
Originally Posted by MrFusion View Post
I like your suggestion of hiding the nasty details in private categories. Not so much the macros. Those can be tricky and difficult to debug, so I have read.
Yeah, hairy macros can be awful. I was mostly thinking of simple name substitution.

For example, if there's an NSSomething class and an UISomethingElse class, and the method and property names you use are the same, then write this:
Code:
#if TARGET_OS_IPHONE (or similar)
#define NSSomething UISomethingElse
#endif
and now you can use the NSSomething class name anywhere.

However, it may be best to signal that you're using a non-standard name:
Code:
#if TARGET_OS_IPHONE (or similar)
#define YourThing UISomethingElse
#else
#define YourThing NSSomething
#endif
chown33 is offline   0 Reply With Quote
Old Jun 25, 2011, 08:06 PM   #8
jiminaus
macrumors 65816
 
Join Date: Dec 2010
Location: Sydney
Quote:
Originally Posted by chown33 View Post
For example, if there's an NSSomething class and an UISomethingElse class, and the method and property names you use are the same, then write this:
Code:
#if TARGET_OS_IPHONE (or similar)
#define NSSomething UISomethingElse
#endif
and now you can use the NSSomething class name anywhere.
Nooo!!!

I can remember being a Novice C++ programmer and being stumped for a long time about why sometimes I would get an error about my MessageBox member function couldn't be found. It was because windows.h would #define MessageBox MessageBoxW. This was so programmers could just use MessageBox and just get the correct ANSI or Unicode version.

From that day forward #define's for anything other than conditional compilation have been by sworn enemy.
jiminaus is offline   0 Reply With Quote
Old Jun 25, 2011, 08:27 PM   #9
MrFusion
Thread Starter
macrumors 6502a
 
Join Date: Jun 2005
Location: West-Europe
Quote:
Originally Posted by jiminaus View Post
Yes, if you turn garbage-collection on. This technique is an anti-pattern under garbage-collection.

If garbage-collection is off, then the "releaser" won't be released until the nearest NSAutoreleasePool is drained. At that point, the "releaser" will be released, its retain count will become zero, it's dealloc to be called, and CGColorRelease will be called on the CGColorRef. This is exactly the same semantics as the original code you posted, but more technically correct.

As per usual, if you wanted to keep the CGColorRef around, you need to call CFRetain and then later call CFRelease yourself. If you find yourself always wanting to do this, then save the overhead, and use the create function I posted instead.
I don't use garbage collection. I prefer to keep my own tab on retain/release. But I also dislike getting a non-autoreleased object from a non-init method. I'll probably stick with the wrapper object. Although, this could maybe cause problems in lion with arc. Apparently, from the WWDC videos, retain/release is not needed anymore when using/compiling with arc. Well, I'll just have to wait for lion to try it out, I guess.
__________________
24" iMac, 13" MacBook, iPod Touch. iPod mini and PowerPC Mac Mini gathering dust somewhere.
MrFusion is offline   0 Reply With Quote
Old Jun 25, 2011, 08:40 PM   #10
chown33
macrumors 603
 
Join Date: Aug 2009
Quote:
Originally Posted by jiminaus View Post
Nooo!!!
...
From that day forward #define's for anything other than conditional compilation have been by sworn enemy.
Macros was the last of my suggestions in my original reply.

I advocate using the simplest and most robust solution that accomplishes a task. Sometimes that happens to be a macro. For example, look up NSAssert().

Trust me, anything can be abused. I can't tell you how many times I see "oversharing" of internal state with senseless proliferation of properties. Why? Because when they first learned it, there was a property for every instance variable, and they never really learned the why behind properties. Properties just became a crutch to avoid having to write retain/release when using ivars.
chown33 is offline   0 Reply With Quote
Old Jun 26, 2011, 03:21 AM   #11
MrFusion
Thread Starter
macrumors 6502a
 
Join Date: Jun 2005
Location: West-Europe
Quote:
Originally Posted by chown33 View Post
Trust me, anything can be abused. I can't tell you how many times I see "oversharing" of internal state with senseless proliferation of properties. Why? Because when they first learned it, there was a property for every instance variable, and they never really learned the why behind properties. Properties just became a crutch to avoid having to write retain/release when using ivars.
Oh, something tells me I might be guilty of this as well. Could you elaborate?
__________________
24" iMac, 13" MacBook, iPod Touch. iPod mini and PowerPC Mac Mini gathering dust somewhere.
MrFusion is offline   0 Reply With Quote
Old Jun 26, 2011, 06:05 PM   #12
chown33
macrumors 603
 
Join Date: Aug 2009
Quote:
Originally Posted by MrFusion View Post
Oh, something tells me I might be guilty of this as well. Could you elaborate?
The basics are simple:
Properties are part of a class's public API. The only things that should appear in a public API are what's needed to interface that class with other classes. It's the Principle of Least Exposure, aka coupling.
http://en.wikipedia.org/wiki/Couplin...puter_science)

I make the analogy with human public behavior. In polite company, we don't discuss bowel movements or publicly display our genitals. Yes, I realize it's a quaint anachronism on the internet, but a certain level of politeness remains an expected courtesy elsewhere. Think carefully about what you expose in public, or you might regret it later.

If there's no reason for a property to be public, and you're only using @synthesize to get auto-generated accessors for things like retain, copy, or atomic attributes, then put the properties in a private category or protocol, or in an anonymous class extension. Properties can be declared in any category or protocol.

You can also modify some attributes of properties in private categories or extensions. An example might be a public readonly property being redeclared as readwrite in private, so private code can use a setter method.

http://developer.apple.com/library/m...roperties.html
http://developer.apple.com/library/m...ategories.html

SIMPLE EXAMPLE CODE
Code:
#import <Foundation/Foundation.h>

@interface Fraction : NSObject
{
@private
   int numerator;
   int denominator;
}

@property  int numerator;
@property  int denominator;

@property (readonly)  double rational;

-(void) test;

@end


//@interface Fraction (PrivateMethods)  // classic named category
//-(void) private1;
//
//@end


@interface Fraction (  )  
  // empty ( ) means a class extension, effectively an anonymous category

@property (readonly)  NSString * frak;

-(void) reduce;

@end


@implementation Fraction;

// Override Inherited
-(NSString *) description
{  
   return [NSString stringWithFormat:@"%i/%i ", numerator, denominator];
}


// Public Properties
@synthesize numerator;
@synthesize denominator;

-(double) rational
{  return ((double) numerator) / denominator;  }


// Public Methods
-(void) test
{
   NSLog(@" frak: %@", self.frak );
   [self reduce];
   [self private1];  // has warning since no forward declaration
}


// Private
-(NSString *) frak
{  return [NSString stringWithFormat:@"%.15f ", self.rational];  }

-(void) private1
{  NSLog( @" private1 " );  }

-(void) reduce
{  NSLog( @" reduce " );  }

@end



int main(int arcgc, char *argv[])
{
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   
   Fraction *frac = [[Fraction alloc] init];
   
   frac.numerator = 1;
   frac.denominator = 3;

   // These only use public API
   NSLog(@" Values of frac are:");
   NSLog(@" frac: %@", frac );
   NSLog(@" rational: %f", frac.rational );

   [frac test];

   // These compile only when main() is in same file as @implementation.
   // Otherwise these will cause compiler warnings or errors.
   NSLog(@" frak: %@", frac.frak );
   [frac private1];
   [frac reduce];

   [frac release];
   
   [pool drain];
   return 0;
}
The overall example is contrived, but it shows simple examples. I recommend trying it as-is in a single file (which is the way I wrote it), then breaking it out into separate files like you'd have in a typical project. Once broken out, it will become clear that main() can't reference the private properties or methods of the Fraction object. I thought it was important to see the in-scope case first, then break it out into separate scopes and see the result.

Note that the rational property depends on the values of numerator and denominator, and is not independent from them. Hence, it has no separate setter, i.e. is inherently readonly. Another example would be a Rectangle class with height and width properties, where area was a readonly property. The world is full of dependent properties like this.

One other thing about this example. The decision of whether to make something a simple method or a readonly property is something you should consider in the class design. I chose to define frak as a property, but it could also be declared as a method in the @interface:
Code:
-(NSString *) frak;
chown33 is offline   0 Reply With Quote
Old Jun 26, 2011, 09:53 PM   #13
Sydde
macrumors 68000
 
Sydde's Avatar
 
Join Date: Aug 2009
Quote:
Originally Posted by MrFusion View Post
I don't use garbage collection. I prefer to keep my own tab on retain/release.
If you are writing for both, it would seem that using GC on the Mac side would be counter-productive, inasmuch as it would require some different design between the two (since GC is not available for iOS), though ARC will probably change this dynamic in the future.
Sydde is offline   0 Reply With Quote

Reply
MacRumors Forums > Apple Systems and Services > Programming > Mac Programming

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Similar Threads
thread Thread Starter Forum Replies Last Post
Source Code from good programmed Mac Applications? mrtnbroder Mac Programming 5 Sep 24, 2013 04:07 PM
Good practice to share the disk space for MacOS and PD VM(WIn8)? oldguru MacBook Pro 1 Mar 14, 2013 05:28 AM
retain UIColor IDMah iPhone/iPad Programming 11 Aug 21, 2012 03:18 PM
Good resources for Cocoa programming kaworu1986 Mac Programming 9 Jul 23, 2012 02:22 PM

Forum Jump

All times are GMT -5. The time now is 03:15 PM.

Mac Rumors | Mac | iPhone | iPhone Game Reviews | iPhone Apps

Mobile Version | Fixed | Fluid | Fluid HD
Copyright 2002-2013, MacRumors.com, LLC