因为OC中没有其他语言那种内置的命名空间机制,所以我们在对文件命名时要十分的注意,若是发生重名冲突,那么应用程序相应的链接过程就会出错,导致运行文件不知道究竟该调用那个文件,因为其中出现了重复的符号。例如:
这给错误就是因为something.o
和something_else.o
中都出现了各自实现的EOCTheClass
类,导致编译器无法识别,而出错了。
当然,不仅仅是类文件中可能出现这种错误,程序库中也可能出现这种错误,所以为了避免这种错误,我们唯一的办法就是自己手动的实现命名空间,即为所有的名称都加上适当的前缀,用以分明各种文件。
并且每个类中相应的方法也应该有该类相应的前缀,用以分别该类和其他类中的名称相同的方法。这样做还有个好处就是:若此符号出现在栈回溯信息中,那么我们就很容易就能判断问题出现在那个类的哪个方法了。
“全能初始化方法”就是一个初始化方法,但是他的类创建对象的时候无论用什么初始化方法创建,其中代码都会调用某个初始化方法,那么这个初始化方法就是“全能初始化方法”。例如:
上述的各种初始化方法都调用了initWithTimeIntervalSinceReferenceDate:
虽然我们看不到内部情况,但是这个方法就是“全能初始化方法”。并且,只有在全能初始化方法中,才会存储内部数据,其他的方法只是在存储内部数据后再进行了一些其他操作而已。
看到这里,你可能就突然发现了问题,那么到底该怎么初始化方法呢?
经过上述的说明,应该大概了解了“全能初始化方法”,我们知道只有在全能初始化方法中,才会存储内部数据,所以我们要重写初始化方法就一定离不开调用之前的“全能初始化方法”。
大概就是你首先需要在你新的初始化方法中的调用这个类的“全能初始化方法”,如果他是继承关系的话,那么就使用super
来调用父类的初始化方法,调用完了之后你就可以编辑你自己的想要实现的初始化方法了,反正总而言之,你全能初始化方法的调用链一定要维持的。
注意: 如果子类的全能初始化方法与超类方法的名称不同,我们总应覆写超类的全能初始化方法,避免子类调用父类的全能初始化方法。
但是,有时候我们可能不想覆写超类的全能初始化方法,因为那样做没有道理,那么我们就可以覆写超类的全能初始化方法,让在调用这个方法的时候抛出异常,这样就可以确保用户一定使用的是你自己定义的方法了,但是我觉得这样的意义并没有多大,唯一可能用到这种方法的,就是你想让程序给初始化方法传入另一个参数,而不传入之前的的数据类型,这也是要到万不得已的时候才使用的方法。
通常我们遇到的类都只有一个全能初始化方法,但是偶尔也有类具有多个全能初始化方法,例如:我们熟知的UI控件,通常我们都是使用代码来初始化的,但是我们还可以使用NIB拖动进行初始化,两种初始化解码的方法是不相同的。这个时候问题就来了,我们初始化方法到底该怎么去写?
由于这两种初始化方法的解码方式不同,而且我们也不能人为的改变其解码的方式,那么我们就只能顺其自然,他有两种我们也重写两种初始化方法,注意: 重写的这两种初始化方法一定是分别调用过之前的两种全能初始化方法的,并且表明这两种新的初始化方法分别适用于那种情况。
反正,总的来说,我们就是要维持原来类的调用链,每个子类的全能初始化方法都应该调用其超类的对应方法,并逐层向上。因为其父类有两个全能初始化方法,这两种初始化方法定义出来的数据可能是不同的,若是你在子类中调用了错误的父类初始化方法,它就会可能因为数据类型的问题使程序发生错误。
我们在调试程序的的时候经常要打印并查看对象信息。此时我们就可以用到description
方法,其实**description就是对这个对象本身的描述。**例如:
这里的description
描述的内容就会替代这个%@
。又例如:
则会输出:
然而,如果是一个自定义的类,那么它就会输出该类的地址:
这是因为你并没有覆写其description
方法,只有在自己的类里覆写description
方法,这个对象的描述才会改变,否则打印信息时就会调用NSObject
类所实现的默认方法。在这个description
方法中,你可以输出你想要该对象输出的内容,例如:
它就会输出:
我建议就是,实现description
方法就和系统的那种描述方法差不多就行了,就跟上述说的Array
差不多就行了。并且,description
没有什么固定的套路输出什么,就比如,你一个字符串的description
和一个数组的description
肯定是不一样的,你肯定得根据你要输出的对象来考虑description
方法的。
在自定义的description
方法中,把待打印的信息放到字典里面,然后将字典对象的description
方法所输出的内容包含在字符串里并返回,这样就可以实现简易的信息输出方式了,例如:
这样写的好处就是,可以令代码更易维护,并且如果你向该类中新增属性的话,你就在description
方法中增加就行了。
这个也是一种描述方法,和description
差不多,就是描述的位置不一样,description
是在函数调用类的时候触发方法才输出的,而debugDescription
是在控制台中使用命令打印该对象时才调用的。当然加断点查看时也可以看到debugDescription
的描述。
如果你在description
不想将一些内容输出的话,你就可以将那些数据写在debugDescription
中,让程序员自己调试时可以方便的看到这些数据,而description
方法就输出你想要让用户看到的信息就行了。
不可变对象,我们第一时间想到的肯定是不可变数组那种不可变对象,但是这里的不可变不是这样的,它指的是这个类里边的属性是不能直接被修改的,要实现这种功能,我们就需要用到我们的readonly
(只读)修饰符。默认情况下,属性是readwrite
(即可读又可写)的,这样修饰出来的类都是“可变的”。就像这样:
有时可能想修改封装在对象内部的数据,但是却不想令这些数据为外人所改动。这种情况下,通常的做法是在对象内部将readonly
属性重新声明为readwrite
。当然,如果该属性是nonatomic
的,那么这样做可能会产生“竞争条件”,即在对象内部写入某属性时,对象外的观察者也许正读取该属性。若想避免此问题,我们可以在必要时通过“派发队列”等手段,将(包括对象内部的)所有数据存取操作都设为同步操作。就像这样:
现在,这个属性就只能用在实现代码内部设置这些属性了,但其实,在对象外部还可以通过“键值编码”技术来设置这些属性,就像“setValue:forKey:
”方法。“点语法”也可以,因为点语法就是调用set
方法的。这样做虽说可以改动,但是却违背了本心,还会导致数据不同而出现问题,所以不建议更改。
我们还可以通过readonly
来设置一个特殊的属性,就是该属性在外部看是不可变、不可设置的,但是内部的实现其实又定义了一个可变的该数据类型的变量,当用户访问这个属性的时候,我们覆盖在.h
处的该属性的get
方法,让其返回在实现部分定义的变量的拷贝结果,这样在外部看来这个变量就好像是不可变的似的,我们可以通过相应的方法实现对该变量的增加删除。
当然我们还可以直接设置为可变的类型,但是这样的话外部就可以直接修改了,不建议这样,同时,通过这个我们可以得出:不要在返回的对象上查询类型以确定其是否可变。因为实现部分和定义部分可能返回的是不同的类型。
就是说,命名时要简洁明了,让用户直接知道该方法是怎么使用的,这样的好处就是,代码读起来像日常语言里的句子。我们通常使用“驼峰命名法”,就是以小写字母开头,其后每个单词首字母大写,不论是类还是属性都可以这样命名。
应该为类与协议的名称加上前缀,以避免命名空间冲突,而且应该像给方法起名时那样把词句组织好,使其从左至右读起来较为通顺。基本命名规则就是:命名方式应该一致,如果要从其他的类中继承子类,那么就要遵守其原本的命名惯例。 例如:UIView
它的子类就应该是***View
,表明其来历。
通常我们在写方法时,并没有对其进行私有共有分类,导致调试时可能很麻烦,现在为私有方法加上前缀,这样便于修改方法或方法签名。具体加什么来代表私有方法因人而异,自己怎么舒服怎么来,唯一注意的是:一定不要只使用_
作为前缀,用p_
都比那个好,因为苹果公司使用的就是_
作为私有方法的前缀的,你自己定义的私有方法名有可能就会和人家自带的冲突。
很多编程语言都有“异常”机制,通过异常机制来处理错误,当然OC中也有。
首先我们要注意的是,“自动引用计数”在默认情况下不是“异常安全的”,就是说,如果抛出异常,那么本应该在作用域末尾释放的对象现在却不会释放了,这样就会造成内存泄漏问题,如果想生成“异常安全”的代码,可以通过设置编译器的标志来实现,不过这将引入一些额外的代码,在不抛出异常时,也照样要执行这部分代码。需要打开的编译器标志叫做-fobjc-arc-exceptions
。
OC语言现在所采用的办法是:只在极其罕见的情况下抛出异常,异常抛出之后,无须考虑恢复问题,而且应用程序此时也应该退出。这就是说,不用再编写复杂的“异常安全”代码了。
既然异常只用于处理严重错误,那么对其他错误怎么办?在出现“不那么严重的错误”时,OC语言所用的编程范式为:令方法返回nil/0
,或是使用NSError
,以表明其中有错误发生。像我们之前都用过的网络请求,其中就使用到了NSError
来表示错误。
NSError
的另一种常见的用法是:经由方法的“输出参数”返回给调用者。就是说用一个方法来判断你传过去的error
是否真的有错误,返回Boolean
值,之后你就可以根据error
是否有内容,或者Boolean
值来决定处理的代码和不处理的代码。就像这样:
doSomething:
会处理一些事,并且还会将出错的问题返回给error
指针传回给调用者,并且还会返回一个Boolean
值给ret
。
若你不想知道其出错的问题,那么就可以直接这样写。
实际上,在使用ARC时,编译器会把方法签名中的NSError**
转换成NSError*__autoreleasing*
,也就是说,指针所指的对象会在方法执行完毕后自动释放。因为doSomething:
不能保证其调用者可以把此方法中创建的NSError
释放掉。
NSError
对象里的“错误范围”、“错误码”、“用户信息”等部分应该按照具体的错误情况填入适当的内容。这样的话,调用者就可以根据错误的类型分别处理各种错误了。错误范围应该定义成NSString
型的全局变量,而错误码则定义成枚举类型为佳。
我们经常会使用copy
函数,但是若是你自定义的类,他自己就不会实现这个函数,此时就需要你自己来实现了,要实现copy函数就的实现NSCopying协议,该协议只有一个方法:
- (id)copyWithZone:(NSZone *)zone;
因为以前开发程序时,会据此把内存分成不同的“区”,而对象会创建在某个区里。现在不用了,每个程序只有一个区:“默认区”。所以说,尽管必须实现这个方法,到那时你不必担心其中的zone
参数。
copy
方法由NSObject
实现,该方法只是以“默认区”为参数来调用“copyWithZone:
”。所以要实现copy
函数,他才是关键。
想要重写copy
函数,要声明该类遵从NSCopying
协议,并实现其中的方法即可,例如:
其实拷贝就是创建一个新的变量,并将其初始化内容为你要拷贝变量的内容,最终再返回这个新的变量。这里初始化最好使用全能初始化方法。
并且,无论当前实例是否可变,若需获取其可变版本的拷贝,均应调用mutableCopy
方法,同理,若需要不可变的拷贝,则总应通过copy
方法来获取。
我们通常把拷贝方法称为copy
而非immutableCopy
的原因在于,NSCopying
不仅设计给那些具有可变版本和不可变版本的类来使用,而且还要供其他一些类来使用,而那些类没有“可变”与“不可变”之分,所以说,把拷贝方法叫成immutableCopy
不合适。
就是说若是你有一个自定义的类,其中有可变和不可变的类型,那我们肯定就是可变的用可变拷贝不可变的就用不可变的拷贝,那这就不能叫做纯正的immutableCopy
了。
深拷贝:在拷贝对象自身时,将其底层数据也一并复制过去。
浅拷贝:在拷贝对象自身时,只拷贝容器对象本身,而不复制其中的数据。
原对象类型 | 拷贝方式 | 目标对象类型 | 拷贝类型(深/浅) |
---|---|---|---|
mutable对象 | copy | 不可变 | 深拷贝 |
mutable对象 | mutableCopy | 可变 | 深拷贝 |
immutable对象 | copy | 不可变 | 浅拷贝 |
immutable对象 | mutableCopy | 可变 | 深拷贝 |