在iOS中建立静态库

若是你做为iOS开发者已经有一段时间,可能会有一套属于本身的类和工具函数,它们在你的大多数项目中被重用。
 
重用代码的最简单方法是简单的 拷贝/粘贴 源文件。然而,这种方法很快就会成为维护时的噩梦。由于每一个app都有本身的一份代码副本,你很难在修复bug或者升级时保证全部副本的同步。
 
这就是静态库要拯救你的。一个静态库是若干个类,函数,定义和资源的包装,你能够将其打包并很容易的在项目之间共享。
 
在本教程中,你将用两种方法亲手建立你本身的通用静态库。
 
为了得到最佳效果,你应该熟悉Objective-C和iOS编程。Core Image的相关知识并非必须的,可是若是你对示例工程和滤镜代码如何工做感兴趣,了解它会有所帮助。
 
准备好以效率的名义减小,重用并再生你的代码!
 
为何使用静态库
 
建立静态库可能出于如下几个理由:
1.你想将一些你和你团队中的同事们常用的类打包并轻松的分享给周围其余人。
2.你想让一些通用代码处于本身的掌控之下,以便于修复和升级。
3.你想将库共享给其余人,但不想让他们看到你的源代码。
 
你想建立一个还在不断开发的库的快照版本。
 
本教程假设你已经完成学习 Core Image Tutorial,并对其中展现如何应用图片特效的代码驾轻就熟。
 
将上述代码添加到一个静态库中,而后在一个app的修改版本中使用这个静态库。咱们会获得一个带有上面列表中所有好处的彻底相同的应用。
 
开始
运行Xcode,选择File\New\Project,在Choose a template 对话框中选择iOS\Framework & Library\Cocoa Touch Static Library,以下图:
 
 
点击Next。在工程选项对话框中,输入ImageFilters做为产品名。再输入一个惟一的公司标识,确保Use Automatic Reference Counting被选中且Include Unit Tests未选中。以下图:
 
 
点击Next。最后,选择你想保存工程的位置并点击Create。
 
Xcode已经准备好静态库工程,甚至已经为你添加了一个ImageFilters类。这就是你的滤镜代码将要存放的地方。
 
注意: 你能够添加任意数量的类到静态库中或者从中删除原有的类。本教程中的代码都会写在开始就被建立好的ImageFilters类中。
 
你的Xcode工程仍是一片空白,如今咱们添加一些代码进去!
 
图片滤镜
 
该库使用UIKit,为iOS设计,因此你要作的第一件事就是在头文件中导入UIKit。打开ImageFilters.h,在文件顶部添加如下代码:
  1. #import <UIKit/UIKit.h> 
接下来将如下声明部分的代码粘贴到@interface ImageFilters : NSObject下面
  1. @property (nonatomic,readonly) UIImage *originalImage; 
  2.   
  3. - (id)initWithImage:(UIImage *)image; 
  4. - (UIImage *)grayScaleImage; 
  5. - (UIImage *)oldImageWithIntensity:(CGFloat)level; 
这些头文件中的声明定义了类的公开接口。其余开发者(包括你本身)使用该库时,只需经过阅读该头文件就能够知道类名和暴露的方法。
 
如今增长实现。打开ImageFilters.m文件,粘贴如下代码到#import "ImageFilters.h"下面:
  1. @interface ImageFilters() 
  2.   
  3. @property (nonatomic,strong) CIContext  *context; 
  4. @property (nonatomic,strong) CIImage    *beginImage; 
  5.   
  6. @end 
上面的代码声明了一些内部使用的属性。它们不是公开的,因此使用该库的引用没有使用它们的入口。
 
