Effective Objective-C 2.0 笔记 1~22条,27条,29~30条

第一条:了解OC语言的起源

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  • OC是“消息结构语言”,运行时所执行的代码由运行期环境决定。编译器不关心接受消息的对象是何种类型,在运行时才会查找所要执行的方法。
  • OC是C的超集,理解C的内存模型有助于OC的引用计数。
  • 对象所占内存总是分配在堆中,不在栈。堆中的内存需直接管理,而栈则随栈帧弹出。NSString *string;(为对象)
  • 分配在堆的是NSString实例,而其指针分配在栈上
  • 有时会有不含*的变量,可能不是对象,例如CGRect(c的结构体)

第二条:在类的头文件中尽量少引用其他头文件

  • 如果在EOCPerson中引入EOCEmployer,只需知道有这个类名时@property (nonatomic ,strong)EOCEmployer *employer; 可以使用“向前声明”——@class EOCEmployer;但是需要知道其接口的全部细节时——#import “EOCEmployer.h”
  • 除非确有必要,否则不要引入头文件。一般,在类的头文件中使用向前声明提及别的类,在实现文件引入头文件。这样可以减低耦合;
  • 有时无法使用向前声明,比如要声明某个类遵循一项协议。这种情况下,尽量把“该类遵循某协议”的这条声明移至“分类”中。如果不行,就把协议单独放在一个头文件中,然后将其引入——P6。

第三条:多用字面量语法,少用与之等价的方法

  • 字面数值
NSNumber *someNumber = [NSNumber numberWithInt:1];
NSNumber *someNumber = @1;

NSNumber *intNumber = @1;
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.14159;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';

int x = 5;
float y = 6.32f;
NSNumber *expressionNumber = @(x * y);
  • 字面量数组
NSArray *animals = [NSArray arrayWithObjects:@"cat",@"dog",@"pig", nil];
NSArray *room = @[@"bed" ,@"desk" ,@"book"];//这样更安全,有nil时就会报出异常,上面则会停止

NSString *dog = [animals objectAtIndex:1];//取下表操作
NSString *desk = room[0];
  • 字面量字典
NSDictionary *personData = [NSDictionary dictionaryWithObjectsAndKeys:@"Matt", @"firstName", @"Galloway", @"lastName", [NSNumber numberWithInt:20], @"age",nil];
NSDictionary *_personData = @{@"firstName":@"Matt", @"lastName":@"Galloway" ,@"age":@28};

NSString *lastString = [personData objectForKey:@"lastName"];//按照特定值访问
NSString *firstString = personData[@"firstName"];
  • 可变数组与字典//修改操作
[mutableArray replaceObjectAtIndex:1 withObject:@"dog"];
mutableArray[1] = @"dog";
[mutableDic setObject:@"Galloway" forKey:@"lastName"];
mutableDic[@"lastName"] = @"Galloway";
  • 局限性 :除了字符串之外,所有对象都属于foundation框架。使用字面量语法创建出来的字符串,数组,字典都是不可变的。如需改变需要再复制一份
NSMutableArray *mutable = [@[@1 ,@3 ,@2]mutableCopy];

第四条:多用类型常量,少用#define预处理指令

  • 如果播放动画 static const NSTimeInterval kAnimationDuration = 0.3; 会比预处理命令 #define ANIMATION_DURATION 0.3 好很多 如果声明在头文件,则引入头文件时就引入了这个预处理指令
  • 常量名称命名法:若常量局限于某“实现文件”,则在前面加k。若在常量之外可见,则通常以类名为前缀。第19条*
  • 常量应该定义在实现文件。若定义在头文件,那么引入这个头文件的其他文件,也会出现这个名字,等于生声明了全局变量。
  • 变量同时用static和const来声明。const:由其修饰符的变量不可修改。static:该变量仅在当前编译单元可见(也就是当前的.m文件,否则在别的.m文件中声明同名变量会报错)。
  • 当需要对外公开某个常量,应放在“全局符号表”中
//header  file
extern NSString *const EOCStringConstant;//extern关键字:在全局符号表中,将会有一个名叫EOCStringConstant的符号
//implementation file
NSString *const EOCStringConstant = @“VALUE”;//此类常量必须要定义,而且只能定义一次
  • 如果有通知如下
//header file
extern NSString *const EOCLoginManagerDidLoginNotification;
//implementation file
NSString *const EOCLoginManagerDidLoginNotification = @"EOCLoginManagerDidLoginNotification";
则应避免名称冲突,最好用与之相关的类名做前缀。如UIKit里,UIApplicationWillEnterForegroundNotification
  • 小结:这样定义常量优于预处理指令,因为会确保常量值不变,否则无意中遭人修改。勿使用预处理定义常量。

第五条:用枚举表示状态,选项,状态码

  • 定义枚举类型:
enum EOCConnectionState state = EOCConnectionStateDisconnected;

否则就是:

enum EOCConnectionState {
	EOCConnectionStateDisconnected,
	EOCConnectionStateConnecting,
	EOCConnectionStateConnected,
};
typedef enum EOCConnectionState EOCConnectionStating;
现在可以用EOCConnectionStating代替完整的enum EOCConnectionState:EOCConnectionStating state = EOCConnectionStateDisconnected;
编译器会为每个枚举分配一个编号,若不特意指明则从0开始,每个枚举递增1.
  • 当选项之间可以彼此组合,更应用枚举。可使用“按位或操作符”,例如下:
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
	UIViewAutoresizingNone                 = 0,
	UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
	UIViewAutoresizingFlexibleWidth        = 1 << 1,
	UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
	UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
	UIViewAutoresizingFlexibleHeight       = 1 << 4,
	UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
  • 凡事需要以按位或操作的枚举都应用NS_OPTIONS定义。若是枚举不需要相互组合,则应使用NS_ENUM
  • 枚举也可以使用“向前声明”,指定底层数据类型 enum EOCConnectionStateConnectionState : NSInterger {//};
  • 用NS_ENUM和NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。这样做可以确保枚举时用开发者所选的底层数据类型实现的,而不是编译器所选类型
  • 在处理枚举类型的switch语句中不要实现default分支。这样加入枚举编译器就会提示。

第六条:理解“属性”这一概念

  • “属性”是OC的一项特征,用于封装对象中的数据
  • 稳固的ABI。
  •   1.对象在编译期将实例变量替换为“偏移量”(表示距离实际存放地址有多远)。
    
  •   2.将实例变量当做存储偏移量所用“特殊变量”,交由“类对象”保管。  所以当类的定义改变了,存储偏移量也就变了,这样无论何时访问实例变量,总能找到正确的偏移量。
    
  • 在对象接口的定义中,可以使用属性,访问封装在对象里的数据。其意思是:编译器会自动写出一套存取方法用以访问给定类型中具有给定名称的变量。
  • 编译器会把“点语法”转化为对存取方法的调用,使用“点语法”与直接调用存取方法相同
NSString *lastName = aPerson.lastName;//same as
NSString *lastName = [aPerson lastName];
  • 若不想编译器自动合成存取方法,可以自己实现。还可以阻止编译器自动合成,就是使用@dynamic
@interface EOCPerson : NSObject
@property NSString *Name;
@end

@implementation EOCPerson
@dynamic Name;/
@end
  • 属性特质分为四类:原子性,读/写权限,内存管理语义,方法名
    • 原子性:如果属性具备nonatomic特质,则不使用同步锁,iOS开发一般都使用。因为atomic属性会严重影响性能。即使实用同步锁,也会读到不同的属性值。所以要实现“线程安全”需要采用更为深层的锁定机制。
    • 读/写权限:具备readwrite(读写)特质的属性有“获取方法”(getter)和“设置方法”(setter)。若由@synthesize实现,则自动生成两个方法。具备readonly(只读),只有getter。若在@synthesize实现,则自动生成getter。可以对外公开readonly,在分类中重新定义readwrite。
    • 内存管理语义:下面这些仅会影响setter方法。assign:针对“纯量类型”(CGFloat,NSInterger)简单赋值操作。strong:一种“拥有关系”,会先保留新值,释放旧值,再设置新值。weak:一种“非拥有关系”,既不保留新值,也不释放旧值,对象遭到摧毁时,属性值也会清空。unsafe_unretained:语义和assign相同,但适用于“对象类型”,表达“非拥有关系”,对象遭到摧毁,属性不会清空。copy:特质于strong类似。设置新值时将其copy一份。例如属性类型为NSString *时,而传递给setter的新值是一个NSMutableString,此时若不copy,那么字符串的值就会在不知情的情况下变动。
    • 方法名:指定存取方法的方法名
getter = <name>制定“获取”的方法名
@property (nonatomic, getter = isOn) BOOL on;   //获取方法加上is前缀
  • 若自己实现存取方法,应保证具备相关属性所声明的特质

第七条:在对象内部尽量直接访问实例变量

  • 在读取实例变量时采用直接访问,设置时通过属性实现
  • 直接访问速度快,不会调用其“设置方法”,不会触发KVO。
  • 属性访问有助于排查错误,新增“断点”。
  • 在初始化及dealloc中,尽量使用直接访问,因为子类可能“覆写”setter方法。
  • 在有可能出现“延迟初始化”时,需要通过属性来读取数据

第八条:理解“对象等同性”这一概念

  • == 比较的是两个指针本身,而不是其所指的对象
  • 如果“isEqual:”方法判定两个对象相等,那么hash也会返回同一个值。如果hash返回同一个值,“isEqual”未必认为两者相等。
  • 作者自己实现的isEqual方法
-(BOOL)isEqualT : (id)object {
	if (self == object) return YES;//判断指针是否相等
	if([self class] != [object class]) return NO;//判断是否属于同一个类
	EOCPerson *otherPerson = (EOCPerson *)object;
	if (![_firstName isEqualToString : otherPerson.firstName])//检测属性
		return NO;
	if (![_lastName isEqualToString : otherPerson.lastName])
		return NO;
	if (_age != otherPerson.age)
		return NO;
	return YES;
}
  • 作者所实现的hash方法,既可以不重复扫描“箱子”内容,也减少了创建字符串的开销
-(NSInterger)hash{
	NSUInterger firstNameHash = [_firstName hash];
	NSUInterger lastNameHash = [_lastName hash];
	NSUInterger ageHash = _age;
	return firstNameHash ^ lastNameHash ^ ageHash;
}
  • 在可以保证受测对象类型时,多使用如“isEqualToString”“isEqualToArray”“isEqualToDictionary”等,速度快
  • 不能保证时,可以自己编写判定方法(如果受测的参数于接受的对象属于同一个类,那么调用自己的判定方法,否则交由超类判断)——p33
  • 深度判断:两个数组比较时使用“等深度判断”(先比较数组个数,再逐个比较调用“isEqual:”)。但类中的实例如果从数据库中创建来,就会有“唯一标识符”——identifier,比较标识符就会确定两个对象相等,尤其是readonly时。
  • 把可变对象放入collection中,尽量确保hash不是由可变部分计算出的,或是之后不再变化。否则应用相关代码处理可能会发生的问题。

第九条:以“类族模式”隐藏实现细节

  • 以UIButton为例,使用者无需知道其属于哪个子类,只需要知道按钮类型,其余细节在类族中隐藏。可以通过定义抽象基类和由其继承的实体子类,在基类选择类型,在子类实现繁冗的细节
  • NSArray是一个类族,所以 if([maybeArray class] == [NSArray class]) 永远为假。但想判断某对象是否位于类族可以 if ( [ maybeArray isKindOfClass : [ NSArray class ] ] )
  • 子类应该继承自类族中的抽象基类,子类应该定义自己的数据存储方式,子类应当覆写超类文档中指明需要覆写的方法

第十条:在既有类中使用关联对象存放自定义数据

  • 在既有类中存储相关信息,通常从对象所属的类中继承一个子类,然后改用子类对象。
  • 可以给对象关联其他对象,他们通过“键”来区分。并指明“存储策略”。
  • 存储策略:
OBJC_ASSOCATION_ASSIGN(assign)
OBJC_ASSOCATION_RETAIN_NONATOMIC(nonatomic,retain)
OBJC_ASSOCATION_RETAIN(retain)
OBJC_ASSOCATION_COPY(copy)
  • 管理关联对象:
void objc_setAssociatedObject(id object, void* key, id value, objc_AssociationPolicy policy)以给定的键和策略设置关联对象
void objc_getAssociatedObjects(id object, void *key)根据给定的键从对象获取相应的关联对象
void objc_removeAssociatedObjects(id object)移除对象的全部关联对象
  • 举例:UIAlertView展示警告信息,当同一个类里面处理多个警告信息时,在创建警告视图时直接把处理每个按钮的逻辑写好。通过关联对象来做
#import <objc/runtime.h>
static void *EOCMyAlertViewKey = "EOCMyAlertViewKey";

-(void)askUserAQuestion{
	UIAlertView *alert =  [[UIAlertView alloc]initWithTitle:@"Question" message:@"what do you want to do ?" delegate:self cancelButtonTitle:@"Cancle" otherButtonTitles:@"Continue", nil];
	void (^ block)(NSInteger) = ^(NSInteger buttonIndex){
		if (buttonIndex == 0) {
			[self doCancle];
		}else{
			[self doContinue];
		}
   };
	objc_setAssociatedObject(alert, EOCMyAlertViewKey, block, OBJC_ASSOCATION_COPY);
	[alert show];
}
//UIAlertViewDelegate
-(void)alertViewDelegate:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
	void (^block) (NSInteger) = void objc_getAssociatedObject(alertView, EOCMyAlertViewKey);
	block(buttonIndex);
}
  • 这种做法很有用,但只是在其他办法行不通时才考虑,若是滥用,则会令代码失控,难于调试。
  • 若想创建这种UIAlertView,就是从中继承子类,把块保存为子类中的属性。

第十一条:理解objc_msgSend的作用

  • id returnValue = [someObject messageName: parameter]; —> . void objc_msgSend (id self, SEL cmd, …) . 第一个参数代表接受着,第二个代表选择子,后续参数就是消息中的参数,即:
id returnValue = objc_msgSend ( someObject, @selector (messageName: ), parameter );//参数个数可变的函数,能接受2个或2个以上的参数
  • 当首次执行某个函数,该方法会在接收者所属的类中搜索“方法列表”,如果找到就跳转至实现代码,若找不到就沿体系继续向上找,等找到再跳转。最终还是找不到就执行“消息转发”操作(12条)。objc_msgSend会将匹配结果缓存在“快速映射表”中,下次查找就会快很多。
  • objc_msgSend_stret 如果返回的消息是结构体,并且结构体可以容纳于CPU中
  • objc_msgSend_fpret 如果返回的消息是浮点数(处理特殊情况)
  • objc_msgSendSuper 如果给超类发消息,例如:[super message:parameter]
  • OC对象的每个方法都可以视为简单的C函数
<return_type> Class_selector( id self , SEL_cmd , ... )//这里的类(class),选择子(selector)
  • 如果函数的最后一项操作是调用另一个函数而不会将其返回值另作他用时,则会执行“尾调用优化”(编译器会生成调转至另一个函数所需的指令码,而且不会向调用堆栈中推入新的“栈帧”)。否则会过早地发生“栈溢出”现象。

