Effective Objective-C

JavaBook
There is a really great book more than worth reading by Joshua Bloch called Effective Java, Programming Language Guide (Pearson Education Inc, Addison-Wesley) which states in 57 items rules to good Java programming. 
Rumors say that the inventors of Java where inspired by some ideas of early Objective-C and therefore as an ObjC programmer 
you will recognize some idioms and kind of feel home in the Java world. 

Actually when I read the book I found that many rules more or less apply to Objective-C as well. In this article I'd like to 
comment on Joshua's items in regard to Objective-C - if they can be applied and how. You will need a copy of Joshua's book as I don't delve into details here.

Item 1: Static factory methods for creation of objects: Generally this is very good idea. Objective-C does not allow method 
overloading (think of it as pure C) and hence you would need differently named init-methods anyway so that would not be the 
reason why you should use class methods as a factory. 
The advantage of class factory methods is that they need not create new objects if objects are allowed to be used more than once. You will find an example in 
[NSImage imageNamed: @"SomeName"] 
Plus class factory methods might give back specialized subclasses. Even though the mechanism differs a little bit in Objective-C go to Apple and read about Class Clusters

Item 2: Closely related to item 1: Singletons. Take a look at Apple's implementation

Item 3: If you really want that no object can be created of your class you have to overwrite NSObjects allocWithZone/init and 
return nil. The concept of a private constructor can not be realized in Objective-C so the best you can do is to overwrite all 
allocWithZone returning nil. 

