《编写高质量代码:改善Objective-C程序的61个建议》——建议6:尽量使用模块方式与多类建立复合关系

本节书摘来自华章出版社《编写高质量代码:改善Objective-C程序的61个建议》一 书中的第1章,第1.6节,作者:刘一道,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

建议6:尽量使用模块方式与多类建立复合关系

在2013年的苹果年度大会上,苹果在Objective-C的性能改进上大的变化之一就是加入了模块(Modules)

  1. 文件编译问题的存在性—编译时间过长
    在了解模块(Modules)之前,需要先了解一下Objective-C的#impor机制。通过使用#import,用来引用其他的头文件。

熟悉C或者C++的人可能会知道,在C和C++里是没有#import的,只有#include(虽然GCC现在为C和C++做了特殊处理,使#imoprt可以被编译),用来包含头文件。#include做的事情其实就是简单的复制、粘贴,将目标.h文件中的内容一字不落地复制到当前文件中,并替换掉这句include;而#import实质上做的事情和#include是一样的,只不过Objective-C为了避免重复引用可能带来的编译错误(这种情况在引用关系复杂的时候很可能发生,比如B和C都引用了A,D又同时引用了B和C,这样A中定义的东西就在D中被定义了两次,造成重复)而加入了#import,从而保证每个头文件只会被引用一次。仔细探究一下,#import的实现是通过对#ifndef一个标志进行判断,然后再引入#define这个标志,来避免重复引用的。
实质上,#import也是复制、粘贴,这样就带来一个问题:当引用关系很复杂或一个头文件被非常多的实现文件引用时,编译时引用所占的代码量就会大幅上升(因为被引用的头文件在各个地方都被复制了一遍)。
在编写Objective-C代码中,估计很多人已经写了一千遍或更多#import语句:

//
//AppDelegate
//
#import <UIKit/UIKit.h>  

按照上面所说,这意味着,对于UIKit框架,通过计算所有行的全部UIKit中的头,就会发现它相当于超过11 000行代码!在一个标准的iOS应用中,就会在大部分文件中导入UIKit,这意味着每一个文件最终变成11000行。这是不够理想的,更多的代码意味着更长的编译时间。

  1. 预编译头文件(Pre-compiled Headers)处理方式—不实用
    理论上讲,解决这个问题可采取C语言的方式,引入预编译头文件(Pre-compiled Headers,PCH),即把公用的头文件放入预编译头文件中预先进行编译。通过在编译的预处理阶段,预先计算和缓存需要的代码;然后在真正编译工程时再将预先编译好的产物加入到所有待编译的源文件中去,来加快编译速度。比如iOS开发中Supporting Files组内的.pch文件,就是一个预编译头文件。默认情况下,它引用了UIKit和Foundation两个头文件,这是在iOS开发中基本上每个实现文件都会用到的东西。

下面是Xcode生成的stock PCH 文件,像这样:

#import <Availability.h>   
#ifndef __IPHONE_5_0   
#warning "This project uses features only available in iOS SDK 5.0 and later."   
#endif   
   
#ifdef __OBJC__   
   #import <UIKit/UIKit.h>   
   #import <Foundation/Foundation.h>   
#endif   

如果开发的应用是使用iOS 5之前的SDK,那么#warning将通知它们。UIKit和Foundation 头文件是stockPCH的一部分。因为在应用程序里的每一个文件将使用Foundation,并且大部分会使用UIKit。因此,这些都被很好地添加进了预编译头文件,以便于在自己的应用程序中预先计算和缓存这些文件的编译文件。
但维护项目的预编译头文件是很棘手的。利用预编译头文件虽然可以加快编译的时间, 但是这样面临的问题是,在工程中随处可用本来应该不能访问的东西,而编译器也无法准确给出错误或者警告,无形中增加了出错的可能性。

  1. 利用模块 (Modules)来解决历史问题—事半功倍
    模块(Modules),第一次在Objective-C中公共露面是在2012 LLVM开发者大会上Apple抯 Doug Gregor的一次谈话中。

模块(Modules),封装框架比以往任何时候都更加清洁。不再需要预处理逐行地用文件的所有内容替换#import指令。相反,一个模块包含了一个框架到自包含的块中,就像预编译文件预编译的方式一样提升了编译速度。并且不需要在预编译头文件中声明自己要用到哪些框架,使用模块简单地获得了速度上的提升。图1-3所示为预编译文件和模块功能在编译上的比较。

《编写高质量代码:改善Objective-C程序的61个建议》——建议6:尽量使用模块方式与多类建立复合关系

模块还有其他方面的优点,在下列的操作编写代码的步骤过程中,都可以感受到模块的特性。
(1)在使用框架的文件中添加#import。
(2)用框架写代码。
(3)编译。
(4)查看链接错误。
(5)忘记链接的框架。
(6)添加忘记的框架到项目中。
(7)重新编译。
忘记链接框架式是编写程序代码中经常会犯的一个错误,利用模块就能解决这个问题。一个模块不仅告诉编译器哪些头文件组成了模块,而且还告诉编译器什么需要链接。这样就不用去手动链接框架了。虽然这是一件小事,但是能让开发变得更加简单,这就是一件好事。

  1. 开启使用模块
    模块的使用相当简单。对于存在的工程,第一件事情就是使这个功能生效。可以在项目的Build Settings中通过搜索Modules找到这个选项,将Enable Modules 选项设为Yes,如图1-4所示。

《编写高质量代码:改善Objective-C程序的61个建议》——建议6:尽量使用模块方式与多类建立复合关系

在默认情况下,模块功能所有的新工程都是开启的,但是应该在自己所有存在的工程中都开启这个功能。在图1-4中,Link Frameworks Automatically选项,可以用来开启或者关闭自动链接框架的功能。
一旦模块(Modules)功能开启,就可以在自己代码中使用它了。要这样做,对以前用到的语法有一点小小的改动,那用@import代替#import:

@import UIKit;   
@import MapKit;   
@import iAd;   

另外,只导入一个框架中自己需要的部分也是可以的。例如,只想要导入UIView,就可以这样写:

@import UIKit.UIView;   

使用模块功能就是这么简单。技术上,自己不需要把所有的#import都换成@import,因为编译器会隐式地转换它们,但是还是建议尽可能地用新的语法来编写代码。
 Xcode 5.0的模块功能还不支持自己的或第三方的框架。目前Xcode 6的测试版都出来了,在很多Xcode的新版本中,都支持模块功能。

 要点
(1)#include和#import,其根本就是简单的复制、粘贴,将目标.h文件中的内容一字不落地复制到当前文件中,后者可以避免多次的重复引用。
(2)以预编译头文件的方式,虽可缩短编译时间,但其维护棘手,不利于广泛应用。
(3)模块功能,其应用不仅仅表现于编译的速度加快,同时在链接框架等方面也非常好用。
(4)启动模块功能后,编译器会隐式地把所有的#import都转换成@import。

上一篇:android中各种图标尺寸以及多分辨率支持方法


下一篇:一个跨设备的云数据平台parse.com