《编写高质量代码:改善Objective-C程序的61个建议.html.pdf》由会员分享,可在线阅读,更多相关《编写高质量代码:改善Objective-C程序的61个建议.html.pdf(113页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、前言 如何写出高质量的代码? 我一直在思考,如何才能编写出高质量、优秀的代码,我也在不停地探寻,希望找出类似于武侠小说中所说的武功秘籍,在编写代码一途可以帮助大家走“捷径”从而达到事半功倍的效果。 道德经第四十八章中说“为学者日益,为道者日损。损之有损,以至于无为。无为而不为”。这句话是说,治学上,不要过于追求外在的经验知识,否则经验知识越积累增多,就会越僵化臃肿。 要学会透过直观体悟把握事物未分化时的状态或者内索自身虚静,洞悉其内在的道化真谛,从而简之再简。这些也就是我们现在说的“大道至简”。 治学如此,写代码更是如此。在程序员写代码的职业生涯中,前5年,他看到的只是一行一行的代码,他会为自
2、己洋洋洒洒写成的代码而陶醉;5年之后,就不是单纯地写代码了,而是在做一件艺术 品,此时的程序员就像雕刻家一样,在刻下每一刀之前,都需纵观全局,细细揣摩,落刀如有神,一气呵成。故此,写出优秀的高质量代码,需要像唐僧西天取经一样,踏踏实实,用平常心闯过一关又一 关,如此,写出高质量的代码自然就是水到渠成的事了。写代码时切忌心态浮躁,急功近利。 本书适合哪些读者 本书不是一本介绍“Objective-C”代码如何编写的入门级的书籍。故此,如果你只想初步了解一下“Objective-C”开发,而不想做深入研究的话,那么本书就不适合你了。 本书主要面向专业从事Objective-C开发或者想转向“Obj
3、ective-C”开发的研究人员,帮助其编写便于维护、执行迅速且不易出错的代码。如果你是“Objective-C”开发技术大咖,翻阅本书,对你 来说可能会有些浪费时间,故此也请你一瞥而过! 本书主要适合如下读者: 对软件开发,特别是对Objective-C开发有兴趣的人。 想成为一名专职的软件开发人员的人。 想进一步提高自己“Objective-C”技术水平的在校学生。 开设相关专业课程的大专院校的师生。 你该如何阅读本书 本书共9章,从内容上可以分3部分1。大家可以根据自身状况,选择性跳读,翻阅自己最感兴趣或与当前工作相关的章节来读。下面把这几章简单归类评述,为你进行选择性跳读时提供参考:
4、第一部分(第15章):主要围绕如何写出高质量代码提出一些建议。这些建议一部分来源于苹果每年举行的一些开发大会,主要是针对Objective-C语法、性能与功能改进进行的阐述和解惑;另一部 分来自于一线开发者平时工作中的点点滴滴的感受。作为笔者,我不过是把这些点滴像串珍珠一样把它们串起来,形成一个实用的、具有整体性的建议。 第1章 作为本书的首章,我感觉唠唠“Objective-C的那些事儿”很有必要。希望通过这一章的唠叨能让你有所收获。 第2章 主讲数据类型、集合和控制语句。道德经第五十二章中有言“天下有始,以为天下母。既得其母,以知其子;既知其子,复守其母。”掌握了基本的开发语言,就可以“知
5、其子”程 序;反过来,通过开发程序,又可以“复守其母”开发语言,即通过开发程序可以进一步巩固、加深对开发语言的理解。在Objective-C中,构建语言的基本单元值和集合,更是重中之重。本章 将围绕这些构建开发语言的“基石”从不同角度加以阐述。 第3章 主讲内存管理。内存管理曾经是程序员的噩梦,特别是在面向过程开发的过程中。虽然,在纯面向对象的开发语言中,例如C#和Java等开发语言,有了自动内存垃圾回收的机制,但依赖这种 自动内存垃圾回收机制的代价往往很高,容易造成程序性能的耗损,进而容易产生难以突破的“性能劲瓶”。因此,在编写高性能的应用程序,特别是服务器程序时,不得不依赖C、Pascal
6、和C+等非纯 面向对象的语言来完成。现在,众多的Android手机或者平板上遇到的App莫名其妙崩溃的现象,在很大程度上与Android底层的内存回收处理不当有很大的关系。 在本书定稿之时,虽然,Objective-C已经具有了“垃圾自动回收”的机制,但洞悉其内存的管理机制,写出高质量的代码,还是至关重要的。 第4章 主讲设计与声明。庄子齐物论中曰:“昔者庄周梦为蝴蝶,不知周之梦为蝴蝶与?蝴蝶之梦为周与?”意思是说,庄子梦见自己变成蝴蝶,醒来后发现自己是庄周。庄子分不清自己是 梦中变成蝴蝶,还是蝴蝶梦中变成庄子了。在Objective-C中,类与对象的关系,就有些类似庄子和蝴蝶的关系,从一定的
7、角度来看,类就是对象,而从另外一个角度来看,对象又是类。本章就以庄周梦蝶 做引子,来谈谈设计和声明。 第5章 众所周知,在面向对象的设计中,对于一个类,完成其定义,要包含类名、实例变量及方法的定义和声明,但这最多也只能算是完成20%的工作,其余80%的工作主要在于其实现。如何把类的 实现写好,很考验一个人的功底。尽可能利用开发语言本身一些特性,是实现类的一个比较有效的途径。本章就结合Objective-C的特性,来介绍在类的实现中一些可利用的做法。 第二部分(第68章),相对于第一部分,本部分更多关注一些“习以为常”的理念,结合Objective-C语言,进行一番新的解读。这几章里面,很多东西
8、你也许早已经知道,但一些理念如何融入自己 的代码和设计中,却是更高的要求。正如巴菲特的理财理念,很多人都能知道,且能背诵得滚瓜烂熟,而在实际操作上,结果却大相径庭,其主要原因是,很多“理念”自己不过是记住罢了,并未已融入 自己的血液和骨髓里,在实际操作上,仍旧沉溺于“旧习”。 第6章 主讲继承与面向对象的设计。随着这几年面向对象编程(OOP)越来越盛行,在编程领域,现在几乎是面向对象编程语言的天下了。继承,作为面向对象编程的三大特征(继承、多态和封 装)之一,起着核心的作用。但不同面向对象的编程语言实现的方式,有着比较大的差异性。本章从纵向和横向两个维度,来阐述“继承”在Objective-C
9、开发语言中的多面性,使你娴熟掌握“继承”在 Objective-C开发语言中的“有所为而有所不为”。 第7章 主讲设计模式与Cocoa编程。设计模式是一种设计模板,用于解决在特定环境中反复出现的一般性问题。它是一种抽象工具,在架构、工程和软件开发领域相当有用。本章将介绍什么是设计模 式,解释为什么设计模式在面向对象的设计中非常重要,并讨论一个设计模式的实例。 第8章 主讲定制init delete这两个关键字,但存在着alloc和init 能,前者用于处理内存的分配,后者用于在分配的内存中进行数据的初始化;与delete类似的是dealloc。掌握init path=/openresource
10、s/teach_ebook/uncompressed/15405/OEBPS/Text/.,对于写出高性能的应用是至关重要的。 第三部分(第9章),在本书将近定稿时,恰遇到Swift的新版本推出,为了解决开发者在混用Objective-C和wift过程中遇到的一些疑难点,故此添加了本部分。如果对Swift“不感冒”,那么本部分 你就没有阅读的必要了。 第9章 主讲Objective-C和Swift的兼容性。Swift是苹果公司在WWDC 2014新发布的一门编程语言,用来撰写OS X和iOS应用程序,但是Swift的推出,使很多原先很熟练使用Objective-C的码农产 生了一定程度的“惊惶
11、”:担忧苹果公司放弃对Objective-C的支持,不得不花费一定的时间和精力来学习一门新的开发语言Swift。Xcode 6或者更高的版本支持二者的相互兼容。也就是说,在 Objective-C文件中可以使用Swift编写的代码,也可以在Swift文件中使用Objective-C编写的代码,但其相互兼容性是有条件的。本章就来讲解二者的兼容性问题。 本书没有采取循序渐进的方式。故此,读者可以根据“兴趣”选择自己喜欢的章节来阅读,这是最有效的读书方式,也是笔者推崇的读书方式。 勘误和支持 除封面署名外,参加本书编写工作的还有孙振江、陈连增、边伟、郭合苍、郑军、吴景峰、杨珍民、王文朝、崔少闯、韦闪
12、雷、刘红娇、王洁、于雪龙、孔琴。由于笔者的水平有限,编写时间仓促, 书中难免会出现一些错误或者不准确的地方,恳请读者批评指正。 书中的不足之处,还望各位读者多提意见,欢迎发送邮件至邮箱yifeilang,期待能够得到你们的真挚反馈。 感恩致谢 本书之所以能出版,多亏华章公司的编辑孙海亮先生多次催促,不断地给予我鼓励。期间华章群一些网友的鼓励,也使我受益匪浅,在此向他们表示感谢。最后,要感谢的就是我亲爱的读者,感谢你 拿起这本书,你的认可,就是我的最大的快乐。 刘一道 于北京 1 这只是逻辑上的划分,为了不影响读者选择性跳读,正文中未明确体现。 第1章 让自己习惯Objective-C 听说有本
13、书叫明朝那些事儿,很多人喜欢,但我没有看过。我这人有些“老帽儿”,对一些时髦的东西不是很感兴趣。我看的都是那些经过千百年“浪沙冲洗”沉淀下来的书籍(技术书籍除 外)。作为本书的首章,我感觉聊一些“Objective-C的那些事儿”很有必要,希望读者能有所收获。 建议1:视Objective-C为一门动态语言 Objective-C,是美国人布莱德确斯(Brad Cox,见图1-1)于1980年年初发明的一种程序设计语言,其与同时代的C+一样,都是在C的基础上加入面向对象特性扩充而成的。C+如日中天,红火 已有30年之久,而Objective-C到2010年才被人注意,并逐渐红火起来。造成这种结
14、果的原因跟其版权为苹果独自拥有和苹果自我封闭性、不作为性有很大的关系。这也是Objective-C在语法上跟其他语言 (如C+、Pascal、Java和C#)等相比有些不足的原因。可喜的是,苹果公司于2010年起,在每年的苹果年度发布大会上都会针对Objective-C语言的语法发布新的改良版本。 图1-1 布莱德确斯 虽然Objective-C和C+都是在C的基础上加入面向对象特性扩充而成的程序设计语言,但二者实现的机制差异很大。Objective-C基于动态运行时类型,而C+基于静态类型。也就是说,用 Objective-C编写的程序不能直接编译成可令机器读懂的机器语言(二进制编码),而是
15、在程序运行时,通过运行时(Runtime)把程序转译成可令机器读懂的机器语言;用C+编写的程序,编译时就直 接编译成了可令机器读懂的机器语言。这也就是为什么把Objective-C视为一门动态开发语言的原因。 从原理上来讲,目前常用的Java和C#等程序开发语言都是动态开发语言,只不过Java用的是虚拟机JVM(Java Virtual Machine),如图1-2所示,C#用的是公共语言运行时CLR(Common Language Runtime)。与此相对,C+可视为静态开发语言。 图1-2 Java虚拟机实现机制 Objective-C作为一门动态开发语言,会尽可能地将编译和链接时要做的
16、事情推迟到运行时。只要有可能,Objective-C总是使用动态的方式来解决问题。这意味着Objective-C语言不仅需要一个编译 环境,同时也需要一个运行时系统来执行编译好的代码。 而运行时(Runtime)正是扮演着这样的角色。在一定程度上,运行时系统类似于Objective-C语言的操作系统,Objective-C就基于该系统来工作。因此,运行时系统好比Objective-C的灵魂,很多 东西都是在这个基础上出现的。很多开发人员常常对运行时系统充满疑惑,而恰恰这是一个非常重要的概念。可以这么问:“如果让你实现(设计)一个计算机语言,你会如何下手?”很少程序员这么思 考过。但是这么一问,
17、就会强迫你从更高层次来思考以前的问题了。 要想理解“Objective-C是一门动态开发语言”,就不得不理解开发语言的三个不同层次。 (1)传统的面向过程的语言开发。例如C语言,实现C语言编译器很简单,只要按照语法规则实现一个LALR语法分析器就可以了。编译器优化是非常难的,不在本节讨论的范围内。这里实现了编译器 中最基础和原始的目标之一,就是把一份代码里的函数名称转化成一个相对内存地址,把调用这个函数的语句转换成一个跳转指令。在程序开始运行时,调用语句可以正确跳转到对应的函数地址。这样很 好,也很直白,但是太死板了。 (2)改进的开发面向对象的语言。相对面向过程的语言来说,面向对象的语言更加
18、灵活了。例如C+,C+在C的基础上增加了类的部分。但这到底意味着什么呢?再写它的编译器要如何考虑呢?其 实,就是让编译器多绕个弯,在严格的C编译器上增加一层类处理的机制,把一个函数限制在它处在的类环境里,每次请求一个函数调用,先找到它的对象,其类型、返回值、参数等确定后再跳转到需要 的函数。这样很多程序增加了灵活性,同样一个函数调用会根据请求参数和类的环境返回完全不同的结果。增加类机制后,就模拟了现实世界的抽象模式,不同的对象有不同的属性和方法。同样的方法, 不同的类有不同的行为!这里大家就可以看到作为一个编译器开发者都做了哪些进一步的思考。虽然面相对象的语言有所改进,但还是死板,故此仍称C+
19、是静态语言。 (3)动态开发语言。希望更加灵活,于是完全把上面那个类的实现部分抽象出来,做成一套完整运行阶段的检测环境,形成动态开发语言。这次再写编译器甚至保留部分代码里的sytax名称,名称错 误检测,运行时系统环境注册所有全局的类、函数、变量等信息,可以无限地为这个层增加必要的功能。调用函数时,会先从这个运行时系统环境里检测所有可能的参数再做jmp跳转。这就是运行时系 统。编译器开发起来比上面更加绕弯,但是这个层极大地增加了程序的灵活性。例如,当调用一个函数时,前两种语言很有可能一个跳到了一个非法地址导致程序崩溃,但是在这个层次里面,运行时系统 过滤掉了这些可能性。这就是动态开发语言更加强
20、壮的原因,因为编译器和运行时系统环境开发人员已经帮你处理了这些问题。 好了,上面说了这么多,再返回来看Objective-C的这些语句: id obj=self; if (obj respondsToSelector:selector(function1:) if (obj isKindOfClass:NSArray class ) if (obj conformsToProtocol:protocol(myProtocol) if (obj class isSubclassOfClass:NSArray class) obj someNonExistFunction; 看似很简单的语句,但是
21、为了让语言实现这个能力,语言开发者要付出很多努力来实现运行时系统环境。这里运行时系统环境处理了弱类型及函数存在检查工作。运行时系统会检测注册列表里是否存 在对应的函数,类型是否正确,最后确定正确的函数地址,再进行保存寄存器状态、压栈、函数调用等实际的操作。 id knife=Knife grateKnife; NSArray *monsterList=NSArray array; monsterList makeObjectsPerformSelector:selector(killMonster:) withObject:knife; 用C和C+完成这个功能还是比较麻烦的,但是动态语言处理却
22、非常简单,并且这些语句让Objective-C语言更加直观。 在Objective-C中,针对对象的函数调用将不再是普通的函数调用: obj function1With:var1; 这样的函数调用将被运行时系统环境转换成: objc_msgSend(target,selector(function1With:),var1); Objective-C的运行时系统环境是开源的,这里可以进行简单介绍,可以看到objc_msgSend由汇编语言实现,甚至不必阅读代码,只需查看注释就可以了解。运行时系统环境在函数调用前做了比较全 面的安全检查,已确保动态语言函数调用不会导致程序崩溃。对于希望深入学习的朋
23、友可以自行下载Objective-C的运行时系统源代码来阅读,进行更深入的了解。 /* * id objc_msgSend(id self, * SEL op, * * * a1是消息接收者,a2是选择器 */ ENTRY objc_msgSend #检查接收者是否空 teq a1, #0 moveq a2, #0 bxeq lr # 保存寄存器(对象),通过在缓存里查找,来加载接收者(对象)类 stmfd sp!, a4,v1-v3 ldr v1, a1, #ISA #寄存器(对象)非空,缓存里可查找到 CacheLookup a2, LMsgSendCacheMiss #缓存碰撞(imp
24、in ip) 准备转发,恢复寄存器和调用 teq v1, v1 /* 设置 nonstret (eq) */ ldmfd sp!, a4,v1-v3 bx ip #缓存漏掉:查找方法列表 LMsgSendCacheMiss: ldmfd sp!, a4,v1-v3 b _objc_msgSend_uncached LMsgSendExit: END_ENTRY objc_msgSend .text .align 2 _objc_msgSend_uncached: #压入堆栈帧 stmfd sp!, a1-a4,r7,lr add r7, sp, #16 SAVE_VFP # 加载类和选择器 l
25、dr a1, a1, #ISA /* class = receiver-isa */ # MOVE a2, a2 /*选择器已经在a2 */ #做查找 MI_CALL_EXTERNAL(_class_lookupMethodAndLoadCache) MOVE ip, a1 # 准备转发,挤出堆栈帧并且调用imp teq v1, v1 /* 设置nonstret (eq) */ RESTORE_VFP ldmfd sp!, a1-a4,r7,lr bx ip 现在说一下动态开发语言的负面影响,其负面影响可以归纳为两方面。 1.执行效率问题 “静态开发语言执行效率要比动态开发语言高”,这句没错。
26、因为一部分CPU计算损耗在了运行时系统过程中,而从上面的汇编代码也可以看出大概损耗在哪些地方。而静态语言生成的机器指令更简 洁。正因为知道这个原因,所以开发语言的人付出很大一部分努力来保持运行时系统的小巧。所以,Objective-C是C的超集加上一个小巧的运行时系统环境。但是,换句话说,从算法角度考虑,这点复杂 度是可忽略的。 2.安全性问题 动态语言由于运行时系统环境的需求,会保留一些源码级别的程序结构。这样就给破解带来了方便之门。一个现成的说明就是Java,大家都知道Java运行在jre上面,这就是典型的运行时例子。它的执 行文件.class全部可以反编译回近似源代码。所以,这里的额外提
27、示就是,如果需要写和安全有关的代码,离Objective-C远点,直接用C。 简单理解:“Runtime is everything between your each function call.” 但是大家要明白,提到运行时系统并不只是因为它带来了这些简便的语言特性,而是这些简单的语言特性在实际运用中,需要从完全不同的角度考虑。 要点 (1)Objective-C是动态语言,C+是静态语言。 (2)静态语言执行效率和安全性要比动态语言高,但其简便性没有动态语言高。 (3)运行时(Runtime)环境可处理弱类型、函数存在检查工作,会检测注册列表里是否存在对应的函数,类型是否正确,最后确定正
28、确的函数地址,再进行保存寄存器状态、压栈、函数调用等实际 操作,确保了Objective-C的灵活性。 建议2:在头文件中尽量减少其他头文件的引用 在面向对象开发语言中,如C+、C#、Java等语言中,对于类的描述,通常划分为头文件和源文件。头文件用于描述类的声明和可公开部分,而源文件用于描述类的方法或函数的具体实现,这也体现 了面向对象语言的“封闭性”和“高内聚低耦合”的特性。而对于基于面向对象而设计的Objective-C也不例外,类分为头文件(.h)和源文件(.m)。 在OOP编程中有两个技术用于描述类与类或对象与对象之间的关系:一个是继承;另一个是复合。在Objective-C中,当一
29、个类需要引用另一个类,即建立复合关系时,需要在类的头文件(.h)中, 通过“#import”修饰符来建立被引用类的指针。例如Car.h: / Car.h #import interface Car:NSObject Tire *tires4; Engine *engine; - (void) setEngine: (Engine *) newEngine; - (Engine *) engine; - (void) setTire: (Tire *) tire atIndex: (int) index; - (Tire *) tireAtIndex: (int) index; - (void)
30、 print; end / Car 在这里先省略类Car的源文件(.m)。对于上面的代码,如果直接这么编译,编译器会报错,提示它不知道Tire和Engine是什么。为了使上面的代码能编译通过,在上面代码中就不得不添加对类Tire和 Engine的头文件(.h)的引用,即通过关键字“#import”来建立起它们之间的复合关系。 要建立正确的复合关系,正确的代码写法如下: / Car.h #import #import Tire.h #import Engine.h interface Car:NSObject Tire *tires4; Engine *engine; - (void) setE
31、ngine: (Engine *) newEngine; - (Engine *) engine; - (void) setTire: (Tire *) tire atIndex: (int) index; - (Tire *) tireAtIndex: (int) index; - (void) print; end / Car 现在,上面的代码虽然能正确编译了,但从“代码的高品质高安全”角度来看,在使用“#import”建立类之间的复合关系时,也暴露了所引用类Tire和Engine的实体变量和方法,与只需知道有一个 类名叫Tire和Engine的初衷有些违背。在解决问题的同时,也带来了代码
32、的安全性问题。 那么如何解决上面的问题呢?可以使用关键字class来告诉编译器:这是一个类,所以只需要通过指针来引用它。它并不需要知道关于这个类的更多信息,只要了解它是通过指针引用即可,减少由依 赖关系引起的重新编译所产生的影响。 对于上面的代码,通过Class即可来建立对于类Tire和Engine的引用,具体写法如下: / Car.h #import class Tire class Engine interface Car:NSObject Tire *tires4; Engine *engine; - (void) setEngine: (Engine *) newEngine; - (
33、Engine *) engine; - (void) setTire: (Tire *) tire atIndex: (int) index; - (Tire *) tireAtIndex: (int) index; - (void) print; end / Car 上面介绍了使用“#import”和“class”在“依赖关系”方面所产生的影响。同时二者在编译效率方面也存在巨大的差异。假如,有100个头文件,都用“#import”引用了同一个头文件,或者这些 文件是依次引用的,如AB、BC、CD这样的引用关系。当最开始的那个头文件有变化时,后面所有引用它的类都需要重新编译,如果自己的类有很多
34、的话,这将耗费大量的时间,而使用class则 不会。 对于初学者,最容易犯“类循环依赖”错误。所谓的“类循环依赖”,也就是说,两个类相互引用对方。在本条款最初的Car.h头文件中,通过“#import”引用了Tire.h头文件,假如在Tire.h头文件里 引用Car.h头文件,即如下: / Tire.h #import #import Tire.h 上面的代码进行编译时会出现编译错误,如果使用class在两个类的头文件中相互声明,则不会有编译错误出现。虽然使用class不会出现编译错误,但还是尽量避免这种“类循环依赖”的出现,因 为这样容易造成类之间“高耦合”现象的产生,给以后代码的维护和管理
35、带来很大的麻烦。 “#import”并非一无是处。既然“#import”与“class”相比有很多不足,那么是否可以用“class”来完全代替“#import”?不可以,在一个头文件(.h)中包含多个类的声明定义时,要与 该头文件声明的多个类建立复合关系,比较好的方式是,采用关键字“#import”来建立复合关系。 例如,下面是头文件PersonType.h的定义: /PersonType.h #import / Person类的声明定义 interface Person:NSObject NSInteger *sexType; property (nonatomic copy) NSStri
36、ng *firstname; property (nonatomic copy) NSString *lastname; - (void) setSexType: : (int) index; end / man类的声明定义 interface man:Person end /woman类的声明定义 interface woman:Person 要与上面头文件PersonType.h中所声明的类建立复合关系,这个时候就不得不用关键字“#import”。使用“#import”建立复合关系,会把所引用的头文件(.h)的所有类进行预编译,这样就会消 耗很长时间。是否是有一种更好的方式来处理这个问题,
37、请参阅“条款9尽量使用模块方式与多类建立复合关系”。 一般来说,关键字“class”放在头文件中只是为了在头文件中引用这个类,把这个类作为一个类型来用。这就要求引用的头文件(.h)名与类的名称一致,且在类头文件(.h)只包含该类的声明定 义的情况下,才可以使用关键字“class”来建立复合关系。同时,在实现这个类的接口的类源文件(.m)中,如果需要引用这个类的实体变量或方法等,还需要通过“#import”把在“class”中声明 的类引用进来。例如下面的类a引用类Rectangle的示例: a.h class Rectangle; interface A : NSObject a.m #imp
38、ort Rectangle implementation A 上面的种种介绍,其核心的目的就是为了“降低类与类之间的耦合度”。也就是说,降低类与类之间的复合关系黏性度。 在自己设计类的时候,除了“#import”和“class”之外,有没有一种更好的方式?有的,一种是通过使用模块方式与多类建立复合关系,详细情况请参阅建议6;另一种是通过使用“协议”的方式 来实现。 在Objective-C中,实际上,协议就是一个穿了“马甲”的接口。通过使用“协议”来降低类与类之间的耦合度。例如,把协议单独写在一个文件中,注意,千万不要把协议写入到一个大的头文件中, 这样做,凡是只要引入此协议,就必定会引入头文
39、件中的全部内容,如此一来,类与类之间的耦合度就会大大增加,不利于代码的管理及程序的稳定性和安全性,为以后的工作带来很大的麻烦。 故此,在自己设计类的时候,首先要明白,通过使用“#import”和“class”,每次引入其他的头文件是否有必要。如果要引用的类和该类所在的文件同名,最好采用“class”方式来引入。如果 引用的类所处的文件有多个类或者多个其他的定义,最好采用“模块方式”来针对性引入自己所需要的类,详细情况请参与建议6。不管采取哪种方式,降低类与类的耦合度,降低不同文件代码之间过度的 黏合性是首要的目的。代码的依赖关系过于复杂则会失去代码的重用性,给维护代码带来很大的麻烦,同时,使编
40、译的应用的稳定性和高效性也大打折扣。 要点 (1)在头文件(.h)中,关键字“class”,只是为了在头文件中引用这个类,把这个类作为一个类型来用,这就要求引用的头文件(.h)名与类的名称一致。 (2)在头文件(.h)中使用“class”,在源文件(.m)中使用“#import”,不但可以减少不必要的编译时间,降低类之间的耦合度,而且还可以避免循环引用。 (3)在设计类时,尽量多采用协议,避免#import过多,引进不必要的部分。 (4)如果头文件(.h)中有多个类的定义,尽量采用模块方式,只针对性引进所需要的类。 建议3:尽量使用const、enum来替换预处理#define #define
41、定义了一个宏,在编译开始之前就会被替换。const只是对变量进行修饰,当试图去修改该变量时,编译器会报错。在一些场合里你只能用#define,而不能用const。理论上来说,const不仅 在运行时需要占用空间,而且还需要一个内存的引用;但从时间上来说,这是无关紧要的,编译器可能会对其进行优化。 const在编译和调试的时候比#define更友好。在大多数情况下,当你决定用哪一个时,这是你应该考虑的一个非常重要的点。 想象一下,下面这样一个应该使用#define而不是const的场景:如果想在大量的.c文件中使用一个常量,只需要使用#define放在头文件中;而使用const,则需要在.c文件
42、和头文件中都进行定义,如 下所示。 / in a C file const int MY_INT_CONST = 12345; / in a header extern const int MY_INT_CONST; MY_INT_CONST,在任何C文件中都不能当作一个静态变量或全局作用域使用,除非它已被定义。然而,对于一个整型常量,你可以使用枚举(enum)。事实上,这就是Apple一直在做的事情。它 (enum)兼有#define和const的所有优点,但是只能用在整型常量上。 / In a header enum MY_INT_CONST = 12345, ; 哪一个更高效或更安全呢?
43、#define在理论上来说更高效,但就像之前说的那样,在现代的编译器上,它们可能没什么不同。#define会更安全,因为当试图赋值给它时,总会出现一个编译器错误。 因此,相对字符串字面量或数字,更推荐适用常量。应使用static方式声明常量,而非使用#define的方式来定义宏。 恰当用法如下所示: static NSString * const NYTAboutViewControllerCompanyName = The New York Times Company; static const CGFloat NYTImageThumbnailHeight = 50.0; 不当用法如下所示
44、: #define CompanyName The New York Times Company #define thumbnailHeight 2 对于整型类型,代替#define比较好的方法是使用enum,在使用enum时,推荐使用最新的fixed underlying type规范的NS_ENUM和NS_OPTIONS宏,因为它们是基于C语言的枚举,保留了C语言 的简洁和简单的特色。这些宏能明确指定枚举类型、大小和选项,改善在Xcode中的代码质量。此外,在旧的编译器中,这种语法声明能正确编译通过,在新的编译器中也可明解基础类型的类型信息。 下面,使用NS_ENUM宏定义枚举。该组值是互
45、斥的,代码如下: typedef NS_ENUM(NSInteger, UITableViewCellStyle) UITableViewCellStyleDefault, UITableViewCellStyleValue1, UITableViewCellStyleValue2, UITableViewCellStyleSubtitle ; 在本例中,在命名UITableViewCellStyle的NSInteger类型时,通过使用NS_ENUM宏使界定双方的名称和枚举类型更容易了。 在下面的代码中,通过使用NS_OPTIONS宏定义了一组可以组合在一起的位掩码值,实现方式如下: type
46、def NS_OPTIONS(NSUInteger, UIViewAutoresizing) UIViewAutoresizingNone = 0, UIViewAutoresizingFlexibleLeftMargin = 1 0, UIViewAutoresizingFlexibleWidth = 1 1, UIViewAutoresizingFlexibleRightMargin = 1 2, UIViewAutoresizingFlexibleTopMargin = 1 3, UIViewAutoresizingFlexibleHeight = 1 4, UIViewAutoresi
47、zingFlexibleBottomMargin = 1 5 ; 像枚举一样,NS_OPTIONS宏定义了一个名称和一个类型。然而,对于选项的类型通常应该是NSUInteger。 在实际编码中,如何使用枚举宏来更换enum,比如这样一个enum定义: enum UITableViewCellStyleDefault, UITableViewCellStyleValue1, UITableViewCellStyleValue2, UITableViewCellStyleSubtitle ; typedef NSInteger UITableViewCellStyle; 用NS_ENUM宏来实现上面的定义,其语法如下: typedef NS_ENUM(NSInteger, UITableViewCellStyle) UITableViewCellStyleDefault, UITableViewCellStyleValue1, UITableViewCellStyleValue2, UITableViewCellStyleSubtitle ; 在实际开发中,经常会使用enum来定义一个位掩码,如下面一个用enum来定义位掩码的示例: enum UIViewAutoresizingNone = 0, UIViewAutoresizingFlexibleLeftMa