Item 4: Prevent object duplicates: Using NSConstantStrings like @"Hello" makes that string constant. You can even enhance its 
use by declaring it static in the definition file of your class. Now your class can reuse the same string over and over again. 
This does not have to be restricted to Strings. You can use any object you like as a static (class) variable. Bloch uses a Date. If 
you like to do the same declare: 
@implementation MyClass 
static NSCalendarDate * const BOOM_START = nil; 
+(void) initialize { 
if( self == [MyClass class]) { 
BOOM_START = [[NSCalendarDate alloc] initWithYear:...]; 


@end
 
+(void) initialize replaces Java's static { } - block. 

If for some reason you need a constant String that has to visible outside of your class declare it extern: 
deklaration file (.h) 
extern NSString * const MY_CONSTANT; 
defininition file (.m) 
NSString * const MY_CONSTANT = @"Constant Name"; 

Item 5: Right now Objective-C uses a well defined memory management with retain-counting. In the near future with the release of Mac OS X 10.5 a Garbage Collector for Objective-C will be introduced. It is very likely that item 5 can be applied there. 

Item 6: Finalizer. In Objective-C this can be compared to 
-(void) dealloc { } 
which is called when an object's retain-count is zero. Joshuas idea to have a separate finalizing method which is called explicitly 
can be taken into account but depends on your design. Generally Objective-C will call dealloc when the retain-count is zero but 
with autorelease pools it can take a while. If you are sure that no other object is interested in your helper object that you want to terminate calling the finalizing method is a good thing. 

But beware of singletons which are shared and not deallocated at runtime. If you use a singleton for database access 
(connect/disconnect etc) for example when you app quits or crashes disconnect has to be called. That's no problem - just a matter of clear design. 

A word of caution on autorelease pools. There are times when an object has to be autoreleased like when it is a return object that was created especially for the caller (read: no ivar). That is part of memory management. But it is not a good idea to use autoreleased objects within a method. For example you have to create objects within a for-loop. Each object will perform a certain task and is not needed later. You cannot reuse that object (for whatever reason). Then alloc/init it, make it perform its task and release it at once. If you put it in the autorelease pool it will stay there at least until the runloop finished one turn. If you create large amounts of objects this means that memory usage explodes and will be released later than necessary. 
On the other hand this should be a rare case. Usually you would either reuse your worker object or it is created and put into a collection which means it isn't deallocated. Still then use release after you put into the collection so the autorelease pool won't have to do your work. 

Item 7Equals in Java is exactly the same as isEqual in Objective-C so all the contract rules Bloch describes apply just the same. The same goes for 

Item 8hashCode which simply translates to hash in Objective-C. Both are methodes that are declared as a protocol (in Java: interface) to NSObject so every object understands isEqual and hash but the default implementation of NSObject will not necessarily meet your needs. 

Item 9: This is easy as well: Java's toString is reflected in NSObject's description, thus returning a meaningful NSString

Item 10clone: The interface Cloneable has the same intention as the protocol NSCopying with one method: copyWithZone:. Read carefully what Bloch has to say on this matter as this is true in Objective-C as well and can lead to really hard to find bugs. 

Item 11comparable. This is a bit tricky as NSObject itself doesn't feature a compare-mtehod. NSString and NSNumber sure do (compare:) both returning a NSComparisonResult but this is not defined in a protocol/interface. On the other-hand there is an informal protocol in Foundation called NSComparisonMethods which are designed for scripting (NSSpecifierTest). 
So probably the best you can do is add compare: to your own classes (if needed) to keep in accordance with the framework. 
To sort members of a collection (array in my example) you can provide a selector like the compare:-method (with the same return type of NSComparisonResult and the same signature). As there is no concept like generics in Obj-C you are responsible that each member understands that selector otherwise doesNotRecognizeSelector: is called and you app will misbehave. 
Instead of a comparison object - the Java and C++ ("predicate") approach - you can also provide a function pointer to a comparison function with the signature of int (*)(id, id, void *) - which is rather a C-approach. See the documentation for NSArray and the like. 

Item 12: Restrict access to members. One of the hardest short-comings of Obj-C is access restriction. You can only set the scope of instance variables with the compiler directives @private@protected and @public. Methods are always public and always will be due to the dynamism of the runtime. The compiler doesn't have a chance to predict if a certain method exists at runtime (Categories can expand any object's methods) and even doesn't know what classes will be there and which one will be loaded during runtime. And there is nothing like private inheritance. 
As I said, all methods are public. You can hide them though by declaring them in the definition file as a Category and thus they are not visible in the class's interface (for the programmer). But they still can be called (and dumped). So yes, you can severely mess with the runtime. 
But the least you can do - and should! - is to declare all fields (instance variables) of a class at least @protected and strive to get to the level of @private where appropriate and provide public accessors for them (adhere to Key-Value-Coding here). This is good OOP and you should follow that road. Never let another class access your instance variables directly (even though there are still means to achieve that even though you declared them private - but that is a hack). 
Bloch mentions the attribute final. As Obj-C is based on C you can use const to the nearly same effect. 

Item 13: Unchangeability. Everything Bloch has to say on that subject you can translate exactly like this to Obj-C classes. Still you will never gain such a high level of security as he does with Java. If you read up to here you can see why. But nonetheless try to adhere to good programming practice. 

Item 14: Favor composition over inheritance. Let me just add one thing: Exclamation-mark! 

Item 15: Bloch demands to either develop a class for inheritance or make it completely impossible to inherit from that class. Read what he has to say because it is important but you will have to realize that in Obj-C there is nothing like a final class. So you can inherit from every class you want. Add this to the list of weaknesses of Obj-C. 

Item 16: Use interfaces (protocols) instead of abstract classes. If you are coming from C++ take this to heart, if you are an old school Obj-C programmer you might have done that all along. Abstract classes have their pros and you should know when to use them but their are not everyday remedy to all problems ;-) 

Item 17: Similar to item 16, but seldom found in the frameworks themselfs (AppKit and Foundation): Also use protocols as a type in the parameters of a method. This gives you more flexibility. 

Item 18: Obj-C does not have inner classes so this item can not be translated. 

The following Chapter 5 deals with substitues for pure C-constructs. All of them will work in Obj-C, of course, but nonetheless Bloch will show you a few advantages the way he implements those substitutes. Item 19, substitute structs with classes, does make sense, and item 20unions, just don't use them anymore. Item 21enums are still a relatively common thing and I keep using them as long as they are not saved to disk. Remember that their values (and hence meaning) can change in the next version so if you have to save any enum value take at least a NSConstantString instead. Still his idea of an enum class is interesting, but obsolete with Java 5. Item 22: function pointers can be evil, so try to follow his rules. 

