第1章 熟悉objective-c 1
第1条:了解objective-c语言的起源 1
EZ_1:Objective-C的动态特性
Objective-C具有相当多的动态特性,基本的,也是经常被提到和用到的有动态类型(Dynamic typing),动态绑定(Dynamic binding)和动态加载(Dynamic loading)。
1、动态类型
即运行时再决定对象的类型。这类动态特性在日常应用中非常常见,简单说就是id类型。id类型即通用的对象类,任何对象都可以被id指针所指,而在实际使用中,往往使用introspection来确定该对象的实际所属类:
id obj = someInstance;
if ([obj isKindOfClass:someClass])
{
someClass *classSpecifiedInstance = (someClass *)obj;
// Do Something to classSpecifiedInstance which now is an instance of someClass
//...
}
动态类型识别常用方法:
-(BOOL)isKindOfClass:classObj 是否是classObj类或其子类
-(BOOL)isMemberOfClass:classObj是否是classObj的实例
-(BOOL)respondsTosSelector:selector 类中是否有这个方法
NSClassFromString(NSString*);由字符串得到类对象
NSStringFromClass([类名 Class]);由类名得到字符串
NSSelectorFromString(NSString*);根据方法名得到方法标识
(NSString*)NSStringFromSelector(SEL);得到SEL类型的方法名
2、动态绑定
基于动态类型,在某个实例对象被确定后,其类型便被确定了。该对象对应的属性和响应的消息也被完全确定,这就是动态绑定。在继续之前,需要明确Objective-C中消息的概念。由于OC的动态特性,在OC中其实很少提及“函数”的概念,传统的函数一般在编译时就已经把参数信息和函数实现打包到编译后的源码中了,而在OC中最常使用的是消息机制。调用一个实例的方法,所做的是向该实例的指针发送消息,实例在收到消息后,从自身的实现中寻找响应这条消息的方法。
动态绑定所做的,即是在实例所属类确定后,将某些属性和相应的方法绑定到实例上。这里所指的属性和方法当然包括了原来没有在类中实现的,而是在运行时才需要的新加入的实现。在Cocoa层,我们一般向一个NSObject对象发送-respondsToSelector:或者-instancesRespondToSelector:等来确定对象是否可以对某个SEL做出响应,而在OC消息转发机制被触发之前,对应的类的+resolveClassMethod:和+resolveInstanceMethod:将会被调用,在此时有机会动态地向类或者实例添加新的方法,也即类的实现是可以动态绑定的。一个例子:
void dynamicMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
//该方法在OC消息转发生效前被调用
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
//向[self class]中新加入返回为void的实现,SEL名字为aSEL,实现的具体内容为dynamicMethodIMP class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, “v@:”);
return YES;
}
return [super resolveInstanceMethod:aSel];
}
当然也可以在任意需要的地方调用class_addMethod或者method_setImplementation(前者添加实现,后者替换实现),来完成动态绑定的需求。
3、动态加载
根据需求加载所需要的资源,这点很容易理解,对于iOS开发来说,基本就是根据不同的机型做适配。最经典的例子就是在Retina设备上加载@2x的图片,而在老一些的普通屏设备上加载原图。随着Retina iPad的推出,和之后可能的Retina Mac的出现,这个特性相信会被越来越多地使用。
深入运行时特性
基本的动态特性在常规的Cocoa开发中非常常用,特别是动态类型和动态绑定。由于Cocoa程序大量地使用Protocol-Delegate的设计模式,因此绝大部分的delegate指针类型必须是id,以满足运行时delegate的动态替换(在Java里这个设计模式被叫做Strategy?不是很懂Java,求纠正)。而Objective-C还有一些高级或者说更底层的运行时特性,在一般的Cocoa开发中较为少见,基本被运用与编写OC和其他语言的接口上。但是如果有所了解并使用得当的话,在Cocoa开发中往往可以轻易解决一些棘手问题。
这类运行时特性大多由/usr/lib/libobjc.A.dylib这个动态库提供,里面包括了对于类、实例成员、成员方法和消息发送的很多API,包括获取类实例变量列表,替换类中的方法,为类成员添加变量,动态改变方法实现等,十分强大。完整的API列表和手册可以在这里找到。虽然文档开头表明是对于Mac OS X Objective-C 2.0适用,但是由于这些是OC的底层方法,因此对于iOS开发来说也是完全相同的。
一个简单的例子,比如在开发Universal应用或者游戏时,如果使用IB构建了大量的自定义的UI,那么在由iPhone版转向iPad版的过程中所面临的一个重要问题就是如何从不同的nib中加载界面。在iOS5之前,所有的UIViewController在使用默认的界面加载时(init或者initWithNibName:bundle:),都会走-loadNibNamed:owner:options:。而因为我们无法拿到-loadNibNamed:owner:options的实现,因此对其重载是比较困难而且存在风险的。因此在做iPad版本的nib时,一个简单的办法是将所有的nib的命名方式统一,然后使用自己实现的新的类似-loadNibNamed:owner:options的方法将原方法替换掉,同时保证非iPad的设备还走原来的loadNibNamed:owner:options方法。使用OC运行时特性可以较简单地完成这一任务。
代码如下,在程序运行时调用+swizze,交换自己实现的loadPadNibNamed:owner:options和系统的loadNibNamed:owner:options,之后所有的loadNibNamed:owner:options消息都将会发为loadPadNibNamed:owner:options,由自己的代码进行处理。
+(BOOL)swizze {
Method oldMethod = class_getInstanceMethod(self, @selector(loadNibNamed:owner:options:));
if (!oldMethod) {
return NO;
}
Method newMethod = class_getInstanceMethod(self, @selector(loadPadNibNamed:owner:options:));
if (!newMethod) {
return NO;
}
method_exchangeImplementations(oldMethod, newMethod);
return YES;
}
loadPadNibNamed:owner:options的实现如下,注意在其中的loadPadNibNamed:owner:options由于之前已经进行了交换,因此实际会发送为系统的loadNibNamed:owner:options。以此完成了对不同资源的加载。
-(NSArray *)loadPadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options {
NSString *newName = [name stringByReplacingOccurrencesOfString:@"@pad" withString:@""];
newName = [newName stringByAppendingFormat:@"@pad"];
//判断是否存在
NSFileManager *fm = [NSFileManager defaultManager];
NSString* filepath = [[NSBundle mainBundle] pathForResource:newName ofType:@”nib”];
//这里调用的loadPadNibNamed:owner:options:实际为为交换后的方法,即loadNibNamed:owner:options:
if ([fm fileExistsAtPath:filepath]) {
return [self loadPadNibNamed:newName owner:owner options:options];
} else {
return [self loadPadNibNamed:name owner:owner options:options];
}
}
当然这只是一个简单的例子,而且这个功能也可以通过别的方法来实现。比如添加UIViewController的类别来重载init,但是这样的重载会比较危险,因为你UIViewController的实现你无法完全知道,因此可能会由于重载导致某些本来应有的init代码没有覆盖,从而出现不可预测的错误。当然在上面这个例子中重载VC的init的话没有什么问题(因为对于VC,init的方法会被自动转发为loadNibNamed:owner:options,因此init方法并没有做其他更复杂的事情,因此只要初始化VC时使用的都是init的话就没有问题)。但是并不能保证这样的重载对于其他类也是可行的。因此对于实现未知的系统方法,使用上面的运行时交换方法会是一个不错的选择~
第2条:在类的头文件中尽量少引入其他头文件 4
除非确有必要,否则不要引入头文件。一般来说,应在某个类的头文件中使用向前声明来提及别的类,并在实现文件中引入那些类的头文件。这样做可以尽量1、降低类之间的耦合(coupling),2、降低编译时间,3、避免循环引用的错误。
有时无法使用向前声明:
1、比如要声明某个类遵循一项协议。这种情况下,尽量把“该类遵循某协议”的这条声明移至“class-continuation分类”中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入。
2、继承父类,必须引用头文件。
第3条:多用字面量语法,少用与之等价的方法 7
1、增强易读性;
2、提高安全性,用字面量语法创建数组或字典时,若值中有nil,则会抛出异常。因此,务必确保值里不含nil。
3、字面量语法有个小小的限制,就是除了字符串以外,所创建出来的对象必须属于Foundation框架才行。如果自定义了这些类的子类,则无法用字面量语法创建其对象。要想创建自定义子类的实例,必须采用“非字面量语法”(nonliteral syntax);
4、使用字面量语法创建出来的字符串、数组、字典对象都是不可变的(immutable)。若想要可变版本的对象,则需复制一份: NSMutableArray *mutable = [@[@1, @2, @3, @4, @5]mutableCopy];
第4条:多用类型常量,少用#define预处理指令 11
1、不要用预处理指令定义常量。这样定义出来的常量不含类型信息,编译器只是会在编译前据此执行查找与替换操作。即使有人重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一致。
2、只在编译单元内可见的常量(即目标文件范围的常量,m文件产量,类范围内的常量)
命名:前缀一般用k
demo:
// EOCAnimatedView.h #import
@interface EOCAnimatedView : UIView
- (void)animate;
@end
// EOCAnimatedView.m
#import “EOCAnimatedView.h"
static const NSTimeInterval kAnimationDuration = 0.3;
@implementation EOCAnimatedView
- (void)animate {
[UIViewanimateWithDuration:kAnimationDuration animations:^(){
}
@end
// Perform animations }];
变量一定要同时用 static 与 const 来声明。如果试图修改由 const 修饰符所声明的变量, 那么编译器就会报错。在本例中,我们正是希望这样:因为动画播放时长为定值,所以不 应修改。而 static 修饰符则意味着该变量仅在定义此变量的编译单元中可见。编译器每收 到一个编译单元,就会输出一份“目标文件”(object file)。在 Objective-C 的语境下,“编译 单元”一词通常指每个类的实现文件(以 .m 为后缀名)。因此,在上述范例代码中声明的 kAnimationDuration 变量,其作用域仅限于由 EOCAnimatedView.m 所生成的目标文件中。假 如声明此变量时不加 static,则编译器会为它创建一个“外部符号”(external symbol)。此时 若是另一个编译单元中也声明了同名变量,那么编译器就抛出一条错误消息:
duplicate symbol _kAnimationDuration in: EOCAnimatedView.o EOCOtherView.o
实际上,如果一个变量既声明为 static,又声明为 const,那么编译器根本不会创建符号, 而是会像 #define 预处理指令一样,把所有遇到的变量都替换为常值。不过还是要记住:用这 种方式定义的常量带有类型信息。
3、全局常亮
命名:为避免名称冲突,最好是用与之相关的类名做前缀。系统框架中一般都 这样做。例如 UIKit 就按照这种方式来声明用作通知名称的全局常量。其中有类似 UIApplicatio nDidEnterBackgroundNotification 与 UIApplicationWillEnterForegroundNotification 这样的常量名。
demo:
// EOCLoginManager.h
#import
extern NSString *const EOCLoginManagerDidLoginNotification;
@interface EOCLoginManager : NSObject - (void)login;
@end
// EOCLoginManager.m #import "EOCLoginManager.h"
NSString *const EOCLoginManagerDidLoginNotification = @"EOCLoginManagerDidLoginNotification";
@implementation EOCLoginManager
- (void)login {
// Perform login asynchronously, then call 'p_didLogin'. }
- (void)p_didLogin { [[NSNotificationCenter defaultCenter]
postNotificationName:EOCLoginManagerDidLoginNotification object:nil];
}@end
4、注意点
- const
const最好理解,修饰的东西不能被修改 指针类型根据位置的不同可以理解成3种情况:
I 常量指针 // 初始化之后不能赋值,指向的对象可以是任意对象,对象可变。
NSString * const pt1;
II 指向常量的指针 // 初始化之后可以赋值,即指向别的常量,指针本身的值可以修改,指向的值不能修改
const NSString * pt2;
III 指向常量的常量指针
const NSString * const pt3;
- extern
等同于c,全局变量的定义,
//x .h 声明
extern const NSString * AA;
//x .m 定义
const NSString * AA = @"abc";
// 调用
#import "x.h"
或者再次申明
extern const NSString * AA;
- static
等同于c,将变量的作用域限定于本文件?
不同于java C++里面的类变量,oc没有类变量
- 结论
一
static
// static变量属于本类,不同的类对应的是不同的对象
// static变量同一个类所有对象中共享,只初始化一次
二
const
// static const变量同static的结论I,只是不能修改了,但是还是不同的对象
// extern const变量只有一个对象,标准的常量的定义方法
// extern的意思就是这个变量已经定义了,你只负责用就行了
三
static const NSString * const ChatWindow_STR_TITLE_INDEX[] = {
@"msg_type_all",
@"msg_type_area",
@"msg_type_PM",
@"msg_type_team",
@"guild_title",
@"msg_type_system",
@"msg_type_world"
};
我在.h文件里定义一个static变量,
I .h初始化
在不同的.m里调用都有值,这些值是否同一个对象?
【不同的对象】
II .m里初始化(or赋值)
初始化的值只在.m文件里有效果,即等同于c,将变量的作用域限定于本文件
其他.m文件调用的值都没有赋值。
【不同的对象】
第5条:用枚举表示状态、选项、状态码 14
http://justsee.iteye.com/blog/2164025
① 如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将各选项值定义为2的幂,以便通过按位或操作将其组合起来。
② 用 NS_ENUM 和 NS_OPTIONS 宏来定义枚举类型,并指明其底层数据类型。这样做可以确保枚举是用开发者所选的底层数据类型实现出来的。而不会采用编译器所选的类型。
③ 在处理枚举类型的switch 语句中不要实现default分支,这样加入新的枚举类型后,编译器就会提示开发者,switch语句并未处理所有的枚举。
④ 可以指明用何种“底层数据类型”来保存枚举类型的变量。这样做的好处是,可以向前声明枚举变量了。若不指定底层数据类型,则无法向前声明枚举类型。因为编译器不清楚数据类型的大小。所以在用到此枚举类型时,也就不知道究竟该给变量分配多少空间了。
第2章 对象、消息、运行期 21
第6条:理解“属性”这一概念 21
1. Java 或 C++ 中,使用publish,private等关键字来声明变量的作用域。
第7条:在对象内部尽量直接访问实例变量 28
1. 建议在读取实例变量的时候采用直接访问的形式,而在设置实例变量的时候通过属性来做。
第8条:理解“对象等同性”这一概念 30
1. 若想检测对象的等同性,请提供“isEqual”与“hash”方法。
- - (BOOL)isEqual:(id)object
- {
- if(self == object) return YES;
- if([self class] != [object class] ) return NO;
- ConcreteId *otherId = (ConcreteId*)object;
- if(![someProperty isEqual:otherId.someProperty])
- return NO;
- // ……… 比较其他属性值
- return YES;
- }
- - (BOOL)isEqual:(id)object
- {
- if([self class] ==[ object class ])
- {
- return [self isEqualToPerson:(EOCPerson*)object};
- }else {
- return [self isEqual:object];
- }
- }
- NSMutableSet *set = [NSMutableSet new];
- NSMutableArray *arrayA = [@[@1, @2] mutableCopy];
- [set addObject:arrayA];
- NSLog(@“set = %@“,set);
- // set = {((1,2))}
- NSMutableArray *arrayB = [@[@1, @2] mutableCopy];
- [set addObject:arrayB];
- NSLog(@“set = %@“,set);
- // set = {((1,2))}
- NSMutableArray *arrayC = [@[@1] mutableCopy];
- [set addObject:arrayC];
- NSLog(@“set = %@“,set);
- // set = {((1),(1,2))}
- [arrayC addObject:@2];
- NSLog(@“set = %@“,set);
- // set = {((1,2),(1,2))}
- NSSet *setB = [set copy];
- NSLog(@“setB = %@“,setB);
- // setB = {((1,2))}
第9条:以“类族模式”隐藏实现细节 35
第10条:在既有类中使用关联对象存放自定义数据 39
第11条:理解objc_msgsend的作用 42
第12条:理解消息转发机制 46
第13条:用“方法调配技术”调试“黑盒方法” 52
第14条:理解“类对象”的用意 56
.第3章 接口与api设计 60
第15条:用前缀避免命名空间冲突 60
第16条:提供“全能初始化方法” 64
第17条:实现description方法 69
第18条:尽量使用不可变对象 73
第19条:使用清晰而协调的命名方式 78
第20条:为私有方法名加前缀 83
第21条:理解objective-c错误模型 85
第22条:理解nscopying协议 89
第4章 协议与分类 94
第23条:通过委托与数据源协议进行对象间通信 94
第24条:将类的实现代码分散到便于管理的数个分类之中 101
第25条:总是为第三方类的分类名称加前缀 104
第26条:勿在分类中声明属性 106
第27条:使用“class-continuation分类”隐藏实现细节 108
第28条:通过协议提供匿名对象 114
第5章 内存管理 117
第29条:理解引用计数 117
第30条:以arc简化引用计数 122
第31条:在dealloc方法中只释放引用并解除监听 130
第32条:编写“异常安全代码”时留意内存管理问题 132
第33条:以弱引用避免保留环 134
第34条:以“自动释放池块”降低内存峰值 137
第35条:用“僵尸对象”调试内存管理问题 141
第36条:不要使用retaincount 146
第6章 块与大中枢派发 149
第37条:理解“块”这一概念 149
第38条:为常用的块类型创建typedef 154
第39条:用handler块降低代码分散程度 156
第40条:用块引用其所属对象时不要出现保留环 162
第41条:多用派发队列,少用同步锁 165
第42条:多用gcd,少用performselector系列方法 169
第43条:掌握gcd及操作队列的使用时机 173
第44条:通过dispatch group机制,根据系统资源状况来执行任务 175
第45条:使用dispatch_once来执行只需运行一次的线程安全代码 179
第46条:不要使用dispatch_get_current_queue 180
第7章 系统框架 185
第47条:熟悉系统框架 185
第48条:多用块枚举,少用for循环 187
第49条:对自定义其内存管理语义的collection使用无缝桥接 193
第50条:构建缓存时选用nscache而非nsdictionary 197
第51条:精简initialize与load的实现代码 200
第52条:别忘了nstimer会保留其目标对象 205