Effective Objective-C [上]
网上看到的 http://esoftmobile.com/2013/08/10/effective-objective-c/
本文是针对《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.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
定义的常量。
//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
。
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_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;
}
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];
}
Effective Objective-C [上]的更多相关文章
- 【硅谷问道】Chris Lattner 访谈录(上)
[硅谷问道]Chris Lattner 访谈录(上) 话题 Chris Lattner 是谁? Xcode 的编译器 LLVM 背后有怎样的故事? Swift 诞生的前世今生,封闭的苹果为何要拥抱开源 ...
- iOS 学习资源
这份学习资料是为 iOS 初学者所准备的, 旨在帮助 iOS 初学者们快速找到适合自己的学习资料, 节省他们搜索资料的时间, 使他们更好的规划好自己的 iOS 学习路线, 更快的入门, 更准确的定位的 ...
- iOS学习资料整理
视频教程(英文) 视频 简介 Developing iOS 7 Apps for iPhone and iPad 斯坦福开放教程之一, 课程主要讲解了一些 iOS 开发工具和 API 以及 iOS S ...
- iOS 学习
iOS 学习资料 (适合初学者) 本文资料来源于GitHub 一.视频教程(英文) Developing iOS 7 Apps for iPhone and iPad斯坦福开放教程之一, 课程主要讲解 ...
- iOS 学习资料汇总
(适合初学者入门) 本文资料来源于GitHub 一.视频教程(英文) Developing iOS 7 Apps for iPhone and iPad斯坦福开放教程之一, 课程主要讲解了一些 iOS ...
- ios书籍推荐
1.Objective-C Programming 内容不多, 却都是精华, 有了一点 C 语言基础可以快速阅读此书, 大概一天时间就可以看完, 看完后对 iOS 开发能够有个基本的印象. 2.iO ...
- iOS Learning
转载自:http://www.cocoachina.com/ios/20150111/10894.html iOS 学习资料整理 2015-01-11 20:20 编辑: suiling 分类:iOS ...
- BlocksKit的使用
一.引言 众所周知Block已被广泛用于iOS编程.它们通常被用作可并发执行的逻辑单元的封装,或者作为事件触发的回调.Block比传统回调函数有2点优势: 允许在调用点上下文书写执行逻辑,不用分离函数 ...
- 【iOS】关联属性存取数据
有时候我们需要在现有的类存放一些额外的信息,通常的做法是继承一个子类,然后定义新增加的属性,然而如果我们为每个需要的类都添加一个类显得太麻烦了,objc提供了一个关联属性的特性,可以给一个对象关联一个 ...
- malloc free 和new delete区别
从网上看的学习之 1. malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符,与"+“.”-“.”*“.”/“有一样的地位. 2. new/delete是 ...
随机推荐
- 2018.7.27 wireless charger TX evaluation kit based on STWBC-EP
1 introduced 我们需要设计一个无线充电方案: 功能需求:通用的无线充电平台 参数要求:8-10W step1: 找寻资料 http://www.ti.com/sitesearch/doc ...
- 在OpenCV2.2后的版本中没有CvvImage类的解决方法(及出现错误:IntelliSense: 未定义标识符 "CvvImage" )
首先在你的解决方案资源管理器中的头文件和源文件下分别添加 CvvImage.cpp 如下图: view类头上加个#include "CvvImage.h" 头文件,应该就可以解决 ...
- Number Sequence (KMP的应用)
个人心得:朴素代码绝对超时,所以要用到KMP算法,特意了解了,还是比较抽象,要多体会 Given two sequences of numbers : a11, a22, ...... , aNN, ...
- python导入csv文件时,出现SyntaxError
背景 np.loadtxt()用于从文本加载数据. 文本文件中的每一行必须含有相同的数据. *** loadtxt(fname, dtype=<class 'float'>, commen ...
- 运行flask程序
Command Line Interface Installing Flask installs the flask script, a Click command line interface, i ...
- C# Chat曲线图,在发布之后出现错误 Invalid temp directory in chart handler configuration c:\TempImageFiles\
First error message: Invalid temp directory in chart handler configuration c:\TempImageFiles\ Soluti ...
- 批量删除osd的shell脚本
cluster环境: # cat /etc/redhat-release CentOS Linux release 7.3.1611 (Core) # ceph -v ceph version 12. ...
- 未在本地计算机上注册 Microsoft.ACE.OLEDB.12.0 提供程序
Visual Studio 8使用了Access数据库,provider选择了ACE.OLEDB,但是运行时出现了错误,提示未在本地计算机上注册"Microsoft.ACE.OLEDB.12 ...
- xcode修改横屏
1.修改工程属性 2.修改info.plist文件
- [摘]Android逆向分析常用网站
androidterm: Android Terminal Emulator http://code.google.com/p/androidterm/ droidbox: Andro ...