第十二条:理解消息转发机制

  • 这一章都是runtime的内容,有时间看看https://www.cnblogs.com/ioshe/p/5489086.html
  • 消息转发机制分为两个阶段:
    • 一,征询接收者所属的类,看看是否可以动态的添加方法,叫“动态方法解析”
    • 二,1⃣️接收者看看有没有其他的对象可以处理这条消息,如果有就转给他,消息转发结束,叫“备援的接受者”2⃣️启动完整的消息转发机制,将细节封装在NSInvocation中,给接收者最后一次机会
  • 动态方法解析
    • 对象收到无法解读的消息后,将调用 +(BOOL)resolveInstanceMethod:(SEL)sel 此方法表示是否能新增一个实例方法用以处理选择子。但如果不是实例方法而是类方法 +(BOOL)resolveClassMethod:(SEL)sel 。这些方法使用前提是相关方法的实现代码已经写好了,只等着运行的时候动态插入。代码1
  • 备援接收者
    • -(id)forwardingTargetForSelector:(SEL)aSelector,当前接收者能否找到备援对象,将其返回,否则返回nil。无法操作经由这一步所转发的消息。
  • 完整的消息转发机制
    • -(void)forwardInvocation:(NSInvocation *)anInvocation ,首先创建NSInvocation对象,把尚未处理的选择子,调用者和参数封装起来。比较有用的实现方法是:以某种方式改变消息内容,比如追加一个参数或者改变选择子。当实现此方法时,若调用操作不应由本类处理,则继承体系中每个类有机会处理此调用请求,直至NSObject,如果还是没有则抛出异常,结束。关于NSINvocation https://www.jianshu.com/p/06b832b10283
  • 消息转发全流程
    • resolveInstanceMethod—>forwardingTargetForSelector—>forwardInvocation
  • 例子,代码2
    • 由开发者添加属性定义,并声明为@dynamic,而类会自动处理相关属性值的存放于获取操作
    • class_addMethod方法,可以向类中动态的添加方法,用以处理给定的选择子

