1、Objective-C 起源:
在 C 语言基础上添加了面向对象特性,是 C 语言的超集。Objective-C 由 SmallTalk 语言演变过来,使用消息结构,运行环境由运行环境决定。
OC 对象所占内存总是分配在堆空间,绝不会在栈空间分配内存。不含 * 的变量,保存的不是OC对象,可能使用栈空间。
2、在类的头文件中,应尽量少引用其他头文件:
在编译一个使用其他类的头文件(.h文件)时,如果不需要知道那个类的实现细节,可以使用 @class ***; 即可。在实现文件中(.m),需要知道要引入类实现细节时,可使用 #import "***"。
* 除非确有必要,否则不要引入头文件。一般来说,在一个类文件中使用向前声明来提及别的类,并在实现文件中引入那些类的头文件,这样可以降低类之间的耦合;
* 有时无法使用向前声明,比如声明某个类 遵循某一协议。此时,尽量把“该类遵循某协议”这条声明移至“class-continuation”分类中,如果不行的话,就把协议单独放一个头文件中。
3、多用字面量语法,少用与之等价的方法:
例如:
NSString *str = @"Hello"; // NSString *str1 = [NSString ]; NSNumber *num = @2; NSNumber *num1 = [NSNumber numberWithInt:1]; NSArray *arr = @[@"1", @"sds", @"sjo"]; NSArray *arr1 = [NSArray arrayWithObjects:@"1", @"sds", @"sjo", nil]; NSDictionary *dic = @{ @"key1":@"value1", @"key2":@"value2", @"key3":@"value3" }; NSDictionary *dic1 = [NSDictionary dictionaryWithObjectsAndKeys:@"key1", @"value1", @"key2", @"value2",@"key3", @"value3", nil];
推荐使用 str,num,arr,dic 方式。
* 应该使用字面量语法来创建字符串、数值、数组、字典,与常规方法相比更简明扼要;
* 应该通过取下标操作来访问数组下标或字典中键对应的元素;
* 用字面量语法创建数组或字典时,务必保证值里面不能包含 nil。
4、多用类型常量,少用 #define 预处理指令:
若常量在外部使用,命名方法:以类名作为前缀
// .h 文件中 // extern 声明外部的常量,加上 static 表示不允许修改,不加上则可以修改。 extern NSString * const EOLoginManagerDidLoginNotification; extern const NSTimeInterval EOBookReadDuring;//阅读时长 // 文件中.m NSString * const EOLoginManagerDidLoginNotification = @"EOLoginManagerDidLoginNotification"; const NSTimeInterval EOBookReadDuring = 155;//单位(分钟)
若常量限于某个编译单元之内使用,即不打算公开这个常量,命名时加上前缀 "k",只需在实现文件中(.m)中声明即可!
static const NSTimeInterval kAnimationDuring = .3;//动画时长 (static const)不允许修改这个常量
* 不要用预编译处理指令定义常量。这样定义出来的常量不含类型信息。编译器只会在编译前查找和替换,即使有人定义了常量值,编译器也不会报警告,这将导致程序中常量值不一致。
* 在实现文件中使用 static const 定义“只在编译单元内可用的常量”,由于此类常量不在全局符号表中,所以无需为此添加类名前缀,加上 k 即可。
* 在头文件中使用 extern 声明全局常量,并在实现文件中定义其值,这种常量要出现在全局符号表中,所以其应该加上类名以区分。
5、使用枚举表示状态、选项、状态码:
typedef NS_ENUM(NSInteger, EOBookType) { EOBookTypeFinished = 0, EOBookTypeSerialize, EOBookTypeVIP, EOBookTypeFree, };
switch (self.bookType) { case EOBookTypeFinished: // break; case EOBookTypeSerialize: // break; case EOBookTypeVIP: // break; case EOBookTypeFree: // break; // 若用枚举定义状态,最好不要有 default 分支。 // default: // break; }
* 应该用枚举来表示状态机的状态、传递给方法的选项及状态码等值,给这些值起个简单易懂的名字。
* 如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将各选项值定为 2 的幂数,以便通过按位或操作将其组合起来。
* 用 NS_ENUM 与 NS_OPTIONS 宏来定义枚举类型,并指明其底层数据类型。这样做可以确保枚举是用开发者所选的数据类型来实现出来的,而不是编译器所选类型。
* 在处理枚举类型的 switch 语句中,不要实现 default 分支,否则,在加入新的枚举类型时,编译器会提示开发者,switch 语句并未处理所有枚举类型。
6、理解“属性”这一概念:
属性 = 实例变量+setter方法+getter 方法。编译器会自动生成一套存取方法,用以访问给定类型中给定名称的变量。
* 可以用 @property 语法来定义对象中所封装的数据;
* 通过属性来指定存储数据所需的正确语义;
* 在设置属性所对应的实例变量时,一定要遵从该属性所声明的语义;
* 开发 iOS 程序时应该使用 nonatomic 属性,因为 atomic 属性会严重影响性能。
7、在对象内部尽量直接访问实例变量:
直接访问实例变量的速度快,编译器所生产的代码会直接访问保存对象实例变量的那块内存;
直接访问实例变量时,不会调用其 setter 方法,这就绕过了相关属性所定义的内存管理语义。如果在ARC下直接访问一个声明为 copy 的属性,那么并不会拷贝改属性,只会保留新值并释放旧值;
在写入实例变量时,通过其 setter 方法来做,而在读取实例变量时,则直接访问。此办法既能提高读取操作的速度,又能控制对属性的写入操作。
* 在对象内部读取数据时,应该直接通过实例变量来读,而写入数据时,则通过属性来写;
* 在初始化方法及 dealloc 方法中,总是应该直接通过实例变量来读写数据;
* 有时会使用懒加载初始化技术配置某分数据,在这种情况下,需要通过属性来读取数据。
8、理解“对象等同性”这一概念:
比较两个对象是否相等,则两个对象的所有属性值都相等,有两种方式比较
- (BOOL)isEqual:(id)object { return YES; } - (NSUInteger)hash { return 1009; }
首先判断两个指针是否相等,若相等,两个对象必定相等。然后比较两对象所属的类。
若两对象相等,则其哈希码必然相等,反之,则未必相等
9、以“类族模式”隐藏实现细节:
例如:+ (instancetype)buttonWithType:(UIButtonType)buttonType;
该方法返回的对象取决于传入的 buttonType,然而不管返回什么类型的对象,他们都继承自相同的基类。使 button 的使用者无需关系创造出来的按钮属于哪个子类,也不用关心按钮的绘制方式及实现细节。
#import <Foundation/Foundation.h> typedef NS_ENUM (NSInteger, EOEmployeeType) { EOEmployeeTypeDeveloper, EOEmployeeTypeDesigner, EOEmployeeTypeFinance }; NS_ASSUME_NONNULL_BEGIN @interface EOEmployee : NSObject + (EOEmployee *)employeeWithType:(EOEmployeeType)employeeType; - (void)workStart; @end @interface EOEmployeeDeveloper : EOEmployee @end @interface EOEmployeeDesigner : EOEmployee @end @interface EOEmployeeFinance : EOEmployee @end NS_ASSUME_NONNULL_END
@implementation EOEmployee + (EOEmployee *)employeeWithType:(EOEmployeeType)employeeType { switch (employeeType) { case EOEmployeeTypeFinance: // return [EOEmployeeFinance new]; break; case EOEmployeeTypeDesigner: // return [EOEmployeeDesigner new]; break; case EOEmployeeTypeDeveloper: // return [EOEmployeeDeveloper new]; break; default: break; } } - (void)workStart { } @end
* 类族模式可以把实现细节隐藏在一套简单的公共接口后面;
* 系统框架经常使用类族;
* 从类族的公共抽象基类中继承子类时,若有开发文档,请先阅读。
10、在既有类中使用关联对象存放自定义数据:
OBJC_EXPORT voidobjc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
OBJC_EXPORT id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
OBJC_EXPORT void objc_removeAssociatedObjects(id _Nonnull object)
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"提示内容" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil]; void (^block)(NSInteger) = ^(NSInteger buttonIndex) { if (buttonIndex == 0) { // cancel } else if (buttonIndex == 1) { // sure } }; objc_setAssociatedObject(alertView, EOAlertViewKey, block, OBJC_ASSOCIATION_COPY); [alertView show]; - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { void (^block)(NSInteger) = objc_getAssociatedObject(alertView, EOAlertViewKey); block(buttonIndex); }
* 可以使用关联对象机制把两个对象连接起来;
* 定义关联对象时,可使用内存管理语义,用以模仿定义属性时所采用的“拥有关系”和“非拥有关系”;
* 只有在其他方法不可行时,才选用关联对象,这种做法难以查找 Bug。
11、理解 objc_msgSend 作用:
消息传递。。。
C 语言使用“静态绑定”,即在编译时就确定了函数。
void printHello () { printf("Hello!\n"); } void printGoodBye () { printf("GoodBye!\n"); } void doSomething (int type) {// 静态语言 在编译时就能确定函数 if (type == 0) { printHello(); } else { printGoodBye(); } return; } void doAnything (int type) {// 动态语言 使用了动态绑定,要调用的函数在运行时才能确定 void (*fnc)(void); if (type == 0) { fnc = printHello; } else { fnc = printGoodBye; } fnc(); return; }
* 消息由接收者、选择子及参数构成。给某对象“发送消息”,也就相当于该对象上“调用方法”。
* 发给某对象的全部消息都要由“动态消息派发系统”来处理,该系统会查出对应的方法,并执行其代码。
12、理解消息转发机制:
当对象接收到无法解读的消息时,就会启动“消息转发”机制。
消息转发分为两大阶段:第一阶段先征询接收者,所属的类,看其能否动态添加方法,以处理当前这个“位置的选择子”,这叫做“动态方法解析”;
第二阶段涉及“完整的消息转发机制”。如果运行期系统已经把第一阶段执行完了,那么接收者自己无法再以动态新增的方法来响应包含该选择子的消息了。此时,运行期的系统会请求接收者以其他手段来处理与消息相关的方法调用。首先,请接收者看看有没有 其他对象处理这条消息。若有,则运行期系统会把消息转给那个对象,于是消息转发过程结束,一切正常。若没有“备援的接收者”则启动完整的消息转发机制,运行期系统会把与消息有关的全部细节都封装到 NSInvocation对象中,再给接收者最后一次机会,令其设法解决当前还未处理的这条消息。
动态方法解析:对象在收到无法解读的消息后,首先将调用其所属类的下列类方法:+ (BOOL)resolveInstanceMethod:(SEL)selector
+ (BOOL)resolveInstanceMethod:(SEL)selector { NSString *selectorString = NSStringFromSelector(selector); if ([selectorString hasPrefix:@"set"]) { class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@"); } else { class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:"); } return YES; }
首先将选择子化为字符串,然后检测其是否表示设置方法。若前缀 set,则表示设置方法,否则为 get 方法。不管哪种情况,都会把处理该选择子的方法加到类里面,所添加的方法是纯 C 函数实现的。
备援接收者:当接收者还有第二次机会处理未知的选择子,在这一步中,运行期系统会问它:能不能把这条消息转给其他接收者来处理。与该步骤对应的处理方法如下:- (id)forwardingTargetForSelector:(SEL)aSelector
消息转发全流程:
#import "EOAutoDictionary.h" #import <objc/runtime.h> @interface EOAutoDictionary () @property (nonatomic, strong) NSMutableDictionary *backingStore; @end @implementation EOAutoDictionary @dynamic string, number, date, opaqueObject; - (instancetype)init { if (self = [super init]) { _backingStore = [NSMutableDictionary dictionaryWithCapacity:1]; } return self; } id autoDictionaryGetter(id self, SEL _cmd) { EOAutoDictionary *typeSelf = (EOAutoDictionary *)self; NSMutableDictionary *backingStore = typeSelf.backingStore; NSString *key = NSStringFromSelector(_cmd); return [backingStore objectForKey:key]; } void autoDictionarySetter(id self, SEL _cmd, id value) { EOAutoDictionary *typeSelf = (EOAutoDictionary *)self; NSMutableDictionary *backingStore = typeSelf.backingStore; NSString *selectorString = NSStringFromSelector(_cmd); NSMutableString *key = [selectorString mutableCopy]; [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)]; [key deleteCharactersInRange:NSMakeRange(0, 3)]; NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString]; [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar]; if (value) { [backingStore setObject:value forKey:key]; } else { [backingStore removeObjectForKey:key]; } } + (BOOL)resolveInstanceMethod:(SEL)selector { NSString *selectorString = NSStringFromSelector(selector); if ([selectorString hasPrefix:@"set"]) { class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@"); } else { class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:"); } return YES; } @end
* 若对象无法响应某个选择子,则进入消息转发流程;
* 通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入到类中;
* 对象可以把其无法解读的某些选择子转交给其他对象来处理;
* 经过上述两步后,如果还是没有办法处理选择子,那就启动完整的消息转发机制;
13、使用 “方法调配技术”调试“黑盒方法”
#import "NSString+EOAddtions.h" #import <objc/runtime.h> @implementation NSString (EOAddtions) + (void)load { Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString)); Method swappedMethod = class_getInstanceMethod([NSString class], @selector(eo_lowercaseString)); method_exchangeImplementations(originalMethod, swappedMethod); } - (NSString *)eo_lowercaseString { NSString *lowercase = [self eo_lowercaseString]; NSLog(@"%@=>%@", self, lowercase); return lowercase; } @end
* 在运行时,可以向类中新增或替换选择子所对应的方法实现;
* 使用另一份实现来替换原有的方法实现,叫 method_swizzing,开发者常用此技术向原有实现中添加新功能;
* 一般来说,只有调试程序的时候才需要在运行时修改方法实现,此方法不宜滥用。
14、理解“类对象”的用意:
* 每个实例都有一个指向 Class 对象的指针,用以表明其类型,而这些 Class 对象构成了类的继承体系;
* 如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知;
* 尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。
15、使用前缀避免命名空间冲突:
* 选择与你公司、应用程序或者二者皆有关联之名称作为类名的前缀,并在所有代码中均使用这一前缀;
* 若自己开发的程序中用到了第三方库,则应为其中的名称加上前缀。
16、提供“全能初始化方法”:
// .h
@interface EORectangle : NSObject <NSCoding> @property (nonatomic, assign) float width; @property (nonatomic, assign) float height; - (id)initWithWidth:(float)width height:(float)height; @end //.m #import "EORectangle.h" @implementation EORectangle - (id)initWithWidth:(float)width height:(float)height { if (self = [super init]) { _width = width; _height = height; } return self; } - (instancetype)init { return [self initWithWidth:5.0 height:10.0]; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:[NSNumber numberWithFloat:_width] forKey:@"width"]; [aCoder encodeObject:[NSNumber numberWithFloat:_height] forKey:@"height"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { _width = [aDecoder decodeFloatForKey:@"width"]; _height = [aDecoder decodeFloatForKey:@"height"]; } return self; } @end //.h @interface EOSpuare : EORectangle - (id)initWithDimension:(float)dimension; @end //.m #import "EOSpuare.h" @implementation EOSpuare - (id)initWithDimension:(float)dimension { return [super initWithWidth:dimension height:dimension]; } - (id)initWithWidth:(float)width height:(float)height { float dimension = MAX(width, height); return [self initWithDimension:dimension]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { // } return self; } @end
* 在类中提供一个全能初始化方法,并于文档中说明。其他初始化方法均应调用此方法;
* 若全能初始化方法与父类不同,则需重写父类中对应的方法;
* 如果父类的初始化方法不适用于子类,那么应该重写这个父类的方法,并在其中抛出异常。
17、实现 descriptin 方法:
#import <Foundation/Foundation.h> typedef NS_ENUM(NSInteger, EOGender) { EOGenderMale = 0, EOGenderFemale }; NS_ASSUME_NONNULL_BEGIN @interface EOAuther : NSObject @property (nonatomic, strong) NSString *name;//名称 @property (nonatomic, strong) NSString *realName;//真实姓名 @property (nonatomic, assign) BOOL isSigned;//是否是签约作家 @property (nonatomic, assign) EOGender gender;//性别 @end NS_ASSUME_NONNULL_END #import "EOAuther.h" @implementation EOAuther - (NSString *)description { return [NSString stringWithFormat:@"<%@:%p,\"%@ %@\">", [self class], self, _name, _realName]; } @end
* 实现 decription 方法返回一个有意义的字符串,用以描述该实例;
* 若想在调试时打印出更详尽的对象描述,则应实现 debugDescrition 方法
18、尽量使用不可变对象:
* 尽量创建不可变的对象;
* 若某属性仅可用于内部修改,则在 "class-continuation分类"中将其由 readonly 改为 readwrite;
* 不要把可变的 collection 作为属性公开,而应提供相关方法,用以修改对象中的可变 collection
19、使用清晰而协调的命名方式:
* 起名时应遵从标准的 OC 命名规范,这样创建出来的接口更容易为开发者所理解;
* 方法名要言简意赅;
* 方法名里不要使用缩略后的类型名称;
* 给方法起名时的第一要务就是要确保其代码风格与自己的代码或所要集成的框架相符。
20、为私有方法添加前缀:
* 给私有方法的名称加上前缀,这样可以容易将其和公共方法区分开;
* 不要单用一个下划线做私有方法的前缀,因为这种做法是预留给苹果公司的
21、理解 OC 错误模型:
* 只有发生了可使整个程序 Crash 的严重 Bug 时,才应使用异常;
* 在错误不那么严重时,可以指派“委托方法”来处理错误,也可以把错误信息放在 NSError 对象里,经由“输出参数”返回给调用者。
22、理解 NSCopying 协议:
深拷贝:在拷贝对象自身时,将其底层数据一并复制过去。
浅拷贝:只拷贝容器对象本身,并不复制其中数据,Foundation 框架中所有的 Collection 类在默认情况下都是浅拷贝。
#import "EOPerson.h" @implementation EOPerson { NSMutableSet *_friends; } - (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName { if (self = [super init]) { _firstName = [firstName copy]; _lastName = [lastName copy]; _friends = [NSMutableSet copy]; } return self; } - (void)addFriend:(EOPerson *)person { [_friends addObject:person]; } - (void)removeFriend:(EOPerson *)person { [_friends removeObject:person]; } - (id)copyWithZone:(NSZone *)zone { EOPerson *copy = [[[self class] allocWithZone:zone] initWithFirstName:_firstName lastName:_lastName]; copy->_friends = [_friends mutableCopy]; return copy; } - (id)deepCopy { EOPerson *copy = [[[self class] alloc] initWithFirstName:_firstName lastName:_lastName]; copy->_friends = [[NSMutableSet alloc] initWithSet:_friends copyItems: YES]; return copy; } @end
* 若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议;
* 如果自定义对象分为可变与不可变版本,那么需要同时实现 NSCopying 与 NSMutableCopying 协议;
* 复制对象时需决定采用浅拷贝还是深拷贝,一般情况下尽量使用 浅拷贝;
* 如果写的对象需要深拷贝,那么可考虑新增一个专门执行深拷贝的方法。
23、通过 delegate 与 dataSource 进行对象间通信:
#import "EONetworkFetcher.h" @interface EONetworkFetcher () { struct { unsigned int didReceiveData : 1; unsigned int didFailWithError : 1; unsigned int didUpdateProgressTo : 1; }_delegateFlags; } @end @implementation EONetworkFetcher - (void)setDelegate:(id<EONetworkFetcherDelegate>)delegate { _delegate = delegate; _delegateFlags.didReceiveData = [delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)]; _delegateFlags.didFailWithError = [delegate respondsToSelector:@selector(networkFetcher:didFailWithError:)]; _delegateFlags.didUpdateProgressTo = [delegate respondsToSelector:@selector(networkFetcher:didUpdateProgressTo:)]; } @end
* 将委托对象应该支持的接口定义成协议,在协议中把可能需要处理的事件定义成方法;
* 当某对象需要从另一个对象获取数据时,可以使用委托模式,dataSource protocal
* 若有必要,可实现含有位段的结构体,将委托对象是否能响应相关协议方法这一信息缓存到其中。
24、将类的实现代码分散到便于管理的某个数的分类中:
* 通过分类机制(category),可以把类代码分成很多个易于管理的小块,以便单独检视;
* 将应该视为“私有”的方法归入名叫 Private 的分类中,以隐藏实现细节。
25、 总是为第三方类的方法名称加前缀:
* 向第三方类中添加分类时,总应给其名称加上你专用的前缀;
* 向第三方类中添加分类时,总应给其中的方法名加上你专用的前缀;
26、勿在分类(Category)中声明属性:
因为存取方法声明为 dynamic,只有在运行时才能提供,在编译器期时是看不到的,编译器会报警告。
* 把封装数据所用的全部属性都定义在主接口里;
* 在“class-continuation分类”之外的其他分类中,可以定义存取方法,但尽量不要定义属性。
27、使用 "class-continuation分类"隐藏实现细节:
@interface EOPerson ()//特殊的分类 //自定义方法 @end
* 通过“class-continuation分类”向类中新增实例变量;
* 如果某属性在主接口中声明为 readOnly,而类的内部又要用设置方法修改此属性,那么就在"class-continuation分类"中将其扩展为 readWrite;
* 把私有方法的原型声明在 "class-continuation分类"里;
* 若想使类所遵循的协议不为人知,则可于"class-continuation分类"中声明。
28、通过协议提供匿名对象:
* 协议可在某种程度上提供匿名类型。具体的对象类型可以淡化成遵从某协议的 id 类型,协议里规定了对象所应实现的方法;
* 使用匿名对象来隐藏类型名称;
* 如果具体类型不重要,重要的是对象能够响应特定方法,那么可使用匿名对象来表示。
29、理解应用计数:
30、以 ARC 简化引用计数:
ARC 在调用 retain/release/autorelease/dealloc 这些方法时,并不通过普通的 OC消息派发机制,而是直接调用其底层 C 语言版本。这样做性能更好 retain<=>objc_retain..
在编译期,ARC 会把能够相互抵消的 retain/release/autorelease 操作简约,如果发现在用一个对象上执行了多次 retain/release ,那么 ARC 有时可以成对的移除两个操作。
__strong:默认语义,保留此值;
__unsafe_unretained:不保留此值,那么做不安全,因为等到再次使用变量时,其对象可能已被回收;
__weak:不保留此值,但是变量可以安全使用,因为如果系统把这个对象回收了,那么变量也会自动清空;
__autoreleasing:把对象“按应用传递”给方法时,使用这个特殊的修饰符,此值在方法返回时自动释放。
* ARC 只负责管理 OC 对象的内存,CoreFoundation 对象不归 ARC 管理,开发者必须适时调用 CFRetain/CFRelease。
31、在 dealloc 方法中只释放引用并解除监听:
* 在 dealloc 方法里,释放指向其他对象的引用,并取消原来订阅的 KVO 或 NSNotificationCenter,不要做其他事情;
* 如果对象持有文件描述符等资源文件,那么应该专门编写一个方法来释放此种资源。这样的类要和其他使用者约定:用完资源后必须调用 close 方法;
* 执行异步任务的方法不应在 dealloc 里调用:只能在正常状态下执行的那些方法也不应在 dealloc 里调用,应为对象已经正在回收的状态了。
32、编译“异常安全代码”时留意内存管理问题:
如果 MRC,必须捕获异常,那么要设法保证代码能把对象正确清理干净;
如果 ARC,必须捕获异常,则需打开编译器的 -fobjc-arc-exceptions 标志。
* 捕获异常时,一定要注意将 try 块内所设立的对象清理干净;
* 在默认情况下,ARC 不会生成安全处理异常所需的清理代码。开启编译器标志后,可以生成这种代码,不过会导致程序变大,而且会降低运行效率。
33、以弱引用避免保留环(循环引用):
使用弱引用来表示“非拥有关系”,避免内存泄漏。
使用 weak 来声明;
* 将某些引用设为 weak,可避免出现循环引用;
* weak 引用可以自动清空,也可以不自动清空。ARC的自动清空,由运行时系统来实现。在具备自动清空功能的弱引用上,可以随意读取其数据,因为这种引用不会指向已经回收过的对象。
34、以“自动释放池”降低内存峰值:
自动释放池用于存放那些需要在稍后某个时刻释放的对象。清空自动释放池时,系统会向其中的对象发送 release 消息。
GCD线程,默认拥有自动释放池,每次执行“事件循环”时,就会将其自动清空。因此不需要手动创建“自动释放池”。
main 函数的 自动释放池。。。
应用程序在执行循环时的内存峰值就会降低。内存峰值:是指应用程序在某个特定时间内的最大内存用量,自动释放池可以减少这个峰值,因为系统会在块的末尾把某些对象回收掉。
* 自动释放池在栈区,对象收到 autorelease 消息后,系统将其放在最顶端的池里;
* 合理运用自动释放池,可降低应用程序的内存峰值;
* @autoreleasepool 这种新式写法能创建出更为轻便的自动释放池。
35、用“僵尸对象”调试内存管理问题:
scheme->run->Diagnostics->Enable Zombie Objects。
僵尸对象工作原理:代码深植于 OC 的运行时程序库,Foundation框架及CoreFoundation框架中。系统在即将回收对象时,如果发现通过环境变量启用了僵尸对象功能,那么还将执行一个附加步骤。这一步就是把对象转化为僵尸对象,而不彻底回收。
* 系统在回收对象时,可以不将其真的回收,而是把它转化为僵尸对象。通过环境变量 NSZombieEnabled 可开启此功能;
* 系统会修改对象的 ISA 指针,令其指向特殊的僵尸类,从而使该对象为僵尸对象。僵尸类能够响应所有的选择子,响应方式:打印一条包含消息及其接受着的消息,然后终止应用程序。
36、不要使用 retainCount: