Effective Objective-C [上]

網上看到的 http://esoftmobile.com/2013/08/10/effective-objective-c/html

本文是針對《Effective Objective-C》一書的代碼解讀,筆者並無看過原書,只是經過閱讀該書的代碼,並結合相應的主題,來臆測做者可能要表達的內容並用本身的語言來描述出來。node

Chapter 1: Accustoming Yourself to Objective-C

Item 1: Familiarize Yourself with Objective-C's Roots

Item 2: Minimize Importing Headers in Headers

減小頭文件中引入(#import)文件的數量,大部分狀況下咱們應該在頭文件中用@class申明要引用的類,在實現文件中涉及到對該類的操做時才#importios

Item 3: Prefer Literal Syntax over the Equivalent Methods

儘量使用對象字面量的形式來建立或操做基礎對象(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;

Item 4: Prefer Typed Constants to Preprocessor #define

儘可能用類型化的常量來代替使用#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";

Item 5: Use Enumerations for States, Options, and Status Codes

使用枚舉來表示各類狀態,選項。其中申明枚舉類型推薦使用Apple的NS_ENUMNS_OPTIONSobjective-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;
}

Chapter 2: Objects, Messaging, and the Runtime

Item 6: Understand Properties

《Ry’s Objective-C Tutorial》# Propertiesapp

Item 7: Access Instance Variables Primarily Directly When Accessing Them Internally

Item 8: Understand Object Equality

在自定義類中,咱們能夠經過實現-(BOOL)isEqual:(id)object;-(NSUInteger)hash;兩個方法來對該類的對象進行比較。dom

Item 9: Use the Class Cluster Pattern to Hide Implementation Detail

必要時經過工廠模式來隱藏一些實現的細節。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

Item 10: Use Associated Objects to Attach Custom Data to Existing Classes

代碼內容與該主題看不出來關聯,先說主題吧,應該是必要時使用objc_setAssociatedObjectobjc_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;
}

Item 11: Understand the Role of objc_msgSend

該主題沒有對應代碼,詳見 《Objective-C Runtime Programming Guide》

Item 12: Understand Message Forwarding

必要時咱們能夠經過實現+ (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];
}

Item 13: Consider Method Swizzling to Debug Opaque Methods

咱們能夠經過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;時獲得的結果和你預期的相反。

Item 14: Understand What a Class Object Is

藉助於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

Chapter 3: Interface and API Design

Item 15: Use Prefix Names to Avoid Namespace Clashes

在類名(Class)、協議名(Protocal)、分類名(Category)等加上本身的前綴避免與其餘庫或代碼發生命名衝突。

Item 16: Have a Designated Initializer

初始化方法是一個類的入口,因此咱們須要精心的設計(其實每一個方法都得用心設計),我我的習慣初始化方法中通常不會超過兩個參數,儘可能讓初始化方法更簡單,同時咱們也須要照顧到繼承來的初始化方法:-(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;
}

Item 17: Implement the description Method

咱們能夠在本身的類中實現description方法,返回關於該對象關鍵信息,這樣在打印(Log)該對象時能夠看到更多信息,不然默認就是該對象的類名和地址。

@implementation EOCPerson
...
// Description method for EOCPerson
- (NSString*)description {
    return [NSString stringWithFormat:@"<%@: %p, \"%@ %@\">", 
            [self class], self, _firstName, _lastName];
}
...
@end

Item 18: Prefer Immutable Objects

不少狀況下咱們申明一個屬性只是爲了讓外部可以獲取一些信息(get),並不須要對這些信息做修改(set),因此這種狀況下最好不要讓外部可以修改,咱們能夠在申明該屬性時加上readonly
或者咱們還一能夠在實現文件中申明「私有」的成員變量,並開放一個方法來獲取該變量的一些信息。

Item 19: Use Clear and Consistent Naming

該部分應該講的Objective-C編碼規範,這裏推薦Apple的《Coding Guidelines for Cocoa》

Item 20: Prefix Private Method Names

該部分建議給「私有方法」(只在當前類的實現文件中使用的方法)加上前綴以便和其餘方法區分開,這裏建議的命名形式爲:- (void)_privateMethod;,即加上下槓符_

Item 21: Understand the Objective-C Error Model

在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,
};

Item 22: Understand the NSCopying Protocol

咱們知道大部分系統的類(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

咱們也能夠在該方法中控制是深拷貝仍是淺拷貝,區別就是是否將當前對象的全部信息(全部成員變量)賦給拷貝後的對象。

Chapter 4: Protocols and Categories

Item 23: Use Delegate and Data Source Protocols for Interobject Communication

《Ry’s Objective-C Tutorial》# Protocols

Item 24: Use Categories to Break Class Implementations into Manageable Segments

當咱們要實現一個功能豐富的類時,咱們可使用分類(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 25: Always Prefix Category Names on Third-Party Classes

這個感受與以前(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

Item 26: Avoid Properties in Categories

Objective-C分類中是不容許增長成員變量的(Instance variables may not be placed in categories),咱們能夠經過運行時函數objc_setAssociatedObjectobjc_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

Item 27: Use the Class-Continuation Category to Hide Implementation Detail

咱們能夠在實現文件中利用拓展(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

Item 28: Use a Protocol to Provide Anonymous Objects

咱們能夠經過協議來提供匿名對象來調用一些方法或獲取一些信息。

// 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;

Chapter 5: Memory Management

Item 29: Understand Reference Counting

《Ry’s Objective-C Tutorial》#Memory Management

Item 30: Use ARC to Make Reference Counting Easier

《Ry’s Objective-C Tutorial》#Memory Management

Item 31: Release References and Clean Up Observation State Only in dealloc

在ARC模式下,dealloc方法中通常只應該出現兩種操做:釋放非Cocoa對象和移除觀察者。

// Releasing CF objects and removing observer in `dealloc'
- (void)dealloc {
    CFRelease(coreFoundationObject);
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

Item 32: Beware of Memory Management with Exception-Safe Code

在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前必要的對象沒釋放問題。

Item 33: Use Weak References to Avoid Retain Cycles

Item 34: Use Autorelease Pool Blocks to Reduce High-Memory Waterline

// 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];
    }
}

Item 35: Use Zombies to Help Debug Memory-Management Problems

Item 36: Avoid Using retainCount

// Never do this
while ([object retainCount]) {
    [object release];
}
相關文章
相關標籤/搜索