文章目录
- 前言
- 15.用前缀避免命名空间冲突
- 要点总结
- 16.提供全能初始化方法
- 全能初始化
- 要点
- 17.实现description方法
- description
- 以字典形式输出description
- debugDescription
- 要点
- 18.尽量使用不可变对象
- 要点
- 19.使用清晰协调的命名方式
- 方法命名
- 类与协议命名
- 要点
- 20.为私有方法名加前缀
- 21.理解Objective-C的错误类型
- ARC的异常安全
- NSError
- NSError用法
- 要点
- 22.理解NSCopying协议
- NSCopying
- 深浅拷贝的使用时机
- 要点
- 总结
前言
- 继续学习Effective_Objective-C。这一章的简介就是在于让开发者熟练的掌握OC语言的编程规范。
15.用前缀避免命名空间冲突
- OC语言中没有其他语言那种内置的命名空间机制,所以我们在对文件命名时要十分的注意,若是发生重名冲突,那么应用程序相应的链接过程就会出错,导致运行文件不知道究竟该调用那个文件,因为其中出现了重复的符号。
- 也就是理解成命名的时候需要加上前缀,比如一个项目里存在很多歌ViewController的子类,那么在命名的时候需要分清楚子类的名称,加上前缀总是好的
要点总结
- 选择与你的公司、应用程序或者二者皆有关联的名称作为类名的前缀,并在所有代码中均使用这一前缀。
- 若是自己所开发的程序库中用到了第三方库,则应为其中的名称加上前缀。
16.提供全能初始化方法
- OC里的所有对象都要经过初始化的过程,有时候有些对象无需开发者向其提供额外信息就能完成初始化✅,但是有时候系统必须为对象提供必要的信息帮助其完成初始化的方法叫做“全能初始化”。
全能初始化
- 如果创建类实例的方式不止一种,那么这个类就会有许多个初始化方法。在这个过程里有一个方法使得其他方法初始化的时候必须调用它,那么这个方法称“全能初始化方法”。
- 只有在全能初始化方法中,才会存储内部数据,其他的方法只是在存储内部数据后再进行了一些其他操作而已
要点
- 在类中提供一个全能初始化方法,并与文档里指明。其他初始化方法均应调用此方法。
- 若全能初始化方法与超类不同,则需覆写超类中的对应方法。
- 如果超类的初始化方法不适用于子类,那么应该覆写这个超类方法,并在其中抛出异常。
- 一个类有多个全能初始化方法要注意,我们就是要维持原来类的调用链,每个子类的全能初始化方法都应该调用其超类的对应方法,并逐层向上。因为其父类有两个全能初始化方法,这两种初始化方法定义出来的数据可能是不同的,若是你在子类中调用了错误的父类初始化方法,它就会可能因为数据类型的问题使程序发生错误
- 重写初始化方法也要注意如果子类的全能初始化方法与超类方法的名称不同,我们总应覆写超类的全能初始化方法,避免子类调用父类的全能初始化方法
17.实现description方法
- 这一条的主要意思就是在调试的过程里面,我们通常会选择打印在控制台或者打断点,这里对这两种调试方法进行了详细说明和建议!
description
- 调试程序的时候,打印数组信息并查看信息如下
- 在构建需要打印到日志的字符串的时候,array对象会收到来自descriprion的消息,该方法返回得到描述信息将取代“格式字符串的%@”。
- 如果自定义类呢?我尝试打印了一下button类。
- 接着自定义类
- 和button的信息相比较,上面的内容不太有用,这里就提到了可以在自己的类覆写description方法,否则打印的时候就会调用NSObject的默认方法。
- EOCPerson里有自己的名字,我们在m文件重写description打印自己需要的。这也是吗了UIButton里面的实现也是改变了,它选择打印了自己的frame layer等属性
#import "EOCPerson.h"
@interface EOCPerson : NSObject
@property (nonatomic, strong)NSString *firstName;
@end
@implementation EOCPerson
- (NSString *)description {
return [NSString stringWithFormat:@"< %p, \"%@>",self, self.firstName];
}
@end
以字典形式输出description
- 书上还介绍了以字典形式输出description,原理都是一样的,只是实现的时候多加了一步,那样看起来更加方便
debugDescription
- 在NSObject协议里面还有一个方法也是用来调试打印代码
debugDescription
。 - 二者区别在于就是描述的位置不一样,description是在函数调用类的时候触发方法才输出的,而debugDescription是在控制台中使用命令打印该对象时才调用的。当然加断点查看时也可以看到debugDescription的描述。
- 这里还交给我们了一个调试方法是 po + 类可以打印详细的信息
- 如果你在description不想将一些内容输出的话,你就可以将那些数据写在debugDescription中,让程序员自己调试时可以方便的看到这些数据,而description方法就输出你想要让用户看到的信息就行了。
要点
- 实现description方法返回一个有意义的字符串,用以描述该实例。
- 若想在调试时打印出更详尽的对象描述信息,则应实现debugDescription方法。
18.尽量使用不可变对象
- 这里的不可变对象不是使用系统提供的不可变类,而是在设置属性的时候添加上readnoly属性
- 默认情况下的属性是既可读又可写的,这样的类我们在这里成为可变类,但一般的数据未必需要改变。
- 原则上尽量把对外公布出来的属性设为只读,并且在有确定的必要的时候设为可读。
- 当我们需要修改封装在对象内部的数据的的时候而且不想数据被外人改动的时候,需要将readonly改为readwrite并且为了避免竞争条件的出现,我们可在必要的时候通过派发队列等手段将数据的存储设置为同步操作(暂时未了解)
- 虽然保持了只能在代码内部实现属性,但其实还可以通过KVC来设置属性。
要点
- 尽量创建不可变的对象。
- 若某属性仅可于对象内部修改,则在“class-continuation分类”中将其由readonly属性扩展为readwrite属性。
- 不要把可变的collection作为属性公开,而应提供相关方法,以此修改对象中的可变collection。
19.使用清晰协调的命名方式
- 清晰的命名读起来是很方便的,这个习惯需要及时的纠正,不仅自己可以读懂,其他人也可以读懂,在合作的时候显得颇为重要
方法命名
- 如果方法的返口值是新创建的,那么方法名的首个词应是返回值的类型,除非前面还有修饰语,例如localizedString。属性的存取方法不遵循这种命名方式,因为一般认为这些方法不会创建新对象,即便有时返口内部对象的一份拷贝,我们也认为那相当手原有的对象。这些存取方法应该按照其所对应的属性来命名。
- 应该把表示参数类型的名词放在参数前面。
- 如果方法要在当前对象上执行操作,那么就应该包含动词;若执行操作时还需数,
则应该在动词后面加上一个或者多名词 - 不要使str这种简称,应该用 string 这样的全称。(经典)
- Boolean 属性应加is前级。如果某方法返口非属性的 Boolean 值,那么应该根据其功
能,选用has 或is 当前缀。 - get这个前缀留给那些借由“输出” 参数,来保布返口值的方法,比如说,把返回值填充到“C语言式数组”(C-style array)里的那种方法就可以使用这个词做前缀。
类与协议命名
- 应该为类与协议的名称加上前缀,以避免命名空间冲突,而且应该像给方法起名时那样把词句组织好,使其从左至右读起来较为通顺。基本命名规则就是:命名方式应该一致,如果要从其他的类中继承子类,那么就要遵守其原本的命名惯例。 例如:UIView它的子类就应该是***View,表明其来历。
- 在写自己项目的时候就出现了这个问题,我把view放在前面了导致看的时候比较混乱,一大段的view 不知道哪个是哪个
要点
- 起名时应遵从标准的OC命名规范,这样创建出来的接口更容易为开发者所理解。
- 方法名要言简意赅,从左至右读起来要像个日常用语中的句子才好。
- 方法名里不要使用缩略后的类型名称。
- 方法名起名时的第一要务就是确保其风格与你自己的代码或所要集成的框架相符。
20.为私有方法名加前缀
- 通常我们在写方法时,并没有对其进行私有共有分类,导致调试时可能很麻烦,现在为私有方法加上前缀,这样便于修改方法或方法签名。具体加什么来代表私有方法因人而异,自己怎么舒服怎么来,唯一注意的是:一定不要只使用_作为前缀,用p_都比那个好,因为苹果公司使用的就是_作为私有方法的前缀的,你自己定义的私有方法名有可能就会和人家自带的冲突。
- 给私有方法的名称加上前缀,这样可以很容易地将其同公共方法区分开。
- 不要单用一个下划线做私有方法的前缀,因为这种做法是预留给苹果公司用的。
21.理解Objective-C的错误类型
- 对于现在的Objective-C来说只有发生了可使整个应用程序崩溃的严重错误时,才应使用异常,但是在之前代码抛出异常的时候是存在很多。
ARC的异常安全
- ARC的异常机制举例“自动引用计数”在默认情况下不是“异常安全的”,就是说,如果抛出异常,那么本应该在作用域末尾释放的对象现在却不会释放了,这样就会造成内存泄漏问题,如果想生成“异常安全”的代码,可以通过设置编译器的标志来实现,不过这将引入一些额外的代码,在不抛出异常时,也照样要执行这部分代码。需要打开的编译器标志叫做
-fobjc-arc-exceptions
-
- 对于
-fobjc-arc-exceptions
我稍稍了解了一下
- 对于
-
-
- 在ARC中异常可能会导致对象的内存泄露。因为ARC是颗粒化对象为一个文件:即可以在obj-c文件上启用ARC.所以我们可以选择性的在编译某个文件上加上-fobjc-arc-exceptions选项 如果开启了该选项,则ARC会额外为异常中的对象申请和释放操作添加代码,保证异常中ARC管理的对象也不会造成内存泄露。当然这样一来缺点就是可能会生成大量平常可能根本用不到的代码。(只有发生异常才会执行) 还需要注意的是我们可以只在必要的obj-c文件上启用-fobjc-arc-exceptions标志,而其他文件禁用该标志,这样才可以做到不浪费多余的内存和空间
-
- 所以OC语言现在的解决方法就是只在及其罕见的情况下抛出异常,异常抛出之后,无须考虑恢复问题,而且这时应用程序也应该退出了,所以无需我们去写“异常安全代码”了。
NSError
- 现在OC不会轻易的抛出异常,当然错误是随时都可能发生的,所以对于没有达到严重错误级别的错误,OC语言会返回nil 或者 0,还有就是使用NSError.以表明其中有错误发生.
- 这个是返回nil的例
-
- 在这周情况下,如果发现无法创建self,则self置nil,我们创建的时候发现创建不出来对象就会知道出错了
- NSError相比之下更加的灵活,它可以把错误的原因回报给调用者。封装如下
NSError用法
- 第一种常见就是通过委托协议传递这个错误,当前对象会把协议中的某个方法传给委托的对象,此方法常用于API的设计。
- 另一种常见用法是:经由方法的“输出参数”返回给调用者。就是说用一个方法来判断你传过去的error是否真的有错误,返回Boolean值,之后你就可以根据error是否有内容,或者Boolean值来决定处理的代码和不处理的代码
- 还可以为error参数“解引用(error所指的那个指针现在要指向一个新的NSError对象)”之前,必须先保证error参数不是nil,因为空指针解引用会导致段错误并使程序崩溃,所以注意要判断error是不是nil。
- 错误范围和错误码和用户信息等分别返回不同的类型:
-
- 调用者就可以根据错误的类型分别处理各种错误了。错误范围应该定义成NSString型的全局变量,而书上推荐错误码则定义成枚举类型为佳。
- 调用者就可以根据错误的类型分别处理各种错误了。错误范围应该定义成NSString型的全局变量,而书上推荐错误码则定义成枚举类型为佳。
要点
- 只有发生了可使整个应用程序崩溃的严重错误时,才应使用异常。
- 在错误不那么严重的情况下,可以指派“委托方法”来处理错误,也可以把错误信息放在NSError对象里,经由“输出参数”返回给调用者。
-
- NSError 对象里封装了三条信息:
-
-
Error domain(错误范围,其类型为字符串)
-
-
-
Error code(错误码,其类型为整数)
-
-
-
User info(用户信息,其类型为字典)
-
22.理解NSCopying协议
- 又遇到了Copy…
- 使用对象的时候经常觉得是否进行拷贝,深拷贝还是浅拷贝?对于这些问题在OC语言里面需要通过copy方法完成。
- 对于系统自带的容器类,字典数组集合都自带copy方法,这里需要说的是我们需要对自己自定义的类实现copy的时候,如何实现?
NSCopying
- NSCopying是NSObject的协议之一,它用于实现自定义类的copy。
- 该协议只有一个方法
-
NSZone
: 在之前开发程序的时候会吧内存分成不同的区,对象则会被创建在某个区里面,现在所有的程序只有一个“默认区”,所以无需紧要这个区
- 若想某个类实现copy功能,则需要声明NSCopying协议然后实现对应的方法。
- 需要注意如果你的类分为可变版本和不可变版本那么还需要实现NSMutableCopying协议和对应的方法
@protocol NSCopying
- (id)copyWithZone:(nullable NSZone *)zone;
@end
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
@end
- 对于不可变的
NSArray
与可变的NSMutableArray
来说,存在如下关系
[NSMutableArray copy] => NSArray
[NSArray mutableCopy] => NSMutableArray
-
- 在可变的对象实现不可变copy返回的是另外一个不可变实例,如此做是为了能在可变版本和不可变版本实现自由切换
深浅拷贝的使用时机
- 要执行“深拷贝”还是“浅拷贝”?
-
- 深拷贝:在拷贝对象自身时,将其底层数据也一并复制过去。
-
- 浅拷贝:在拷贝对象自身时,只拷贝容器对象本身,而不复制其中的数据。
- 浅拷贝:在拷贝对象自身时,只拷贝容器对象本身,而不复制其中的数据。
- 如果有需要,我们可以自行添加一个深拷贝方法。
- 这个自行实现的深拷贝也可以看出来深浅拷贝的区别,深拷贝是在堆里新开辟一块内存存放底层数据。
- 浅拷贝是只拷贝存放在栈上指向堆内存的指针。
NSArray *array = @[@"immutableCopy", @"mutableCopy"];
NSArray *immurtableArray = [array copy];
NSArray *mutableArray = [array mutableCopy];
-
- 可以看出copy出的
immuatleArray
和原array指向了同一块内存区。而mutalbeArray
在堆里新开辟一块内存存
- 可以看出copy出的
要点
- 若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议。
- 如果自定义的对象分为可变版本和不可变版本,那么就要同时实现NSCopying与NSMutableCopying协议。
- 复制对象时需决定采用浅拷贝还是深拷贝,一般情况下应该尽量执行浅拷贝。
- 如果你所写的对象需要深拷贝,那么可考虑新增一个专门执行深拷贝的方法。
总结
- 这一章学的大部分之前都了解过或者使用过但不知道原理,还是要加深印象。