Effective Objective-C [上]

来源:互联网 发布:java图形界面布局 编辑:程序博客网 时间:2024/04/27 18:55

Effective Objective-C [上]

2013-08-10

本文是针对《Effective Objective-C》一书的代码解读,笔者并没有看过原书,只是通过阅读该书的代码,并结合相应的主题,来臆测作者可能要表达的内容并用自己的语言来描述出来。

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

Item 3: Prefer Literal Syntax over the Equivalent Methods

尽可能使用对象字面量的形式来创建或操作基础对象(NSString、NSNumber、NSArray、NSDictionary等),这种形式不仅使用方便,代码看起来也更清晰。

NSString *someString = @"Effective Objective-C";NSNumber *someNumber = @1;NSNumber *intNumber = @10;NSNumber *floatNumber = @2.5fNSNumber *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定义的常量。

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

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》# Properties

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

必要时通过工厂模式来隐藏一些实现的细节。

@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 methodsMethod 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 checkingNSMutableDictionary *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 exceptionid 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.mstatic 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 infoNSFetchedResultsController *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 leakEOCSomeClass *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 @autoreleasepoolNSArray *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 thiswhile ([object retainCount]) {    [object release];}

Posted by TracyYih - 2013-08-10
如需转载,请注明: 本文来自 Esoft Mobile

原文地址:http://esoftmobile.com/2013/08/10/effective-objective-c/

0 0
原创粉丝点击