Item 23: Check the validity of parameters - just the same in Obj-C. Don't forget to explicitly state in the documentation if and under what conditions this method might throw an exception. 

Item 24: The creation of defensive copies is a good idea. Actually the AppKit itself has a pitfall here: If you want to know what the user has entered in a TextField you can ask the current FieldEditor (and sometimes have to if you don't want to end editing). The FieldEditor is an instance of NSText and usually a window has only one of it that is shared among the TextFields - which is a clever approach. But if you want to get the content of what is edited you use 
- (NSString *)string 
and get back a pointer to an NSString which is a pointer to an internal String that changes when the text gets changed. You have to make a copy yourself if you are interested in maintaining the content, otherwise it is lost. 

Item 25 Design method signature carefully should be true for all programming languages ;-) 

Item 26: Method overloading is not supported in Obj-C. This is a relict of C. But you can certainly have different constructors - just name them differently. You usually use the pair alloc/init... so you are free to name them accordingly to what they expect - to give you an example here are just some init-methods from NSString: 
- (id)initWithString:(NSString *)aString 
- (id)initWithUTF8String:(const char *)bytes 
- (id)initWithContentsOfURL:(NSURL *)url encoding:(NSStringEncoding)enc error:(NSError **)error 
- (id)initWithCharacters:(const unichar *)characters length:(unsigned)length 
- (id)initWithBytes:(const void *)bytes length:(unsigned)length encoding:(NSStringEncoding)encoding
 
Just to give you an idea.... 

Item 27: This can not be stressed enough! It is good programming practice to return an empty collection instead of nil/null. Like Bloch states: It makes sense. You will see this used throughout the Cocoa framework. 

Item 28: Dito. Instead of JavaDoc you can use AppleDoc which is integrated with XCode, meaning macro scripts exists. You might also want to take a look at doxygen. You can copy and change Apple's scripts easily to produce doxygen-tags instead of AppleDoc-tags. 

Item 29: Scope of local variables. Since gcc 3.0 (some time ago) you can now declare variables anywhere in your method and hence follow Bloch's advice. 

Item 30: Know your framework. Again, this can't be stressed enough: Don't reprogram what others already did and tested for you in many extreme situations by millions of users. Cocoa is part of a RDE and you can gain a lot of time with it - but you have to know what the framework can do for you. The time spent to learn the framework is time saved well later on. A little bit off topic but it should be mentioned here: Don't program what you can download

Item 31: Float and double an inaccurate. Period. We all learned that and we all should act accordingly. In Obj-C to store big numbers you can use NSDecimalNumber

Item 32: Dito. You should not use Strings where not appropriate. 

Item 33: String concatenation is slow. Every-time you use NSString's 
- (NSString *)stringByAppendingString:(NSString *)aString 
a new instance of NSString is created. This is slow. Like Bloch writes, use NSMutableString - the counterpart to StringBuffer - instead. 

Item 34: Reference objects by their interface. To use an interface as a type can easily be done in Obj-C, too. So this item also gets a dito. You can write: 
NSObject *object; 
The only drawback in Obj-C is that collections don't share a common interface even though they have many methods in common. 

Item 35: Use Interfaces instead of Reflection. Obj-C is highly dynamic and a concept like reflection is kind of build in. You can load classes at runtime, make classes pose as others and the poor compiler didn't have a chance to know about this mess. This can be erroneous so you have to be careful. Nonetheless skip item 35 in Bloch's book. 

Item 36: Native methods. Based on C and thanks to the Obj-C++ compiler you can use both pure C functions and C++ classes mixed (to some great extend) in Obj-C. So there is nothing like Java's native methods with the overhead of switching in the JVM. You can freely use C functions and like always you are responsible for the results. 

Item 37: Optimization: Read this item twice - it is important to remember this; always. 

Item 38: Keep to the naming conventions. Take a look at Apple's

...to be continued soon...

posted on 2012-04-19 15:22  Hibernate4  阅读(427)  评论(0编辑  收藏  举报

导航