iOS笔记篇-熟悉OC<三>
前言
首先回顾下前面两篇文章的内容。
- 熟悉
OC<一>中提到,在OC语言的起源,OC语言为C语言的超集,所以可以容纳所有的C语言的语法。以及OC语言所采用的消息结构。从语言特性上来说对于引入头文件可以使用向前声明,少用#define以及多用枚举等内容。 - 熟悉OC<二>围绕着对象、消息还有运行期详细阐述了类和对象的本质,消息分发系统的工作过程,以及OC作为动态绑定的语言,具备的语言特点。
接下来文章将从OC语言的重点语法方面进行总结,以及好的编程风格和注意事项进行一些归纳。
第一、 命名和API的设计
1. 为何要注重命名
- OC是“动态绑定”的语言,采用“消息结构”,OC中没有命名空间的概念,所以OC语言的命名相对于JAVA语言和C++等面向对象的语言在命名上就需要更加注意,以解决命名冲突的问题。
- 另外更好的命令,能够让代码更加易读。这是一个优秀程序员的基本素养。
2. 命名的注意事项
-
首先新建Xcode工程的时候,如果你用过版本早点的Code应该会发现现在新建的工程没有出现Classes Prefix的输入项了,到哪里去了,请看下图。
Classes Prefix
设置Classes Prefix - 关于上面提到的类前缀,一般选择与公司、应用程序相关联的为宜。而且应该一般选用3个大写字母。(PS: 因为Apple官方宣传其保留使用所有"两个字母前缀"的权利,所以一般我们选择3个字母。 比如:你自己写了一个网络请求TWRequest,然后刚好过了一段时间Apple官方推出新的框架TWRequest专门用来封装请求,那么就会出现冲突)
- ClassesPrefix需要增加前缀,此外对于分类Category也需要。避免和他人的代码整合的情况下出现冲突。
- 如果自己所开发的应用程序中用到了第三方库,那么请也其前面加上前缀,虽然麻烦,但是若能避免潜在命名冲突还是值得的。(如果是通过cocoapods集成的,那么笔者认为只需要的md文件上说明让用户去集成也是不错的)。
- OC的方法名虽然繁冗,但是十分清晰,从C++或者JAVA切换过来,也许刚开始不好适应,但是也请写完整。如下例子中OC的方法名虽然很长,但是意思更加明确,可读性更强。
//比如
Rectangle* aCppRectangle = new Rectangle(5.0f, 10.0f); //其他语言
Rectangle* aOcRectangle = [[Rectangle alloc] initWithWidth:5.0f
height:10.0f]; //OC语言
- 关于私有方法的命名,OC是语言是没有办法将某个方法标志为私有,每个对象都可以响应任何的消息。所以开发者应该在命名惯例上体现出“私有方法”的语义。由于苹果公司喜欢对于私有的变量和方法采用下划线开头,所以笔者认为,对于私有的方法,我们应该采用类似于“p_”的前缀,以避免覆盖父类的私有方法。
- 如果方法的返回值是新创建的,那么方法名的首个词应该返回值的类型,除非前面还有修饰语。
- 应该把表示参数类型的名词放在参数的前面。
- 如果方法要在当前的对象上执行操作,那么应该包含动词,如果还需要参数,则应该在动词后面加上一个或者多个名词。
- 尽量不要使用缩写而是使用全称。‘
- Boolean类型的属性返回应用is前缀,如果方法的返回是非属性,那么使用is或者has
- 将get这个前缀留给那些借由"输出参数"来保存返回值的方法。比如:把返回值填充到"C语言数组"中就可以使用该方法。
- 对于类和协议的命名,应该言简意赅,从左到右读起来像个日常用语最佳。
3. 设计类的时候尽可能使用不可变对象
也就是最好把一些不必要让外界改变的属性,设置成readonly。虽然根据KVC的特性,外界依然可以通过如下方法来改变数值,绕过了API方法,但是得自己承担可能出现的问题。
此外设置成为readonly对于实现的代码块里面怎么进行属性访问,这个我们可以在"classes-continuatioon"分类里面将其转换成readwrite。
最好不要把可变collection作为属性公开,而应提供相关的方法,以此修改对象中的可变collection。
[Person setValue:@"Marray" forKey:@"Name"]
4. 调试和异常处理
以下两个在NSObject协议上的方法分别对应于NSLog的打印,以及通过调试器(LLDB)的命令行输出打印。
description
debugDescription
OC语言中异常只用于处理严重的错误(fatal error, 致命错误),那么对其他错误(nonfatal error),OC中所采用的编程规范是令该方法返回nil/0,或者使用NSError,以表明其中错误的发生。
NSError的用法可以封装成三条信息
Error domain: 错误发生的根源,通常用一个全局变量来定义。比如:"处理URL的子系统(URL-handling subsytem)"在从URL中解析或取得数据是出错了,那么久会使用NSURLErroDomain来表示错误范围。
Error code:(错误码,类型为整数),特有的是错误代码。通常也用enum来表示。例如HTTP请求,就可以选用HTTP的状态码,来作为错误码。
User info(用户信息,类型为字典类型),对于错误的本地化描述。或许还含有导致错误发生的另外一个错误,经由此种信息,尅将相关的错误串成一个错误链。
在错误不那么严重的情况,可以指派“委托方法”来处理错误,也可以通过把错误信息放在NSError对象中,经“输出参数”返回给调用者。
5. NSCopying协议
- 若想令自己缩写的对象具有拷贝功能,则需是实现NSCopyint协议
如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopying与NSMutableCopyint协议 - 复制对象时需要决定采用浅拷贝还是深拷贝,一般情况下是浅拷贝。
- 如果你缩写的对象需要深拷贝,那么可考虑新增一个专门的执行拷贝的方法。
第二、协议和分类
1. 关于协议
协议代理的设计模式可谓老生常谈,这里就不详细论述了,只指出几个重要的点。
- 为了不产生"保留环"的问题,代理类型一定存储模式一定要写成 weak 或者 unsafe_unretain 当然后者的话就需要在对象销毁后进行清空。weak有系统自动清空的功能,详细见《熟悉OC一》。
[_delegate responsesToSelector:@selector(XXXMethod)]
- 如上对于delegate中判定该方法是否存在非常重要,因为协议的optional类型,代理对象虽然遵守协议但是未必有实现对应的方法。
必要的时候,可实现包含位段的结构体,将委托对象是否能响应协议的方法缓存至其中。因为一般,代理对象是不会轻易的改变的。
struct data{
unsigned int didReceiveData : 1;
unsigned int didFailWithError : 1;
unsigned int didUpdateProgresTo : 1;
}_delegateFlag;
//从结构体上可以看出didReceiveData均分别是占用1位的二进制位,所以只能取值0或者1,适合存放bool类型
2. 协议活学活用举例
- 协议可在某种程度上提供匿名类型。具体的对象类型可以淡化成遵守某个协议的id类型
- 使用匿名对象隐藏对象类型或者(类名)
- 如果具体的类型不重要,重要的是对象能够响应(定义在协议里的)特定方法,那么可以使用匿名对象来表示。
3. 关于分类的优势
a. 使用分类机制可以把类的实现代码划分成多个易于管理的小块。
甚至可以将"私有"的方法归入名字叫做private的分类中,以隐藏实现细节。
b. 使用分类方便调试,比如:将对应的功能归纳到指定的分类,那么到时候编译打包生成的符号表,将会是
[Person(Property) setCountry:@"China"];
很方便可以知道问题的所在区域。
c. 如果某个属性在主接口上声明为"只读",而在类的内部又要用设置方法来设置此属性,那么就在class-continuation中将其拓展为"可读写"
把私有方法原型声明在"class-continuation"分类中。
d. 若是想使用的类,所遵守的协议不为人所知,则可于"class-continuation分类"中声明
4. 使用分类应用的注意事项
- 给第三方类中添加分类时,总是应该给其名称和方法上加上属于你专用的前缀。
- 虽然从技术上来讲可以在分类上新增属性,但是这样会使内存管理容易出错。
- 将封装数据的所有的属性都定义在主接口里
- 在class-continuation分类之外的其他分类,尽可能不要定义属性。
- 通过class-continuation分类中可以添加实例变量。
- 如果某个属性在主接口上声明为"只读",而在类的内部又要用设置方法来设置此属性,那么就在class-continuation中将其拓展为"可读写"
把私有方法原型声明在"class-continuation"分类中。 - 若是想使用的类,所遵守的协议不为人所知,则可于"class-continuation分类"中声明