声明:本文整理自《Effective Objective-C》
Objective-C 基础
Objective-C 中使用消息传递机制而不是函数调用,消息传递机制是借鉴 smalltalk 语言发展而来。
对象的类型在运行时被发现,编译器不关心接收消息的对象的类型,而是在运行时进行动态绑定。
对象的内存始终分配在堆(heap)而不是栈(stack)中。而指向对象的指针,分配在栈(stack)中。
使用
@class
, 进行向前声明,将引入头文件的时机尽量延后。将遵从某个 protocol 的声明移动到 class-continuation,这样只需要将包含协议的头文件引入到实现(.m)文件,而不需将其放入公共头文件。
多用 NSNumber / NSArray / NSDictionary 的字面量语法(literal syntax),缩减代码长度。
使用 literal syntax 在 NSArray 存入 nil 时会抛出异常,可提前发现要加入数组的无效对象。
使用 literal syntax 创建的字符串、数组、字典对象都是不可变的,如果需要可变版本的对象,复制一份:
1
NSMutableArray *mutablearray = [@[@1,@2,@3,@4] mutableCopy];
多用类型常量,少用宏定义:
1
static const NSTimeInterval kAnimationDuration = 0.3;
相比
#define
宏定义,类型常量有类型信息,可提前发现代码编写错误。要对外公开某个常量,把常量放入全局符号表(global symbol table),以便在定义常量的编译单元之外使用。
1
extern NSString *const BXStringConstant;
除常用情况外,可在定义能够互相组合的选项时使用 NS_OPTIONS。如系统框架中定义的 UIViewAutoresizing 枚举:
1
2
3
4
5
6
7
8
9typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};在处理枚举类型的 switch 语句中,不用添加 default 情况,以开启漏写枚举值情况的提示
对象、消息、运行时
@synthesize 语法指定实例变量的名字,@dynamic 取消自动合成的存取方法和实例变量。
属性各自用法:
nonatomic / atomic: 指定原子性。
readwrite / readonly: 指定读写权限。
assign: 用于标量类型(scalar type),不持有。
strong: 表明持有关系。
unsafe_unretained: 用于对象类型(object type),不持有对象。
weak: 不持有对象,且对象销毁后自动置nil
。
copy: 如果一个不可变属性后续有可能会被可变实例赋值,使用copy
关键字保证不可变。如 NSString, 可保护其封装性;用于 Block 可将 Block 栈变量复制到堆。
getter =/ setter= : 指定存取方法名。私有属性在头文件中声明为
readonly
, 在 class-continuation category 中声明为readwrite
。使用 nonatomic 因为iOS性能瓶颈,并且 atomic 不能保证线程安全。
为了更有效率,直接的方式读实例变量;为了使用属性的内存管理性质,用属性的方式赋值。
在对象间用
==
符号比较的是指针是否相同,isEqual:
方法比较的是对象是否相同。用isEqualToString:
比较字符串更快。自定义类的等同性判定方法
isEqualToObj
如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15- (BOOL)isEqualToPerson:(OCPerson*)otherPerson {
if (self == otherPerson) return YES;
if (![_firstName isEqualToString:otherPerson.firstName]) return NO;
if (![_lastName isEqualToString:otherPerson.lastName]) return NO;
return YES;
}
- (BOOL)isEqual:(id)object {
// override 父类方法
if([self class] == [object class]) {
return [self isEqualToPerson:(OCPerson*)object];
} else {
return [super isEqual:object];
}
}Cocoa 中对类簇(class cluster)的应用如下:
1
+ (UIButton*)buttonWithType:(UIButtonType)type;
类簇是抽象工厂方法设计模式的一种实现。
给对象发送消息
id returnValue = [someObject messageName:parameter];
语句由编译器转换为标准的 C 语言函数调用:1
id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
objc_msgSend
会在接收者所属类中搜寻方法列表(list of methods),并跳转至与 selector 相匹配的方法,并且将匹配结果缓存在映射表里(fast map)。某些情况需要由运行时另一些函数处理,如
objc_msgSend_stret
处理返回结构体的消息,objc_msgSend_fpret
处理返回浮点数的消息,objc_msgSendSuper
处理发给超类的消息。一张图理解消息转发(message forwarding)机制:
Method Swizzling 在运行时改变 selector 所对应的实现。
类继承体系如图所示:
接口与 API 设计
由于 Objective-C 没有命名空间机制,要使用前缀避免命名空间冲突。
类中所有的初始化方法都应该调用同一个 Designated initializer.
调试程序时,实现
debugDescription
方法可以打印更详细的信息。1
2
3-(NSString*)debugDescription {
return [NSStringstringWithFormat: @"<%@: %p, \"%@ %@\">", [self class], self, _firstVariable, _lastVariable];
}私有函数加前缀进行区分如
p_privateMethod:
异常(throw exception)处理严重错误,会 crash 应用。更好的是用 NSError 来处理错误,NSError 通过委托协议传递此错误如:
1
2-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
}还可以经方法的输出参数返回给调用者,如:
1
2
3
4
5
6
7-(BOOL)doSomething:(NSError**)error;
NSError *error = nil;
BOOL ret = [object doSomething:&error];
if(error) {
}遵循 NSCopying 协议并实现协议规定的方法,即可使自定义的对象使用 copy 方法。
1
2
3
4- (id)copyWithZone:(NSZone*)zone {
BXPerson *copy = [[[self class] allocWithZone:zone] initWithFirst:_first andLast:_last];
return copy;
}深拷贝指对象中的数据将被拷贝一份,所指对象是原始相关对象的一份拷贝;而浅拷贝则仅拷贝对象本身,与原始内容均指向相同对象。
协议与分类
使用分类(category)机制把类的实现代码划分成易于管理的块。
使用 delegateflags 位段(bitfield)数据类型对 delegate 相关代码进行优化。
运用关联对象(associated objects)在分类中声明属性。
对象的类型不重要,重要的是对象有没有实现某些方法,这种情况下使用匿名对象,如
@property (nonatomic, weak) iddelegate;
内存管理
悬空指针(dangling pointer)指向无效对象,指向对象 release 后应该置空,即 weak 属性所做的。
MRC 下的属性设置方法如下:
1
2
3
4
5-(void)setFoo:(id)foo {
[foo retain];
[_foo release];
_foo = foo;
}ARC 下可用下列修饰符改变局部变量或实例变量的内存管理语义:
__strong: 默认,持有此值。
__unsafe_unretained: 不持有此值,可能不安全,因为再次使用变量时,其指向对象可能已经回收。
__weak: 不持有此值,不会造成安全问题,如果系统把对象回收了,那么变量会自动清空(autonilling)。
__autoreleasing: 在方法返回时自动释放。clang 的静态分析器完成内存操作代码的插入,实现 ARC。ARC 的底层优化很多,例如objc_autoreleaseReturnValue(id object) 和 objc_retainAutoreleasedReturnValue(id object).
在
dealloc()
中进行解除观察者监听等操作。使用 ARC 时必须捕获异常,需打开编译器的 -fobjc-arc-exceptions 以安全处理异常。
非 ARC,使用 unsafe_unretained(对象类型) / assign(C类型) 避免 retain cycle。
在 for 循坏中大量创建对象,可在循环中用 autoreleasepool 进行自动释放。
Block 与 GCD
Block 定义格式是
return_type (^block) (parameters)
,下面是一个标准的 Block 变量赋值操作:1
2
3int (^addBlock)(int a, int b) = ^(int a, int b) {
return a+b;
};原本 Block 内存分配在栈里,对其 copy 可以拷贝到堆中。
最好使用
typedef return_type (^block) (parameters)
为常用的块类型创建typedef
。在有多个实例需要监控时,使用委托模式会经常需要根据传入的对象来切换操作,而若改用 handler blocks 来实现,则可直接将块与对象联系起来。
使用 NSArray / NSDictionary / NSSet 中的块枚举方法实现遍历功能,如 NSArray 的方法:
1
- (void)enumerateObjectsUsingBlock:(void (^)(ObjectType obj, NSUInteger idx, BOOL *stop))block;
Block 能够持有变量,注意消除 Block 中的 retain cycle.
用 GCD 实现属性原子性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-(NSString *)someString {
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
-(void)setSomeString:(NSString*)someString {
dispatch_barrier_async(_syncQueue, ^{
_someString = someString;
});
}写入时用
dispatch_barrier
,串行执行。dispatch_after
替代performSelector:afterDelay:
,在主线程运行并行队列来替代performSelectorOnMainThread
:1
2
3dispatch_async(dispatch_get_main_queue(), ^{
// code here
});使用
dispatch_once
来执行只需要运行一次的代码:1
2
3
4
5
6
7
8+ (instancetype)sharedManager {
static BXManager *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[BXManager alloc] init];
});
return sharedManager;
}