Chansy的N次元空間
没有鹳狸猿,没有河蟹的世界|鹳狸猿とチュウゴクモクズガニのない世界
没有鹳狸猿,没有河蟹的世界|鹳狸猿とチュウゴクモクズガニのない世界
六月 19th, 2011
All languages have something interesting to teach us about the art of programming — and as a Ruby developer almost exclusively, I’ve always been afraid of strongly typed languages like Java and C++, or the great-grand-daddy of them all, C. So when I decided to tackle Cocoa to work on iPhone apps I went in somewhat leery of how I’d have to change, but hopeful that I’d become a better, more versatile programmer. After a few weeks I would say I’ve achieved my goal, but it’s certainly taken a lot of blood, sweat, and tears to get here.
I think a lot of people are in a similar position to me: trying to pick up Objective-C to do iPhone programming, and coming in with only a book or two as their guide. Most of the books out there for learning Cocoa and the iPhone SDK are very good but there are a lot of gotchas and stylistic tricks that really mean the difference between comprehending a statement and staring at it in horror and confusion until Google comes to your rescue. The aim of this blog post is to come to your rescue instead of Google. These are the 8 biggest foul-ups that I wish I had known before I started learning Objective-C. Hopefully you’ll learn something here that will prevent you from making an idiotic mistake I already made.
Seriously, I’m not even kidding. This is THE way to debug hard to find errors in your apps.
As you may or may not know, iPhone apps do not have an automated garbage collector, so you have to release and retain objects manually. Sometimes you accidentally release an object that you intended to hold on to. Your code will not fail until a message is sent to the deallocated instance, at which point your application will crash and you’ll have no idea why. Oh, sure, you’ll have the general EXC_BAD_ACCESS error that tells you you tried to access memory that was deallocated… but that isn’t really helpful.
If you turn on NSZombieEnabled, then deallocated instances are retained in memory but their class is changed to Zombie, so that when they receive a message they raise an error. Now you can actually debug those memory problems.
To turn this on:
Do this right now. You’ll thank me later. Just remember to redact it before you bundle your app to production, because in production, you want instances to be deallocated, not turned into zombies.
This one was difficult for me to wrap my head around.
In most object oriented languages, like Ruby, if you send a method to an object but that object is nil, your program dies messily. This is what amazing gems like Andand are supposed to protect against: nil inadvertently receiving a message and raising an error.
In Objective-C, if nil is sent a message, that’s just fine. In fact I would go so far as to say this is an amazing and distinguishing feature of Objective-C: it is used extensively in the iPhone classes and you should use it too. This allows you to make code sort of like this:
if ([anObjectThatMayBeNil startProcessing]) { // ...code...}You don’t have to test whether or not the object is nil. If this statement isn’t true then either startProcessing failed, or the object wasn’t there: either way, you don’t want to execute the code block. Whether or not this is just madness is certainly open for personal interpretation. Coming from Ruby I sort of like it, but I can see how it can make code very very difficult to debug.
My advice is to embrace the insanity. Objective-C provides a lot of power by allowing you to use objects’ methods even when they may not exist: be careful in how you use this power and your programs will flourish.
This is a really simple and dumb one.
NSString is what most Objective-C classes that want string-like objects expect; they are NOT formed with quotes, but with an at-sign then quotes.
@”This is a valid NSString.”
“This is not, and will raise annoying and mysterious compiler errors if you try to use it.”
Similarly, don’t forget your semi-colons. I know I sound like a noob, but coming from Ruby it’s easy to trip up and just hit enter and then be confused when the compiler dies. Happily in this case at least you get a sensible message so you can find the problem and put in a semi-colon.
Native Objective-C classes (particularly iPhone classes) make extensive use of delegates. They rely on the fact that you can send messages to nil objects without a problem that I discussed earlier: even if you don’t assign a delegate the messages are sent anyway, there’s just no one there to listen to them. It’s like ‘if a function falls in a forest…’ or something like that.
Anyway, delegates are extremely powerful and are one of the cooler elements of style in Objective-C. They allow you to send long-running or complicated tasks off to another object and be certain that you’ll hear back from them eventually, while simultaneously affording you the power of polymorphism in that you can call that task from any object and respond to the task situationally.
An excellent example of this is CoreLocation, the framework the iPhone uses to do location updating. In my iPhone app I have a singleton class Locator that controls access to the CoreLocation framework. It is the delegate of the CLLocationManager like this:
self.locationManager = [[CLLocationManager alloc] init];self.locationManager.delegate = self;[self.locationManager startUpdatingLocation];
When the CLLocationManager receives a location update, it calls a method on the delegate called locationManager:didUpdateToLocation:fromLocation:. All I have to do in my code is implement that method: I know that it will be called if and only if the location was correctly received. There’s another method for locationManager errors.
The power of this, of course, is that one object can have its delegate changed; thus, the delegators can respond to the same call in different ways. Perhaps in one controller in my app I pop up a window after the location has been updated. In another, I animate something. The method is the same for both controllers (locationManager:didUpdateToLocation:fromLocation:): all I’m doing is changing which one is the delegate of CLLocationManager.
Once you get used to this programming style, you’ll really like it.
NSLog(@"Hi there, the object you want is: %@", anObject);
Ruby gives an excellent backtrace when it crashes. Objective-C (even with gdb) leaves a lot to be desired, in my opinion. Log frequently, everywhere, everything, so that when your application mysteriously crashes (and it will) you know around where so hopefully you can find the foul-up.
By the way, the %@ inside the NSString up there? That’s the syntax for object substitution. This ONLY works for NSObjects, and it calls their description method. If you try to do it on a regular C type it’ll crash, so this is totally wrong:
int i = 1;NSLog(@"The number is: %@", i); // BOOM CRASH!
Instead, you should use the correct string substitution:
int i = 1;NSLog(@"The number is: %i", i); // sweet, sweet success
An easy gotcha to miss. Check out the Cocoa string substitution documentation for all the other types out there.
A closely-related corollary to the NSLog advice: give all your custom object classes a description method.
- (NSString *)description {return self.name;}Now when you log them with NSLog they’ll return a sensible string, instead of nil.
Use strong typing when it seems sensible, but you don’t have to be married to it because Objective-C certainly isn’t. Any Objective-C object can be declared with the type ‘id’ rather than its particular class. This allows you to use some potentially dangerous instances of polymorphism:
Duck *aDuck = [[Duck alloc] init];Goose *aGoose = [[Goose alloc] init];Pond *bigLake = [[Pond alloc] init];bigLake.inhabitant = aDuck;[bigLake.inhabitant quack]; // The duck says: "quack!"bigLake.inhabitant = aGoose;[bigLake.inhabitant quack]; // The goose says: "do I look like a duck to you?"
This is made possible by a simple declaration in the Pond class:
@interface Pond : NSObject {id *inhabitant;}@property (nonatomic, retain) id *inhabitant;Now Objective-C doesn’t care what the inhabitant is, only that it descends from NSObject (that defines id). This will sensibly warn you at compile-time that the inhabitant might not respond to quack, though, so be careful with this power. If you send a bad method to an object it isn’t the same as if you sent a message to nil: your program will crash if the object has no such method defined on it.
But still, you can pass all kinds of different objects around now however you like. Just remember to make sure they respond to the method you pass to them before you use them.
I was trying to do something clever with the UIWebView object but it didn’t seem like it was possible with the documentation given to me by the default API viewer. Then Google came to the rescue and showed me the light (or rather, the method webView:shouldStartLoadWithRequest:navigationType:). Even though the documentation is ostensibly complete (and even that won’t be true unless you subscribe to it and download the complete docs), a lot of people have experimented with Cocoa and the iPhone SDK for a lot longer than we have. A quick Google search can sometimes yield surprising results, and you can bet any problem you’ve run into, someone else has already had trouble with.
I’m sure there’s a bunch I missed. My C was very rusty; after using Ruby almost exclusively for a few years, it was difficult to pick up Objective-C. But ultimately I think it was very rewarding. Objective-C is elegant and simple, giving you more than enough rope to hang yourself, or create something really amazing (out of rope, to maintain the metaphor). Just make sure to keep in mind this hit-list of 8 items and your Objective-C programs will flourish. And if you’ve got something to share, post it as a comment, and let the world read your helpful advice!
近期评论 | 最新のコメント