第十三条:用“方法调配技术”调试“黑盒方法”

  • “方法调配”:使用另一份实现来替换原有的方法实现(即给定的选择子对应的方法在运行期改变),开发者常用此技术向原有实现中添加新功能。
  • IMP:函数指针。类的方法列表会将选择子映射在相关方法实现上,使得“动态消息派发系统”根据此找到应该调用的方法。这些方法就是函数指针。函数原型:id ( * IMP) (id, SEL, …)
  • 互换两个方法实现
    • void method_exchangeImplementations(Method m1, Method m2)//两个参数表示待交换的方法
    • Method class_getInstanceMethod(Class aClass, SEL aSelector)//方法实现通过下列函数获得
    • 例如:Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString)); Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString)); method_exchangeImplementations(originalMethod, swappedMethod); //这样在NSString上调用,那么执行的是uppercaseString的原有实现
  • 可以通过“方法调配”为既有方法实现增添新功能,代码3
    • 通过此方法可以为那些“完全不知道具体实现的”黑盒方法增加日志记录功能,有助于程序调试。尽量不要滥用,否则不易懂且难于维护。

第十四条:理解“类对象”的用意

  • 在运行期检视对象类型,叫做类型信息查询
  • oc中的对象,具有类型信息,能接受消息
  • 将objc_object理解为对象,将objc_class理解为类,每个对象都是一个类的实例
typedef struct objc_class *Class;
	typedef struct objc_object {
  		Class isa;  //该变量定义了对象所属的类
	} *id;
  • 以下是class的实现,类的实现包含了isa指针,它也能接受消息(即类方法),所以:每个类也是一个对象
    • 此结构体存放类的“元数据”,例如类的实例实现了几个方法,具备的实例变量等信息
    • 结构体中含有变量super_class,定义了本类的超类
    • 元类
      • 类对象所属的类型是另一个类,叫做元类,即metaclass
      • 元类用来表述类对象本身所具备的元数据,例如类方法,可以理解为对象的实例方法
      • 元类也是OC对象,包含isa指针,但指向自己
typedef stuct objc_class *Class;
	struct objc_class {
  		Class isa;
  		Class super_class;
  		const char *name;
  		struct objc_method_list **methodLists;
  		// ...略
	};
super_class指针确立了继承关系,而isa指针描述了实例所属的类
  • isMemberOfClass:判断对象是否为某个特定类的实例
    isKindOfClass:判断对象是否为某类或其派生类的实例-
