A Quick Objective-C 2.0 Tutorial: Part II
In the first tutorial, we looked at the basic syntax and features in Objective-C 2.0. In this installment, we'll take a look at some of the options for customizing, and some of the more advanced syntax.Notes About Dot Syntax
This may be obvious, but a few comments I've seen elsewhere suggests maybe it's not. If you use dot syntax like this:
NSString* movieTitle = movie.title;
You can continue to add dots to the expression:
NSString* director;
director = movie.director.fullName.capitalizedString;
NSUInteger movieTitleLength;
movieTitleLength = movie.title.length;
Which is equivalent to:
NSString* director;
director = [[[movie director] fullName] capitalizedString];
NSUInteger movieTitleLength;
movieTitleLength = [[movie title] length];
If you have a category for an existing class, you can use it with dot syntax as you would any other property:
@interface NSString (MyCategory)
- (NSUInteger)lengthWithoutSpaces;
@end
NSUInteger movieTitleLength;
movieTitleLength = movie.title.lengthWithoutSpaces;
This syntax should only be used for methods that act as getters or setters.
Public Properties with Private Setters
Some properties should have public getters and private setters. A good example of this is a cached value that you don't want clients to set directly. The conventional Cocoa solution to this is to create a Private category:
// Movie.h
@interface Movie : NSObject
{
NSString* summary;
}
- (NSString*)summary;
@end
// Movie.m
@interface Movie (Private)
- (void)setSummary:(NSString*)aValue;
@end
@implementation Movie
- (void)setSummary:(NSString*)aValue
{
NSString* oldSummary = summary;
summary = [aValue retain];
[oldSummary release];
}
@end
If you translate this to the property syntax, you'll end up with the following:
// Movie.h
@interface Movie : NSObject {
NSString* summary;
}
@property (readonly) NSString* summary;
@end
// Movie.m
@interface Movie (Private)
@property (readwrite,copy) NSString* summary;
@end
@implementation Movie
@synthesize summary;
@end
This looks okay, but when the program runs, we see that the setter was never actually implemented:
-[Movie setSummary:]: unrecognized selector sent to instance 0x132ac0
The reason for this is that the compiler only looks at the original class definition — not additions from categories — to determine which methods to implement. Summary is declared as readonly, so the compiler doesn't generate a setter.
But there's another way. Objective-C 2.0 allows you to create nameless categories, which are formally called class continuations. From the compiler's perspective, any methods in a continuation are part of the original class — not just add-ons. This gives the compiler enough information to generate a setter.
Using a class continuation, we can redefine the property privately in the implementation file to be readwrite:
// Movie.m
@interface Movie ()
@property (readwrite,copy) NSString* summary;
@end
The one caveat here is that we get a warning:
warning: property 'summary' attribute in 'Movie' class continuation
does not match class 'Movie' property
Let's put the two definitions side-by-side to see why we get this warning:
// Movie.h
@interface Movie : NSObject {
NSString* summary;
}
@property (readonly) NSString* summary;
@end
// Movie.m
@interface Movie ()
@property (readwrite,copy) NSString* summary;
@end
If we don't specify copy/retain/assign, the property defaults to assign. So, to the compiler, it looks like this:
@property (readonly,assign) NSString* summary;
@property (readwrite,copy) NSString* summary;
We can fix this by changing the public property definition to copy. Here's the final, correct version of a public property with a private setter:
// Movie.h
@interface Movie : NSObject {
NSString* summary;
}
@property (readonly,copy) NSString* summary;
@end
// Movie.m
@interface Movie ()
@property (readwrite,copy) NSString* summary;
@end
It looks a little bit weird, but usually it's better to make sure your project compiles without warnings. And don't worry about clients of your class accidentally calling the setter. If they do, they'll get this:
error: object cannot be set - either readonly
property or no setter found
That's a hard error, not a warning. If the Movie class attempts to set its own private value internally, no errors or warnings are generated:
self.summary = @"Superheroing meets everyday life.";
Custom Accessor and Variable Names
If you want customize getter and/or setter names, it's a simple matter of adding an additional key/value pair to the property declaration:
@property (assign,getter=isSelected) BOOL selected;
The setter is a bit different. It must have the colon at the end to match normal Objective-C notation:
@property (copy,setter=setDefaultTitle:) NSString* title;
You should use these sparingly, though. Properly-named accessors will make your life easier, particular with Key-Value Coding and Cocoa Bindings. The example above of adding an "is" prefix is fairly common for BOOLs, and Key-Value Coding is smart enough to understand the intention.
You can also override instance variable names. By default, the compiler will look for an instance variable which has the same name as the property, but you can customize this with the following syntax:
@synthesize propertyName=ivar;
For example:
// Movie.h
@interface Movie : NSObject {
NSString* movieTitle;
}
@property (copy) NSString* title;
@end
// Movie.m
@implementation Movie
@synthesize title=movieTitle;
@end
Mixing Synthesized and Custom Accessors
The @synthesize directive is flexible. It will only generate accessors for methods which don't already exists. For example, this is completely valid:
@implementation Movie
@synthesize title;
- (void)setTitle:(NSString*)newTitle {
title = [newTitle copy];
}
@end
(this example assumes garbage collection is on)
In this case, @synthesize will only generate a getter, since the setter already exists. This is helpful if you want to do something other than just set a value in the setter — such as add logging, for example.
Methods Provided at Runtime
Some accessors are created dynamically at runtime, such as certain ones used in CoreData's NSManagedObject class. If you want to declare and use properties for these cases, but want to avoid warnings about methods missing at compile time, you can use the @dynamic directive instead of @synthesize. For example:
// Movie.h
@interface Movie : NSManagedObject {
}
@property (retain) NSString* title;
@end
// Movie.m
@implementation Movie
@dynamic title;
@end
The other subtle note here is that there's no title instance variable defined. This is because NSManagedObject has its own data store. Since we're using @dynamic, there will be no warnings or errors about the missing instance variable.
Using the @dynamic directive essentially tells the compiler "don't worry about it, a method is on the way."
Extra Notes
The poseAsClass: method has been deprecated, but the runtime functions have been completely overhauled, so you might be able to find what you need in /usr/include/objc/runtime.h
In terms of style, you should only use the dot syntax for things that act as getters and setters. You should not use it for utility methods, for example. In this way, Objective-C is different from Ruby, JavaScript, Java, and so on:
// Good
NSString* fileName = filePath.lastPathComponent;
NSSize imageSize = image.size;
person.fullName = @"Bob Parr";
// Don't do this
container.rebuildCache;
fileName.stripWhitespace;
object.alloc.init;
Finally, it's worth noting that Xcode 3.0 can actually convert your code to Objective-C 2.0:
A Quick Objective-C 2.0 Tutorial: Part II
Posted Nov 3, 2007 — 30 comments below
Posted Nov 3, 2007 — 30 comments below
Ulai — Nov 03, 07 4959
What would be a good place to start?
Is the community really that dependent on nice people like you writing their own independent tutorials on their blogs? Hasn't Apple made any good tutorials to teach one the whole Cocoa from start to finish for their new developers?
I have considered books like Hillegass etc., but now that Leopard is out I'd like some material that really is written from Leopard point of view.
Scott Stevenson — Nov 03, 07 4960
What existing experience do you have, and what are you looking to accomplish?
Hasn't Apple made any good tutorials to teach one the whole Cocoa from start to finish for their new developers?
I'm not sure how to answer this question. For one, there's no "finish," per se. Mac programming is a universe of frameworks, tools, concepts, and conventions, so any reference that would attempt to capture it all would be immense
It's also not necessarily clear where "start" is. Do you teach C? Object-oriented design? Just Cocoa? Cocoa and CoreFoundation? There are developers of all skill levels and backgrounds out there, many with different reasons for learning Mac programming.
Marco Masser — Nov 03, 07 4962
I found an error though: In the third code box, the last line shouldn't have that "NSUInteger" there.
Ulai: There's the "Cocoa Application Tutorial" in the Xcode documentation (also on the ADC, of course), which is quite nice to get started: http://tinyurl.com/kzrf
For Objective-C 2.0, see here: http://tinyurl.com/yw3t
Pay attention to the links on the bottom of these pages and you'll find enough to read : )
Ulai — Nov 03, 07 4964
I am just someone who knows C# and some C, but basically a beginner/novice in Mac programming. I own a Mac and I want to start to program for it :) As an example, let's just say some Mac OS X app to keep track of my stock portfolio or something. That would be a nice first exercise for myself :) In other words: Assume that I'm a total beginner wanting to learn to code some programs for Mac OS X.
Ulai — Nov 03, 07 4966
Thanks alot. Maybe http://tinyurl.com/yw3t is something that I will take a look at. This seems to be mostly to serve as an ObjC2 tutorial though, but not much Cocoa (XCode 3.0, Interface Builder, etc.) per se. Has Apple also made something available for Cocoa per se?
Marco Masser — Nov 03, 07 4967
If you click on the Tools section (bottom right), you'll also find Xcode guides.
Steve Johnson — Nov 03, 07 4968
You are such a gifted writer with great knowledge, I am asking you
to PLEASE write a whole BOOK on Macintosh programming:
the NEW Objective-C,
the NEW XCode,
the NEW Interface Builder and
the NEW Cocoa.
I am sure there are many other readers who feel the same way.
SteveJ
Steven Quinones-Colon — Nov 04, 07 4969
I second the motion.
Best regards.
Steve Weller — Nov 04, 07 4970
Your wish might be granted if you can raise oodles of cash and thrust it toward SteveS. You'd have to convince him that is was a good use of his time as well as a good use of your money though. Writing books takes a huge amount of time and effort and then just when you are done everything changes again.
Aaron Harnly — Nov 04, 07 4972
Drew McCormack — Nov 04, 07 4975
I am a bit confused about the use of @dynamic. Your Movie example above seems to imply that if I create an NSManagedObject, which has an attribute title, and then use the property declaration prescribed, that I will be able to then then this
and everything will work out. Well, I tried it to make sure, and it compiles fine, but the method setTitle: does not exist at run time, and an exception is the result.
Am I missing something?
Drew
Ross Carter — Nov 04, 07 4977
Joachim Mrtensson — Nov 04, 07 4978
This may be obvious, but a few comments I've seen elsewhere suggests maybe it's not. If you use dot syntax like this:
NSString* movieTitle = movie.title;
I thought it was logical that it did not work this way? so what you are saying is that I can not only reach attributes declared with the @property syntax, but any method that does not take an argument? In essence this means that I could avoid bracket syntax for a lot of Cocoa?(This is madness!) What is Apples stance on that? (This is ... Cupertino!)
This syntax should only be used for methods that act as getters or setters. I hope you don't mind if I enforce this in the TextMate Objective-C completion bundle.
Blain — Nov 04, 07 4979
NSManagedObjects, for better or worse, don't create setters or getters in 10.4, and looks like 10.5 either. They do it all with KVC. What it means is that, with managed objects:
// Works! movie.title = @"The Hudsucker Proxy"; // also works! [movie setValue: @"The Hudsucker Proxy" forKey: @"title"]; // Doesn't work unless you made the function. [movie setTitle: @"The Hudsucker Proxy"];
Long answer:
If you've got your heart set on setTitle:, the 10.4 way would be making the method yourself, and using setPrimativeValue:forKey:. You'd have to diddle about to verify that willChangeValueForKey: and didChangeValueForKey: were called for you or not. It's pretty messy, and there's probably a better way that I don't know.
Short answer:
Dot notation and setValue:forKey: are your friends, especially with MO.
Drew McCormack — Nov 04, 07 4980
I wish it did. I just tested this too, and get the same error at runtime, ie, that setTitle: doesn't exist. This makes sense, because the dot syntax is just a short-hand for calling setTitle: directly.
In fact, I can remember pretty clearly that Apple stated at WWDC that you should be careful not to assume dot-syntax and KVC are interchangeable. They are actually totally unrelated.
Drew
Scott Stevenson — Nov 04, 07 4981
I just made a Core Data project from scratch with this for the header:
@interface Movie : NSManagedObject { } @property (retain) NSString * title; @end
And this for the implementation:
@implementation Movie @dynamic title; @end
This in the controller:
- (IBAction)testTitle:(id)sender { Movie* movie; movie = [NSEntityDescription insertNewObjectForEntityForName:@"Movie" inManagedObjectContext:self.managedObjectContext]; movie.title = @"My Movie"; [movie setTitle:@"My Movie 2"]; NSLog(@"%@", movie.title); }
It prints "My Movie 2" and no exceptions are raised. Are you sure your model is correct?
britt — Nov 04, 07 4982
I yowled (nicely, I hope :-) at him for that; because of this:
"Avoid the use of the underscore character as a prefix meaning private, especially in methods. Apple reserves the use of this convention. Use by third parties could result in name-space collisions; they might unwittingly override an existing private method with one of their own, with disastrous consequences. See “Private Methods” for suggestions on conventions to follow for private API."
found here.
and this:
"Don’t use the underscore character as a prefix for your private methods. Apple reserves this convention."
found here.
Scott and I both agree that Apple's recommendation to name private components with a prefix (like this: XY_doSomethingAmazing) is really ugly to read. My solution is to, whenever possible, avoid having true private methods -- instead, I prefer to use protected semantics, where those methods are declared in the header and documented as being available only for use by subclasses (typically, I can design a class such that what those methods are doing is abstract enough to keep subclasses from getting too tightly bound to the implementation details of the superclass).
For true private ivars (and yes, I do know that this makes you grimace, Scott :-) where by that I mean an ivar that should not be available to subclasses at all, I declare them @private and do not create accessors for them (figuring that a true private ivar is so close to the implementation of the class that it doesn't need the encapsulation that accessors provide, and accepting the slight trade-off in maintainability should I ever need to attach functionality to the ivar accesses), and if a subclass does use the same name, the compiler will throw an error.
The proper solution, of course, would be for GCC & the runtime to add true private namespaces for ivars and methods; perhaps by some simple name-mangling scheme like prepending the class path (delimited by periods or some other character that you can't type in a method/ivar declaration) to the token string as soon as the compiler recognizes it as a private item...
Scott Stevenson — Nov 04, 07 4984
Britt sent me some contributions to THCanvasView, and this topic came up. I've always used underscores as a prefix for ivars and private methods. Britt convinced me that this isn't such a great idea for private methods — because collisions with built-in frameworks are possible — but I still think it's okay for ivars. There was no particular reason I left them out in this case.
@britt: Scott and I both agree that Apple's recommendation to name private components with a prefix (like this: XY_doSomethingAmazing) is really ugly to read
Just to be clear, I don't think the XY_ syntax is actually a recommendation, as much as it is a suggestion. The only real recommendation is avoid single and double-underscores at the beginning of a method name.
For true private ivars (and yes, I do know that this makes you grimace, Scott :-) where by that I mean an ivar that should not be available to subclasses at all, I declare them @private and do not create accessors for them
The reason I grimmace is that I've seen countless problems with bindings, KVC, and memory management where people blame the frameworks, only to discover that using accessors solves all of it. You can get and set instance variables directly if you really need to, but I think it just complicates life with no real benefits.
britt — Nov 04, 07 4985
Uhh, yes - the frameworks should be presumed innocent until conclusively proven guilty: ten bazzillion other folks are using these things without much of a hiccup, so the odds are it's your oddball code, not the frameworks...
Um.. for the benefit of the other readers (as we - Scott and I - have been over it already :-) it is my general position that true private ivars should never be used with KVC/KVO or bindings; as those mechanisms all are open to the outside world, so anything used with them should be at least in the protected scope, if not public. And if it's at least protected, then: a) it most definitely should have accessors, b) it should not have prepended underscores (Apple's names or not), and c) it should be well documented so the rest of us can avoid hitting it.
Although Apple says not to do it (and so I don't do it in my code), I don't think it's actually harmful to prepend an underscore on a @private declared ivar, because the compiler will catch ivar namespace collisions and throw an error.
As for the memory management; yup, it's a hazard that must be watched out for. Whenever I get a "bad access" crash, the first thing I think is either I forgot to nil something out or I released something that I shouldn't have -- tracing the origin of the bad pointer back thru the code will usually turn up the culprit.
>> Just to be clear, I don't think the XY_ syntax is actually a recommendation, as much as it is a suggestion.
Yes - you're right; that's certainly not the only way to get around the issue, nor does Apple say "thou shalt".
>> The only real recommendation is avoid single and double-underscores at the beginning of a method name.
Well, Apple does actually call it out for ivars, too, but because the compiler throws errors in that case, I don't think it's as big an issue as with method names (which can cause silent failures, sometimes in already shipped code if the frameworks change out from under you with a system update).
:-)
mmalc — Nov 05, 07 4987
Or -- with no disrespect to Scott -- by reading Apple's documentation...
(I'm glad though that Scott is able to present this material from another perspective.)
The documentation also covers managed object accessor methods -- including use of the dot syntax -- in some detail.
For several example projects that use Objective-C 2, see this page. See also this example which illustrates Objective-C 2 language features, garbage collection, and integration with Core Foundation.
Public Properties with Private Setters
One important point, though, that I'm afraid Scott doesn't address here is that by default properties are atomic.
This means that the code example shown for Movie is not equivalent to the declared property:
@property (readonly) NSString* summary;
should be
@property (readonly, nonatomic) NSString* summary;
For more details, see Performance and Threading.
mmalc
Drew McCormack — Nov 05, 07 4990
I threw together a quick test app, but I initialized the NSManagedObject by calling init instead of using the usual CD initialization method. Obviously if you do that the accessors are not correctly added to the runtime.
So dot syntax, and setters, should work with properties, and @dynamic is useful in this case.
Drew
mmalc — Nov 05, 07 4991
... as discussed in detail in the documentation -- in Managed Object Accessor Methods and Using Managed Objects...
I threw together a quick test app, but I initialized the NSManagedObject by calling init instead of using the usual CD initialization method. Obviously if you do that the accessors are not correctly added to the runtime.
The need to use the designated initialiser is stressed in Creating and Deleting Managed Objects, the class reference, and the Release Notes.
mmalc
leeg — Nov 05, 07 4993
Blain — Nov 05, 07 4995
typedef struct ourHiddenData ourHiddenDataStruct; @interface ourFunkyClass: NSObject { ourHiddenDataStruct *doNotTouch; }
And then define the fields in your nonheader code. (If you're really perverse, C++ compilers won't fuss about redefining a declared struct into a class, so you can have the plain Obj-C headers use pointers that Obj-C++ code later uses as a C++ class.)
Ahh, Cocoa. Where two of the most-talked about subjects are how to access Apple's undocumented frameworks and interfaces, and how to obfuscate and obscure your own data that would be accessible by default. In short, we know the arts of making private public and public private.
Scott Stevenson — Nov 05, 07 4996
You have an interesting sense of humor.
Blain — Nov 06, 07 4999
I wish I could blame the operator overloading debate for the blending, but no, I was fooling around, trying to like C++, and about the possibility of wrappers, to let C and objective-C call C++ things. So yeah, opaque struct pointers for the C side, and they just happened to be C++ class instances.
It could be worse. At one point for Historian, I was debating the possibility of using 1980s era Mac Toolbox carbon functions to help UTI accuracy (IE, get type and creator codes). And then I found modern functions, which ruined the fun.
Marco Masser — Nov 06, 07 5007
What I meant specifically is, for example, that thing with empty category names (class continuation). Honestly, I don't think I would have known where to look for a solution if I had a problem with properties like the one described in the article, but I'm quite sure that it would take a lot of time until I came up with such a solution (if at all...).
As I read it here, maybe I recall that if I ever come across such a situation, so this could save me quite some time in the future.
And, just so you know, "playing around" includes reading documentation most of the times for me : )
kamelito — Nov 29, 07 5147
http://www.amazon.fr/Mac-Os-ant%C3%A9rieures-Applescript-Dashboard/dp/2100500767
charles castille — Oct 09, 08 6480
categories. I am working with the example code provided by Apple on page
48 of the Obj C language description.
The problem I have entails calling a function defined in the extension
from within a function defined in the original class. Referring to the
code example described above, suppose I call the setNumber method
defined in the extension, from within init, defined in the MyObject interface. If I do this, the compiler returns an error saying setNumber is not known. Example code is provided below.
#import "MyObject.h" @interface MyObject ( ) -(void)setNumber:(NSNumber *)newNumber; -(void)resetNumber; @end @implementation MyObject -(NSNumber *)number { return number; } -(id)init{ setNumber = [[NSNumber alloc] initWithDouble:10]; /*error*/ return self; } -(void)setNumber:(NSNumber *)newNumber { number = newNumber; } @end
Scott Stevenson — Oct 11, 08 6481
The problem here is just the syntax. To call a method named "-setNumber:", you need to do something like this:
NSNumber* myNumber = [NSNumber numberWithDouble:10.0]; [self setNumber:myNumber];
You only use the equals sign when setting a variable or property value. You could probably also get away with this in Objective-C 2.0:
self.number = [NSNumber numberWithDouble:10.0];
Stylistically, though, you should probably actually define a @property if you're going to do that.