最后,你须要实现方法。粘贴如下代码到@implementation ImageFilters:下面:
  1. - (id)initWithImage:(UIImage *)image 
  2.     self = [super init]; 
  3.     if (self) { 
  4.         _originalImage  = image; 
  5.         _context        = [CIContext contextWithOptions:nil]; 
  6.         _beginImage     = [[CIImage alloc] initWithImage:_originalImage]; 
  7.     } 
  8.     return self; 
  9.   
  10. - (UIImage*)imageWithCIImage:(CIImage *)ciImage 
  11.     CGImageRef cgiImage = [self.context createCGImage:ciImage fromRect:ciImage.extent]; 
  12.     UIImage *image = [UIImage imageWithCGImage:cgiImage]; 
  13.     CGImageRelease(cgiImage); 
  14.   
  15.     return image; 
  16.   
  17. - (UIImage *)grayScaleImage 
  18.     if( !self.originalImage) 
  19.         return nil; 
  20.   
  21.     CIImage *grayScaleFilter = [CIFilter filterWithName:@"CIColorControls" keysAndValues:kCIInputImageKey, self.beginImage, @"inputBrightness", [NSNumber numberWithFloat:0.0], @"inputContrast", [NSNumber numberWithFloat:1.1], @"inputSaturation", [NSNumber numberWithFloat:0.0], nil].outputImage; 
  22.   
  23.     CIImage *output = [CIFilter filterWithName:@"CIExposureAdjust" keysAndValues:kCIInputImageKey, grayScaleFilter, @"inputEV", [NSNumber numberWithFloat:0.7], nil].outputImage; 
  24.   
  25.     UIImage *filteredImage = [self imageWithCIImage:output]; 
  26.     return filteredImage; 
  27.   
  28. - (UIImage *)oldImageWithIntensity:(CGFloat)intensity 
  29.     if( !self.originalImage ) 
  30.         return nil; 
  31.   
  32.     CIFilter *sepia = [CIFilter filterWithName:@"CISepiaTone"]; 
  33.     [sepia setValue:self.beginImage forKey:kCIInputImageKey]; 
  34.     [sepia setValue:@(intensity) forKey:@"inputIntensity"]; 
  35.   
  36.     CIFilter *random = [CIFilter filterWithName:@"CIRandomGenerator"]; 
  37.   
  38.     CIFilter *lighten = [CIFilter filterWithName:@"CIColorControls"]; 
  39.     [lighten setValue:random.outputImage forKey:kCIInputImageKey]; 
  40.     [lighten setValue:@(1 - intensity) forKey:@"inputBrightness"]; 
  41.     [lighten setValue:@0.0 forKey:@"inputSaturation"]; 
  42.   
  43.     CIImage *croppedImage = [lighten.outputImage imageByCroppingToRect:[self.beginImage extent]]; 
  44.   
  45.     CIFilter *composite = [CIFilter filterWithName:@"CIHardLightBlendMode"]; 
  46.     [composite setValue:sepia.outputImage forKey:kCIInputImageKey]; 
  47.     [composite setValue:croppedImage forKey:kCIInputBackgroundImageKey]; 
  48.   
  49.     CIFilter *vignette = [CIFilter filterWithName:@"CIVignette"]; 
  50.     [vignette setValue:composite.outputImage forKey:kCIInputImageKey]; 
  51.     [vignette setValue:@(intensity * 2) forKey:@"inputIntensity"]; 
  52.     [vignette setValue:@(intensity * 30) forKey:@"inputRadius"]; 
  53.   
  54.     UIImage *filteredImage = [self imageWithCIImage:vignette.outputImage]; 
  55.   
  56.     return filteredImage; 
这段代码实现了初始化和图片滤镜功能。详细解释上述代码的功能已经超出了本教程的范围,你能够从Core Image Tutorial中了解到更多的关于 Core Image 和滤镜的知识。
 
到这里,你已经有了一个静态库,它有一个暴露了如下3个方法的公开类ImageFilters:
initWithImage : 初始化滤镜类
grayScaleImage : 建立灰阶图片
oldImageWithIntensity : 建立怀旧效果的图片
 
如今构建并运行你的库。你会注意到Xcode的”Run”按钮只是执行了一次构建,而并不能真正的运行库去查看效果,由于并无真正的app。
 
静态库的后缀名是.a而并非一个.app或者.ipa文件。能够在工程导航栏中的Products文件夹下找到生成的静态库。右键点击libImageFilters.a并在弹出菜单中选择Show in Finder。
 
 
Xcode会在Finder中打开文件夹,你能够看到如下相似的结构:
 
 
离完成一个库产品还剩两件事:
1.Header files : 在include文件夹中能够找到库的全部公开头文件。在该示例中,只有一个公开类因此文件夹中只有一个ImageFilters.h文件。稍后你会在你的app工程中用到这个头文件以便于Xcode在编译期识别暴露的类。
2.Binary Libraty : Xcode生成的静态库是ImageFilters.a。想在应用中使用该库,你须要用该文件连接。
 
这两个部分和你想在应用里包含一些新的框架时所须要作的事很类似,简单的导入框架头文件并创建连接。
 
库已经准备就绪,须要附加说明的是,默认状况下,库文件只会为当前的架构构建。若是你在模拟器下构建,那么库会包含对应i386架构的结果代码;若是在真机设备下构建,你将会获得对应ARM架构的代码。你可能须要构建两个版本的库,而且当从模拟器切换到设备的时候选择其中一个使用。
 
怎么办?
 
幸运的是,有一个更好的办法能够不创建多个配置或在工程中构建产品就能够支持多个平台。你能够建立一个对应 2个 架构的包含结果代码的universal binary。
 
通用二进制
通用二进制是一种特殊的二进制文件,它包含对应多个架构的结果代码。你可能在从PowerPC(PPC)到Inter(i386)的Mac电脑产品线的过渡中对其有所熟悉。在这个过程当中,Mac应用程序一般迁移为包含 2个 可执行包的一个二进制文件,这样应用程序即能在Inter也能在PowerPC的Mac电脑上运行。
 
同时支持ARM和i386的概念并无太大不一样。在这里静态库要包含支持iOS设备(ARM)和模拟器(i386)的结果代码。Xcode能够识别通用库,每次你构建应用的时候,它会根据目标选择适当的架构。
为了建立通用二进制库,须要使用一个名为lipo的系统工具。
 
 
别担忧,不是那种lipo! :] (lipo有脂肪的意思 — 译者注)
 
lipo是一个命令行工具,它容许在通用文件上执行操做(相似于建立通用二进制, 列出通用文件内容等等)。本教程中使用lipo的目的是联合不一样架构的二进制文件到单个输出文件中。你能够直接在命令行中使用lipo命令,但在本教程中你可让Xcode执行一段建立通用库的命令行脚原本为你作这件事。
 
Xcode中一个集合目标能够一次构建多个目标,包括命令行脚本。在Xcode菜单中选择File/New/Target,选择iOS/Other并点击Aggregate,如图:
 
将目标命名为UniversalLib,确保选中ImageFilters工程,如图:
 
在工程导航视图中选中ImageFilters,而后选择UniversalLib目标。切换到Build Phases标签;在这里设置构建目标时将要执行的动做。
 
点击Add Build Phase按钮,在弹出的菜单中选择Add Run Script,以下图:
 
如今你须要设置脚本项。展开Run Script模块,在Shell行下粘贴以下代码:
  1. # define output folder environment variable 
  2. UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal 
  3.   
  4. # Step 1. Build Device and Simulator versions 
  5. xcodebuild -target ImageFilters ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos  BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" 
  6. xcodebuild -target ImageFilters -configuration ${CONFIGURATION} -sdk iphonesimulator -arch i386 BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" 
  7.   
  8. # make sure the output directory exists 
  9. mkdir -p "${UNIVERSAL_OUTPUTFOLDER}" 
  10.   
  11. # Step 2. Create universal binary file using lipo 
  12. lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/lib${PROJECT_NAME}.a" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/lib${PROJECT_NAME}.a" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/lib${PROJECT_NAME}.a" 
  13.   
  14. # Last touch. copy the header files. Just for convenience 
  15. cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/include" "${UNIVERSAL_OUTPUTFOLDER}/" 
 
代码并不十分复杂,它是这样工做的:
UNIVERSAL_OUTPUTFOLDER 包括了通用二进制包将要被存放的文件夹:“Debug-universal”
 
Step 1. 第2行执行了xcodebuild并命令它构建ARM架构的二进制文件。(你能够看到这行中的-sdk iphoneos参数)
下一行再次执行了xcodebuild命令并在另外一个文件夹中构建了一个针对Inter架构的iPhone模拟器的二进制文件,在这里关键参数是-sdk iphonesimulator -arch i386。(若是感兴趣,你能够在 man page了解更多关于xcodebuild的资料)
 
Step 2. 如今已经有了2个.a文件分别对应两个架构。执行lipo -create,用它们建立出一个通用二进制。
 
最后一行的做用是复制头文件到通用构建文件夹的外层。(用cp命令)
 
你的Run Script窗口应该看起来以下:
 
如今你已经准备好构建一个静态库的通用版本。在方案下啦菜单中选择集合目标UniversalLib,以下(不像截图上的”iOS Device”,你看到的多是本身的设备名字):
 
 
点击Play按钮来为集合方案构建目标。
 
在libImageFilters.a上再次选择Show in Finder查看结果。将Finder切换到列视图查看文件夹层次,能够看到一个包含库的通用版本的叫作Debug-Universal的新文件夹(或Release-Universal若是你构建了发布版本),以下图:
 