NSMutableDictionary *dict = [NSMutableDictionary new];
	[dict isMemberOfClass: [NSDictionary class]];//<NO
	[dict isMemberOfClass: [NSMutableDictionary class]]//<YES
	[dict isKindOfClass: [NSDictionary class]]//<YES
	[dict isKindOfClass: [NSMutableDictionary class]]//<NO

使用isa指针获取对象所属的类,然后通过super_class指针在继承体系中游走

  • 有时可以用类对象是否等同来做,使用“==”而不是“isEqual:”。因为类对象是“单例”,在程序中,每个类仅有一个实例。
  • 但是,尽量使用“类型信息查询”的方法来做,因为某些对象可能实现了“消息转发机制”。
  • 如果某个对象将收到的选择子都转发给另一个对象,这样叫做“代理”,以NSProxy为根类。如果在这种代理对象上调用class的方法,则返回发起类的对象;如果改用“isKindOfClass”则返回接受代理的对象。

第十五条:用前缀避免命名空间冲突

  • Objective-C没有内置命名空间,所以起名时尽量避免命名冲突
  • 解决办法为所有名称加上适当前缀,直接选用的前缀应该是三个字母(因为Apple宣称其保留使用所有“两字母前缀”)
  • 在编译好的目标文件里纯C函数和全局变量算作“顶级符号”,应当格外注意,加上前缀可减少重复几率,并且知道源自哪块代码
  • 如过开发过程引用了第三方库,应当为其中的名称加上前缀,这样就可以随意使用自己引入的库

第十六条:提供“全能初始化方法”

  • 可为对象提供必要信息以便其能完成工作的初始化方法叫做“全能初始化方法”。
  • 作为全能初始化方法,其余的初始化方法都可以调用它。只有在全能初始化方法中,才会存储内部数据,当底层数据改变时,只需修改此代码就好(由单个数据成为结构体)。
  • 抛出异常的方法:
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Must use initWithWidth: andHeight: instead." userInfo:nil]; 
  • 若全能初始化方法与超类不同,则需要覆写超类中的对应方法。全能初始化的调用链一定要维系。
  • 每个子类的全能初始化方法都应该调用其超类的对应方法,并逐层向上 。 if( self = [super initWithCode : deode] )
  • NSCoding协议,提供了“序列化机制”,对象可依次指明自身的编码及解码方式。Mac OS X的AppKit与IOS的UIKit框架都在用此机制,将对象序列化,并保存至XML格式的“NIB”文件中。这些NIB文件通常用来存放试图控制器及视图布局。加载NIB文件系统会在解压的过程中解码试图控制器。

第十七条:实现description方法

  • NSLog(@“object = %@”, object); object对象会收到description消息,返回的描述信息将取代“格式字符串”
-(NSString *)description{
	return [NSString stringWithFormat:@"<%@: %p, \"%@ %@\">",[self class], self, _firstName, _lastName];
}

在新实现的description方法中,也应该像默认的那样,打印出类的名字和指针地址

  • 可以自己实现description方法,使之返回更有用的信息来描述实例 。 -(NSString )description{/…*/}。可以借助字典包含在返回对象中实现精简的信息输出,这样也更易维护
  • 如果想打印出更详尽的描述信息,应实现debugDescription方法,是开发者在调试器中以控制台命令打印对象时调用
  • 这时需要简单了解lldb的简单语法。。。。。。
  • 当执行到 NSLog(@“person = %@”,person); 时加入断点,在(lldb)后输入 po person/为需要打印的对象/,则返回信息
person = <EOCPerson: 0x712>
(lldb) po person
(EOCPerson *) $1 = 0x712 <EOCPerson: 0x712, "Bob Smith">
-(NSString *)debugDescription{
	return [NSString stringWithFormat:@"<%@: %p, \"%@ %@\">", [self Class], self, _firstName, _lastName];
 }

