網上看到的 http://esoftmobile.com/2013/08/10/effective-objective-c/html
本文是針對《Effective Objective-C》一書的代碼解讀,筆者並無看過原書,只是經過閱讀該書的代碼,並結合相應的主題,來臆測做者可能要表達的內容並用本身的語言來描述出來。node
減小頭文件中引入(#import
)文件的數量,大部分狀況下咱們應該在頭文件中用@class
申明要引用的類,在實現文件中涉及到對該類的操做時才#import
。ios
儘量使用對象字面量的形式來建立或操做基礎對象(NSString、NSNumber、NSArray、NSDictionary等),這種形式不只使用方便,代碼看起來也更清晰。git
NSString *someString = @"Effective Objective-C"; NSNumber *someNumber = @1; NSNumber *intNumber = @10; NSNumber *floatNumber = @2.5f NSNumber *doubleNumber = @3.14159; NSNumber *boolNumber = @YES; NSNumber *charNumber = @'a'; NSArray *array = @[ object1, object2, object3 ]; NSDictionary *dict = @{ @"name": @"TracyYih", @"blog": @"http://esoftmobile.com" }; NSMutableArray *mutableArray = [@[ object1, object2 ] mutableCopy]; mutableArray[2] = object3; NSMutableDictionary *mutableDict = [@{ @"name": @"TracyYih" } mutableCopy]; mutableDict[@"age"] = @25;
儘可能用類型化的常量來代替使用#define
定義的常量。github
//In head file. extern const NSTimeInterval EOCAnimatedViewAnimationDuration; extern NSString *const EOCLoginManagerDidLoginNotification; //In implmentation file. const NSTimeInterval EOCAnimatedViewAnimationDuration = 0.3; NSString *const EOCLoginManagerDidLoginNotification = @"EOCLoginManagerDidLoginNotification";
使用枚舉來表示各類狀態,選項。其中申明枚舉類型推薦使用Apple的NS_ENUM
和NS_OPTIONS
。objective-c
typedef NS_ENUM(NSUInteger, EOCConnectionState) { EOCConnectionStateDisconnected, EOCConnectionStateConnecting, EOCConnectionStateConnected, }; switch (_currentState) { case EOCConnectionStateDisconnected: break; case EOCConnectionStateConnecting: break; case EOCConnectionStateConnected: break; }
使用枚舉表示狀態使代碼的可讀性大大加強,對比下面的代碼你就知道了:瀏覽器
switch (_currentState) { case 0: break; case 1: break; case 2: break; }
《Ry’s Objective-C Tutorial》# Propertiesapp
在自定義類中,咱們能夠經過實現-(BOOL)isEqual:(id)object;
和-(NSUInteger)hash;
兩個方法來對該類的對象進行比較。dom
必要時經過工廠模式來隱藏一些實現的細節。ide
@implementation Employee + (Employee *)employeeWithType:(EmployeeType)type { switch (type) { case EmployeeTypeDeveloper: return [[EmployeeDeveloper alloc] init]; break; case EmployeeTypeDesigner: return [[EmployeeDesigner alloc] init]; break; case EmployeeTypeFinance: return [[EmployeeFinance alloc] init]; break; } } @end
代碼內容與該主題看不出來關聯,先說主題吧,應該是必要時使用objc_setAssociatedObject
, objc_getAssociatedObject
兩個方法將一些自定義的數據與已有的類相關聯,一般在分類(Category)中添加屬性時會用到,後面會涉及到。
代碼部分是能夠經過函數指針來延遲函數的綁定。
void printHello() { printf("Hello, world!\n"); } void printGoodbye() { printf("Goodbye, world!\n"); } void doTheThing(int type) { void (*fnc)(); //here. if (type == 0) { fnc = printHello; } else { fnc = printGoodbye; } fnc(); return 0; }
該主題沒有對應代碼,詳見 《Objective-C Runtime Programming Guide》。
必要時咱們能夠經過實現+ (BOOL)resolveClassMethod:(SEL)sel
和+ (BOOL)resolveInstanceMethod:(SEL)sel
方法來動態的爲選擇器(selector)提供對應的實現(implementation)。
+ (BOOL)resolveInstanceMethod:(SEL)selector { NSString *selectorString = NSStringFromSelector(selector); if (/* selector is from a @dynamic property */) { if ([selectorString hasPrefix:@"set"]) { class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@"); } else { class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:"); } return YES; } return [super resolveInstanceMethod:selector]; }
咱們能夠經過runtime提供的method_exchangeImplementations
方法來交換兩個方法的實現。
// Exchanging methods Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString)); Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString)); method_exchangeImplementations(originalMethod, swappedMethod);
這裏例子比較有意思,實現了以上代碼後,你再使用-(NSString *)lowercaseString;
和-(NSString *)uppercaseString;
時獲得的結果和你預期的相反。
藉助於Objective-C強大的runtime系統,咱們能夠在代碼中判斷一個對象是屬於什麼類型。isMemberOfClass
:判斷一個對象是否爲某類的實例。isKindOfClass
:判斷一個對象是否爲某類或該類的子類的實例。
// Class hierarchy checking NSMutableDictionary *dict = [NSMutableDictionary new]; [dict isMemberOfClass:[NSDictionary class]]; ///< NO [dict isMemberOfClass:[NSMutableDictionary class]]; ///< YES [dict isKindOfClass:[NSDictionary class]]; ///< YES [dict isKindOfClass:[NSArray class]]; ///< NO
在類名(Class)、協議名(Protocal)、分類名(Category)等加上本身的前綴避免與其餘庫或代碼發生命名衝突。
初始化方法是一個類的入口,因此咱們須要精心的設計(其實每一個方法都得用心設計),我我的習慣初始化方法中通常不會超過兩個參數,儘可能讓初始化方法更簡單,同時咱們也須要照顧到繼承來的初始化方法:-(id)init;
和 -(id)initWithCode:
。
// Designated initialiser - (id)initWithWidth:(float)width andHeight:(float)height { if ((self = [super init])) { _width = width; _height = height; } return self; } // Super-class’s designated initialiser - (id)init { return [self initWithWidth:5.0f andHeight:10.0f]; } // Initialiser from NSCoding - (id)initWithCoder:(NSCoder*)decoder { // Call through to super’s designated initialiser if ((self = [super init])) { _width = [decoder decodeFloatForKey:@"width"]; _height = [decoder decodeFloatForKey:@"height"]; } return self; }
咱們能夠在本身的類中實現description
方法,返回關於該對象關鍵信息,這樣在打印(Log)該對象時能夠看到更多信息,不然默認就是該對象的類名和地址。
@implementation EOCPerson ... // Description method for EOCPerson - (NSString*)description { return [NSString stringWithFormat:@"<%@: %p, \"%@ %@\">", [self class], self, _firstName, _lastName]; } ... @end
不少狀況下咱們申明一個屬性只是爲了讓外部可以獲取一些信息(get),並不須要對這些信息做修改(set),因此這種狀況下最好不要讓外部可以修改,咱們能夠在申明該屬性時加上readonly
。
或者咱們還一能夠在實現文件中申明「私有」的成員變量,並開放一個方法來獲取該變量的一些信息。
該部分應該講的Objective-C編碼規範,這裏推薦Apple的《Coding Guidelines for Cocoa》。
該部分建議給「私有方法」(只在當前類的實現文件中使用的方法)加上前綴以便和其餘方法區分開,這裏建議的命名形式爲:- (void)_privateMethod;
,即加上下槓符_
。
在Objective-C中,錯誤處理能夠有兩種形式:NSException 和 NSError 。
// Throwing exception id someResource = …; if ( /* check for error */ ) { @throw [NSException exceptionWithName:@"ExceptionName" reason:@"There was an error" userInfo:nil]; } [someResource doSomething]; [someResource release];
// Returning the error - (BOOL)doSomethingError:(NSError**)error { // Do something NSError *returnError = nil; if (/* there was an error */) { if (error) { *error = [NSError errorWithDomain:domain code:code userInfo:userInfo]; } return YES; } else { return NO; } }
其實在Objective-C中後一種更常見,咱們能夠結合前面提到的使用枚舉類表示一些錯誤碼類型。
typedef NS_ENUM(NSUInteger, EOCError) { EOCErrorUnknown = −1, EOCErrorInternalInconsistency = 100, EOCErrorGeneralFault = 105, EOCErrorBadInput = 500, };
咱們知道大部分系統的類(UI & NS)能夠調用-(id)copy;
方法來得到該對象的一份拷貝,若是是自定義的類咱們也想使用該方法,必須遵循NSCopying
協議,並實現-(id)copyWithZone:(NSZone *)zone);
方法。
//Support the NSCopying protocol. @interface EOCPerson : NSObject <NSCopying> @end @implementation EOCPerson // NSCopying implementation - (id)copyWithZone:(NSZone*)zone { Person *copy = [[[self class] allocWithZone:zone] initWithFirstName:_firstName andLastName:_lastName]; return copy; } @end
咱們也能夠在該方法中控制是深拷貝仍是淺拷貝,區別就是是否將當前對象的全部信息(全部成員變量)賦給拷貝後的對象。
《Ry’s Objective-C Tutorial》# Protocols
當咱們要實現一個功能豐富的類時,咱們可使用分類(Category)將該類分割成相對獨立一些的塊,這樣代碼結構會比全部東西都放在一塊兒實現要清晰的多。
//RenderObject.h @class CXMLNode; @interface RenderObject : NSObject @property (nonatomic, copy, readonly) NSString *name; @property (nonatomic, weak, readonly) CXMLNode *node; @property (nonatomic, strong, readonly) UIView *view; @property (nonatomic, strong, readonly) NSDictionary *style; - (instancetype)initWithNode:(CXMLNode *)node style:(NSDictionary *)style; //... @end //RenderObject+RenderTree.h @interface RenderObject (RenderTree) @property (nonatomic, weak, readonly) RenderObject *parent; @property (nonatomic, weak, readonly) RenderObject *firstChild; @property (nonatomic, weak, readonly) RenderObject *lastChild; @property (nonatomic, weak, readonly) RenderObject *nextSibling; @property (nonatomic, weak, readonly) RenderObject *previousSibling; @end //RenderObject+Layout.h @interface RenderObject (Layout) - (void)layout; - (void)loadView; - (void)paint; //... @end
以上代碼並不是該書所附代碼,爲筆者開發的一商業瀏覽器項目代碼。
這個感受與以前(Item 15)內容類似,給本身建立的全部分類(不論是基於Cocoa類仍是第三方類)加上本身的前綴。
// Namespacing the category @interface NSString (ABC_HTTP) // Encode a string with URL encoding - (NSString*)abc_urlEncodedString; // Decode a URL encoded string - (NSString*)abc_urlDecodedString; @end
Objective-C分類中是不容許增長成員變量的(Instance variables may not be placed in categories),咱們能夠經過運行時函數objc_setAssociatedObject
和 objc_getAssociatedObject
來讓分類支持保存和獲取一些數據,從而支持屬性。
//EOCPerson+FriendShip.h @interface EOCPerson (FriendShip) @property (nonatomic, strong) NSArray *friends; @end //EOCPerson+FriendShip.m static const char* kFriendsPropertyKey = "kFriendsPropertyKey"; @implementation EOCPerson (Friendship) - (NSArray*)friends { return objc_getAssociatedObject(self, kFriendsPropertyKey); } - (void)setFriends:(NSArray*)friends { objc_setAssociatedObject(self, kFriendsPropertyKey, friends, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end
咱們能夠在實現文件中利用拓展(Class Extension)將不須要外界瞭解的成員變量移到拓展中,也就是全部咱們應該在頭文件中申明爲@private
的成員變量均可以移到拓展中,這樣可以保證頭文件只出現外界關心的東西。
//EOCClass.h @class EOCSuperSecretClass @interface EOCClass : NSObject { @private EOCSuperSecretClass *_secretInstance; } @end
其中_secretInstance
既不會被子類繼承,也不會在類外被訪問,就不須要留在頭文件中了。
//EOCClass.h @interface EOCClass : NSObject @end // EOCClass.m #import "EOCClass.h" #import "EOCSuperSecretClass.h" @interface EOCClass () { EOCSuperSecretClass *_secretInstance; } @end @implementation EOCClass // Methods here @end
在新版本編譯器中,實現(@implmentation
)中也支持申明成員變量了,因此咱們還能夠這樣寫:
@implementation EOCClass { EOCSuperSecretClass *_secretInstance; } // Methods here @end
咱們能夠經過協議來提供匿名對象來調用一些方法或獲取一些信息。
// Database connection protocol @protocol EOCDatabaseConnection - (void)connect; - (void)disconnect; - (BOOL)isConnected; - (NSArray*)performQuery:(NSString*)query; @end // Database manager class #import <Foundation/Foundation.h> @protocol EOCDatabaseConnection; @interface EOCDatabaseManager : NSObject + (id)sharedInstance; - (id<EOCDatabaseConnection>)connectionWithIdentifier:(NSString*)identifier; @end
這種用法在CoreData中也能夠遇到:
// Fetched results controller with section info NSFetchedResultsController *controller = /* some controller */; NSUInteger section = /* section index to query */; NSArray *sections = controller.sections; id <NSFetchedResultsSectionInfo> sectionInfo = sections[section]; NSUInteger numberOfObjects = sectionInfo.numberOfObjects;
《Ry’s Objective-C Tutorial》#Memory Management
《Ry’s Objective-C Tutorial》#Memory Management
在ARC模式下,dealloc方法中通常只應該出現兩種操做:釋放非Cocoa對象和移除觀察者。
// Releasing CF objects and removing observer in `dealloc' - (void)dealloc { CFRelease(coreFoundationObject); [[NSNotificationCenter defaultCenter] removeObserver:self]; }
在MRR(Manual Retain Release)下,須要特別留意異常狀況下的內存管理問題。
// @try/@catch block under manual reference counting @try { EOCSomeClass *object = [[EOCSomeClass alloc] init]; [object doSomethingThatMayThrow]; [object release]; } @catch (...) { NSLog(@"Whoops, there was an error. Oh well, it wasn’t important."); }
// Fixing the potential leak EOCSomeClass *object; @try { object = [[EOCSomeClass alloc] init]; [object doSomethingThatMayThrow]; } @catch (...) { NSLog(@"Whoops, there was an error. Oh well, it wasn’t important."); } @finally { [object release]; }
其實一樣須要注意的還有在switch-case或if-else條件下,避免return前必要的對象沒釋放問題。
// Reducing high memory waterline with appropriately places @autoreleasepool NSArray *databaseRecords = …; NSMutableArray *people = [NSMutableArray new]; for (NSDictionary *record in databaseRecords) { @autoreleasepool { EOCPerson *person = [[EOCPerson alloc] initWithRecord:record]; [people addObject:person]; } }
// Never do this while ([object retainCount]) { [object release]; }