除了这个连接到模拟器和真实设备的二进制文件,你还能够找到普通的头文件和静态库文件。
 
这是你建立本身的通用静态库所须要学习的全部知识。
 
归纳起来,一个静态库工程和一个应用工程很是类似。能够拥有一个或多个类,最后的产品是头文件和一个.a文件。这个.a文件就是能够连接到多个应用程序中的静态库。
 
在应用中使用静态库
在应用中使用ImageFilters类和直接使用源代码并无太大区别:导入头文件而后开始使用类。问题是Xcode并不知道头文件和库文件的位置。
 
有两种办法能够将静态库引入到工程中:
方法 1: 直接引用头文件和库二进制文件(.a)
方法 2: 将库工程做为子项目
 
选择哪种方法彻底取决于你的喜爱或者是否有静态库的源代码和工程配置文件任由你支配。
 
本教程将分别介绍两种方法。你能够自由尝试第一个或第二个,但推荐按照文中介绍的顺序分别尝试两个。在两个部分的开头,须要一个zip文件,该文件是在 Core Image Tutorial中建立的应用的修改版本,修改后的版本使用了库中新的ImageFilters类。
 
本教程的主要目的是教你如何使用静态库,因此修改后的工程包括了全部应用须要的源代码。这样你就能够将注意力集中在使用库所须要的工程设置上。
 
方法 1: 头文件和库二进制文件
在本节中,你须要下载 starter project for this section。复制压缩文件到硬盘上的任意文件夹并解压。能够看到以下的文件夹结构:
 
为了方便起见,.a通用库文件和头文件已经复制了一份在其中,但工程并未设置使用它们。你将从这里开始。
 
注意: 标准的Unix引入惯例是一个include文件夹,用来存放头文件,一个lib文件夹用来存放库文件(.a)。这种文件夹结构这是一种惯例,并不强制。你并不须要必定听从这种结构或者复制文件到工程文件夹中。在你本身的应用中,你能够任意选择头文件和库文件的位置,只要随后在Xcode工程中设置了适当的路径。
 
打开工程,构建并运行你的应用,将会看到如下错误:
 
正如所指望的那样,应用并不知道去哪里寻找头文件。为了解决这个问题,你须要在工程中添加一个Header Search Path,指明头文件存放的位置。设置头文件搜索路径始终是使用静态库的第一步。
 
按照下图示范,在导航栏中点击工程根节点(1),选择CoreImageFun目标(2)。选择Build Settings(3),在列表中找出Header Search Paths设置项。若是必要,能够在搜索框中输入”header search”来过滤庞大的设置列表(4)。
 
双击Header Search Paths项,弹出一个浮动窗口,点击+按钮,输入:
  1. $SOURCE_ROOT/include 
弹出窗口应该以下所示:
 
$SOURCE_ROOT是一个Xcode环境变量,指向工程根文件夹。Xcode会使用包含你工程的实际文件夹代替此变量,这意味着即便你把工程移动到其它文件夹或驱动器,它仍然能够指向最新的位置。
在弹出窗口范围外点击鼠标使其消失,你会看到Xcode已经自动将变量转换为工程的实际位置,如图所示:
 
构建并运行应用,看看结果是什么。呃……一些连接错误出现了:
 
这看起来并非很好,可是给了你另外一个你所须要的信息。仔细看,会发现全部的编译错误全都消失了,所有被连接错误所代替。这表示Xcode找到了头文件而且用它去编译应用,但在连接阶段,Xcode没法找到ImageFilter类的结果代码。为何?
 
很简单 — 你尚未告诉Xcode去哪里寻找包含类实现的库文件。(看,没什么大不了)
 
以下面的屏幕截图所示,回到CoreImageFun目标(2)的构建设置(1)。选择Build Phases标签(3),展开Link Binary With Libraries部分(4)。最后,点击+按钮(5)。
 
在出现的窗口中,点击Add Other…按钮,在工程根文件夹下的lib子目录中找到libImageFilters.a库文件,如图:
 
完成这些之后,你的Build Phase标签看起来以下:
 
最后一步是增长-ObjC连接标识。该连接尝试更高效的只包含须要的代码,而有时会排除静态库代码。使用该标识,库中的全部Objective-C类和类别都将被适当的加载。你能够从苹果的 Technical Q&A QA1490了解详细信息。
 