第十八条:尽量使用不可变对象

  • 尽量使用不可变对象,把对外公布的属性设为只读。如果想修改对象内部的数据,办法一:将readdonly重新声明为readwrite,但可能会产生线程问题,所以通过“派发队列”等手段(41条)。办法二:重新声明readwrite这一操作写于“class-continuation分类”,在公共接口重新声明。
  • 当修改属性为readonly后,并不是只能在类内部修改了。可以通过KVC设置属性值 [pointOfInterest setValue: @“abc” forKey: @“identifier”];编译器会找"setIdentfier"并修改属性,但这样不推荐使用。
  • 不要将可变的数据(collection)作为属性公开,可提供一个readonly属性的对象供外界使用。可添加相关方法,修改对象的可变collection。
  • 开发者不宜从底层直接修改对象中的数据(比如直接返回NSMutable的,直接修改这个,不借助相关)
    如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

第十九条:使用清晰而协调的命名方式

  • 起名时应该言简意赅(不宜太啰嗦或是不清晰),从左至右读起来像句子一样)
  • 为方法起名时
    • 1⃣如果返回值是新建的,那么首个词应该是返回值类型,除非前面还有修饰语
    • 2⃣如果方法要在当前对象上执行操作,那么应该包含动词(hasPrefix:)
    • 3⃣Boolean属性应加is或has前缀,根据功能
    • 4⃣不要使用缩略后的类型名称
    • 将get前缀留给借由“输出参数”来保存返回值的方法。例如NSString中的
-(void)getCharacters:(unichar*)buffer range:(NSRange)aRange//参数buffer指向一个足够大的数组,用来容纳所请求范围内的字符
  • 在类与协议名称上加上前缀。如果从其他框架继承的子类,类名末尾加上超类(从UIView中继承,末尾必须时View)
  • 创建自定义委托协议,名称应包含委托放发起方的名称,后面再跟上Delegate

第二十条:为私有方法名加前缀

  • 应当为私有方法加上前缀,有助于和公共方法区分。
  • 作者所用“p_”为前缀,“_”后为真正的方法名。如果子类所继承的既不是苹果公司的框架,也不是自己的,那么建议使用类名前缀作为私有方法的前缀,避免冲突。
  • 不使用单独的“_”作为前缀,这是预留给苹果公司使用的

第二十一条:理解Objective-C错误模型

  • 当程序中有异常时我们往往会抛出错误,但这样本应在作用域释放的对象则不会被释放。所以只有在发生了可使整个程序崩溃的严重错误时,才使用异常
  • -fobjc-arc-exceptions,想生成异常安全的代码时,可以打开指定的OC文件的这个选项。如果开启了该选项,ARC会额外为异常中的对象申请和释放操作添加代码,保证异常中ARC管理的对象也不会造成内存泄漏。但这样会生成大量平常用不到的代码,即使不抛出异常也会执行这部分的代码。https://blog.csdn.net/mydo/article/details/46646003
  • 所以对于一般错误,我们使用nil/0(nil),或者NSError
  • NSError一般有三个信息:
    • 1⃣domain:错误范围,字符串
    • 2⃣code:错误码,整数
    • 3⃣info:用户信息,字典
  • NSError
    • 第一种用法:通过委托协议来传递错误。
    • 第二种:经由输出参数返回给调用者 - (Bool)doSomething: (NSError **)error 如果不关心具体错误,直接捕获Bool类型就好。如果关心则改变传入的error。例如下:
- (Bool)doSomething: (NSError **)error{//使用ARC时,编译器会将方法签名的NSError**转换成NSError*__autoreleasing* 这样指针所指的对象会在执行完毕后自动释放。
//do something that may cause an error
    if ( /* there was an error */ ){
        if ( error ){//保证error不是nil
            *error = [ NSError errorWithDomain : domain code: code userInfo: userInfo ];
//按照具体错误情况填写//最好自己制定一个“错误范围”字符串,和枚举类型的错误码
//domain:错误范围  code:错误码  userInfo:用户信息
        }
        return NO;//表明失败
    }else{
        return YES;//表明成功
    }
}

第二十二条:理解NSCopying协议

  • 如果想令自己的支持类具有拷贝操作,需要自己实现的 - (id)copyWithZone: (NSZone *)zone 以EOCPerson为例:
