本文是针对《Effective Objective-C》一书的代码解读,笔者并无看过原书,只是经过阅读该书的代码,并结合相应的主题,来臆测做者可能要表达的内容并用本身的语言来描述出来。html
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
申明要引用的类,在实现文件中涉及到对该类的操做时才#import
。node
Item 3: Prefer Literal Syntax over the Equivalent Methods
尽量使用对象字面量的形式来建立或操做基础对象(NSString、NSNumber、NSArray、NSDictionary等),这种形式不只使用方便,代码看起来也更清晰。ios
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
定义的常量。git
//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_ENUM
和NS_OPTIONS
。github
typedef NS_ENUM(NSUInteger, EOCConnectionState) { EOCConnectionStateDisconnected, EOCConnectionStateConnecting, EOCConnectionStateConnected, }; switch (_currentState) { case EOCConnectionStateDisconnected: break; case EOCConnectionStateConnecting: break; case EOCConnectionStateConnected: break; }
使用枚举表示状态使代码的可读性大大加强,对比下面的代码你就知道了:objective-c
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》# Propertiesapi
Item 7: Access Instance Variables Primarily Directly When Accessing Them Internally
Item 8: Understand Object Equality
在自定义类中,咱们能够经过实现-(BOOL)isEqual:(id)object;
和-(NSUInteger)hash;
两个方法来对该类的对象进行比较。浏览器
Item 9: Use the Class Cluster Pattern to Hide Implementation Detail
必要时经过工厂模式来隐藏一些实现的细节。app
@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_setAssociatedObject
,objc_getAssociatedObject
两个方法将一些自定义的数据与已有的类相关联,一般在分类(Category)中添加属性时会用到,后面会涉及到。dom
代码部分是能够经过函数指针来延迟函数的绑定。
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_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
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]; }