点击Build Settings标签,找到Other linker Flags设置,如图:
 
在弹出窗口中,点击+按钮并输入-ObjC,如图:
 
最后构建并运行应用,此时应该不会获得任何构建错误信息,应用顺利展现它的光彩之处:
 
拖动滑块改变滤镜级别,或者点击GrayScale按钮。对图片应用滤镜的代码来自于静态库,而不是应用。
 
恭喜 — 你已经构建了你的第一个静态库并在一个真正的应用里使用它!你会发现这种包含头文件和库的方法在不少第三方库中使用,如AdMob,TestFlight或一些不提供源代码的商业库。
 
方法 2: 子项目
在这部分,请在 这里下载所需工程。
 
复制下载的文件到任意位置,解压。能够看到如下文件夹结构:
 
 
若是学习了方法一,你可能注意到了工程的差别。这个工程里没有任何的头文件和静态库文件 — 由于根本不须要。做为替代方案,你要将你在本教程开始建立的ImageFilters库工程添做为依赖加到本工程中。
在作这些以前,构建并运行应用。会看到如下错误:
 
若是学习过上一个方法,你已经知道如何修复这个问题。在示例工程中,你在ViewController类中使用了ImageFilters类,但并未告诉Xcode去哪里寻找头文件。Xcode会尝试寻找ImageFilters.h文件,可是失败了。
 
将ImageFilters库工程做为子项目所需的全部操做就是拖拽库工程文件到库文件树中。若是该工程已经在另外一个Xcode窗口中被打开,那么Xcode没法正确将其添加为子工程。因此在继续本教程以前,确保ImageFilters库工程已经被关闭。
 
在Finder中找到名为ImageFilters.xcodeproj库工程文件。拖拽它到CoreImageFun工程左侧的导航栏中,如图:
 
完成拖放后,你的工程浏览视图应该以下图所示:
 
如今Xcode已经识别了子工程,你能够将库添加为工程依赖。这样Xcode就能够在构建主应用以前确保库为最新版本。
 
点击工程文件(1),选择CoreImageFun目标(2)。点击Build Phases标签(3)并展开Target Dependencies(4),如图:
 
点击+按钮增长一个新依赖。以下图所示,确保你从弹出窗口中选择了ImageFilters目标(不是universalLib):
 
添加完成以后,依赖窗口应该如图所示:
 
最后,设置静态库工程连接到应用。展开Link Binary with libraries,点击+按钮,如图:
 
选择libImageFilters.a,点击Add:
 
添加库以后,Link Binary with Libraries部分应该如图所示:
 
像方法一那样,最后一步是增长-ObjC连接标识。点击Build Settings标签,找到Other linker Flags设置,如图:
 
在弹出窗口中,点击+按钮并输入-ObjC,如图:
 
构建并运行应用,应该没有任何错误,应用会再一次被打开:
拖动滑块或者点击GrayScale按钮查看图片滤镜结果。滤镜逻辑的代码彻底包含在库中。
 
若是按照第一种方法在应用中添加库(使用头文件和库文件),你可能注意到和第二种方法的区别。在方法二中,你没有在工程设置中添加任何头文件搜索路径。另外一个区别是你没有使用通用库。
 
为何会有这样的区别?当添加一个库做为一个子工程,Xcode会为你考虑几乎全部的事情。添加子工程和依赖后,Xcode知道去哪里寻找头文件和二进制文件,也知道根据你的设置去选择哪须要构建哪个架构的库。这很是方便。
 
若是你使用你本身的库或者拥有源代码和工程文件,将库做为子工程不失为一个引入静态库的简便的方法。让你更容易做为工程依赖构建整合,并担忧更少的事情。
 
将来
你能够从这里下载到包括了本教程全部代码的工程。
 
但愿本教程可以让你对静态库的基本概念和怎样在应用中使用它们有一个更深刻的了解。
 
下一步就是用所学到的知识构建你本身的库了。你确定有一些添加到工程中通用类。它们是你加入你本身的可复用库的优秀候选人。你也能够考虑根据功能建立多个库:网络部分的代码做为一个,UI部分做为另外一个,等等。你能够只向工程中添加所须要的代码。
 
为了强化和深刻探讨你在本教程中所学到的概念,我推荐苹果的文档 Introduction to Using Static Libraries in iOS

 

http://blog.csdn.net/pcwe2002/article/details/45579241html

相关文章
相关标签/搜索