- (id)copyWithZone: (NSZone *)zone{
	EOCPerson *copy = [ [ [ self class ] allocWithZone: zone ] initWithFirstName: _firstName andLastName: _lastName];
	//通常情况下,应该采用全能初始化来初始化待拷贝对象
	copy -> _friends = [ _friends mutableCopy ];
	//在需要拷贝set时,如果不拷贝改变一个则另一个也改变。若是set为不可变的,那么没有必要复制
	return copy;
}
  • 如果自定义的对象分为可变与不可变版本,那么就要同时实现NSCopy和NSMutableCopying协议。这样为了在不同版本之间自由切换,以数组为例:[ NSMutableArray copy ]=>NSArray [ NSArray mutableCopy ]=>NSMutableArray
  • 深拷贝就是在拷贝对象时,将底层数据也一并复制。一般默认浅拷贝,只拷贝容器对象本身,而不复制其中的数据。因为不是所有数据都可以拷贝,并且调用者也并非想拷贝每一个对象。
  • NSSet提供了一个深拷贝方法
    • -(id) initWithSet: (NSArray *)array copyItem: (BOOL)copyItems//当copyItems设置为YES时,向数组的每个元素发送copy消息,并创建新的set
  • 如果自己所写的对象需要深拷贝,那么新增一个专门执行深拷贝的方法。遵从NSCopying协议,大多数执行的是浅拷贝。

第二十七条:使用“class-continuation分类”隐藏实现细节

  • “class-continuation”类写在.m文件中,是真正的私有,如果在公共接口中定义,即使标注为private,也还是会泄露实现细节
  • 在编写objective-c++代码时,在EOCClass头文件中引入含有c++的头文件时,一旦某个文件包含EOCClass,则也就是引入含有c++的文件,必须内部转化为c++才有效,这样的化程序很容易失控。所以写在“class-continuation”中,这样头文件就没有c++代码,使用头文件的人也很难意识到底层混有c++成分
  • 某属性在主接口中声明为“只读”,而类的内部又要设置方法修改此属性,就在“class-continuation”中将其扩展为“可读写”。这样在实现代码中可以调用setter或getter方法以及“点语法”来设置属性,外部又无权修改。
  • 私有方法可以写在里面,这样可以把类里所含的相关方法统一描述于此,记得加上前缀以体现私有性(见20条)。
  • 私有协议想不为外界所知,也在分类中声明。

第二十九条:理解引用计数

  • 对象创建出来之后,引用计数至少为1
  • 对象(A)如果持有指向其他对象(B)的强引用,那么A就“拥有”B
  • 调用alloc方法,表达了想令对象继续存活下去的意愿,但并不是说,对象此时的保留计数一定为1,因为在alloc或者init方法中,或许还有其他对象保留此对象,所以引用计数可能大于1,但至少为1
  • 绝不应该说保留计数一定是某值,只能说你所执行的操作递增还是递减了该计数
  • 为避免无意间使用无效对象,一般调用完release之后都会清空指针,否则为“悬垂指针”
[number release];
number = nil;

第三十条:以ARC简化引用计数

  • “静态分析器”:用于指明促销中引用计数出现的地方
  • 使用这四种名称开头的方法。返回的对象归调用方所有(alloc、new、copy、mutablecopy)
  • objc_autoreleaseReturnValue:将对象进行autorelease进入放生池(会查看接下来是否retain,并设置标记位,不执行autorelease操作)
  • objc_retainAutoleaseReturnValue:对象进行retain,查看是否有标记位,有则不执行retain操作,直接返回对象,清空标记位
  • __autoreleasing:将对象“按引用传递”给方法,使用此修饰符。此值在方法返回时自动释放
  • ARC清理实例变量:借用Objective-C++的“清理例程”,
    • 回收Objective-C++对象,会调用C++对象的析构函数,生成名为.cxx_destruct方法
    • 在非Objective-C对象下,如CoreFoundation对象,不归ARC管理,需开发者适时调用CFRetain/CFRelease
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值