作者:shede333 主頁(yè):http://my.oschina.net/shede333 版權(quán)聲明:原創(chuàng)文章,版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名 | [Creative Commons BY-NC-ND 3.0][]
本人英語(yǔ)也不是太好,翻譯質(zhì)量不是太高,如有不妥之處,歡迎指點(diǎn)批評(píng)。
點(diǎn)此查看文章 英文原文
創(chuàng)建IOS靜態(tài)庫(kù)
如果你開發(fā)ios有一段時(shí)間了,你可能有許多想在你大部分項(xiàng)目里重用的類和工具函數(shù)。
重用代碼最容易的方法就是復(fù)制/黏貼,但是,這在代碼維護(hù)上很快就變成一個(gè)噩夢(mèng)。 既然每一個(gè)app都擁有一份 共享代碼 的拷貝,這就很難保證所有 拷貝的代碼 與 共享代碼 在bug修正與更新的同步(一致性)。
這里就使用靜態(tài)庫(kù)來(lái)拯救噩夢(mèng)。靜態(tài)庫(kù)就是類、函數(shù)、定義(definitions)和資源的一個(gè)包,使用靜態(tài)庫(kù),你能把代碼打包在一起,并且在你所有的項(xiàng)目間共享。
在這個(gè)教程,你將親身經(jīng)歷使用兩種不同的方法創(chuàng)建你自己的通用靜態(tài)庫(kù)。
你應(yīng)該熟悉Objective-C and iOS開發(fā),才能理解大致上這個(gè)教程。 如果你對(duì)怎樣做一個(gè)相同的app 以及 圖像濾光代碼在庫(kù)里的工作原理 感興趣,Core Image 的相關(guān)知識(shí)雖然不是必須的,但是對(duì)你會(huì)很有幫助。
準(zhǔn)備開始高效的減少、重用和循環(huán)使用你的代碼吧!
為什么使用靜態(tài)庫(kù)
你可能因?yàn)楹芏嘣蚨鴦?chuàng)建靜態(tài)庫(kù):
- 你想要把你或者你團(tuán)隊(duì)里的成員編寫的類打包在一起,便于合理的使用,并且很容易與周圍的人分享。
- 你想要讓所有通用的代碼集中到一起,便于你對(duì)代碼的bug修復(fù)與更新。
- 你想和一些人分享你的代碼庫(kù),但是你不想要他們看到你的代碼。
- 隨著開發(fā)時(shí)間的進(jìn)展,你想要做一個(gè)版本庫(kù)的快照。
在這個(gè)教程里,假設(shè)你已經(jīng)看過(guò)Core Image Tutorial這個(gè)教程,并且理解了如何使用一些照片處理效果的代碼。
你將會(huì)把那些代碼添加到靜態(tài)庫(kù)里,并且在一個(gè)修改過(guò)的app里使用靜態(tài)庫(kù)。最終將會(huì)得到一個(gè)相同的app,但是會(huì)體現(xiàn)出面陳提到的所有優(yōu)點(diǎn)。
開始 Let`s Go!
打開Xcode,選擇File\New\Project,當(dāng)Choose a template對(duì)話框出現(xiàn)時(shí),選擇iOS\Framework & Library\Cocoa Touch Static Library,如下圖:
點(diǎn)擊Next,在項(xiàng)目選項(xiàng)對(duì)話框,輸入ImageFliters作為項(xiàng)目名稱(project name),然后輸入一個(gè)唯一的公司標(biāo)識(shí)(company identifier ),并且勾選Use Automatic Reference Counting,而Include Unit Tests不要勾選。
點(diǎn)擊Next,最后選擇一個(gè)你想要保存項(xiàng)目的位置,然后點(diǎn)擊Create。
Xcode已經(jīng)創(chuàng)建了一個(gè)準(zhǔn)備使用靜態(tài)庫(kù)的項(xiàng)目,并且項(xiàng)目里已經(jīng)自動(dòng)添加了ImageFliters類。(這難道不是Xcode的優(yōu)點(diǎn)么?)這就是你將要編寫的圖像濾光代碼的地方。
注意:你可以你想要的任何類到靜態(tài)庫(kù)里,甚至刪除原來(lái)的代碼(ImageFliters類)。
在這個(gè)教程,所有代碼將會(huì)添加到Xcode開始自帶的類ImageFliters。
既然你項(xiàng)目仍然空,讓我們添加一些代碼進(jìn)去吧!
項(xiàng)目:Image Filters
這個(gè)庫(kù)是為ios設(shè)計(jì)的,并且使用了UIKit框架,所以,你要做的第一件事是:在頭文件,導(dǎo)入(import)UIKit框架,打開ImageFilters.h ,并且在該文件最頂部添加下列代碼:
#import <UIKit/UIKit.h>
接下來(lái),在文件@interface ImageFilters : NSObject 這一行的下面,黏貼如下聲明代碼:
@property (nonatomic,readonly) UIImage *originalImage;
- (id)initWithImage:(UIImage *)image;
- (UIImage *)grayScaleImage;
- (UIImage *)oldImageWithIntensity:(CGFloat)level;
這些頭文件的聲明代碼,定義了類的公開接口。當(dāng)其他開發(fā)者(包括你自己)使用這個(gè)庫(kù),通過(guò)讀這個(gè)頭文件,他們就知道類名和內(nèi)嵌的庫(kù)中的方法。
現(xiàn)在,是時(shí)候添加實(shí)現(xiàn)代碼 (implementation)了, 打開ImageFilters.m ,在這行#import "ImageFilters.h" 下面黏貼如下代碼:
@interface ImageFilters()
@property (nonatomic,strong) CIContext context; @property (nonatomic,strong) CIImage beginImage;
@end
上面的代碼聲明了許多用于類內(nèi)部的屬性,這些屬性并不是公開的,所以任何使用這個(gè)庫(kù)的app都不能訪問(wèn)這些屬性。
最后,你需要實(shí)現(xiàn)類中的方法,在這行@implementation ImageFilters 下面黏貼如下代碼:
- (id)initWithImage:(UIImage *)image
{
self = [super init];
if (self) {
_originalImage = image;
_context = [CIContext contextWithOptions:nil];
_beginImage = [[CIImage alloc] initWithImage:_ originalImage];
}
return self;
}
- (UIImage*)imageWithCIImage:(CIImage *)ciImage
{
CGImageRef cgiImage = [self.context createCGImage:ciImage fromRect:ciImage.extent];
UIImage *image = [UIImage imageWithCGImage:cgiImage];
CGImageRelease(cgiImage);
return image;
}
- (UIImage *)grayScaleImage
{
if( !self.originalImage)
return nil;
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;
CIImage *output = [CIFilter filterWithName:@"CIExposureAdjust" keysAndValues:kCIInputImageKey, grayScaleFilter, @"inputEV", NSNumber numberWithFloat:0.7], nil].outputImage;
UIImage *filteredImage = [self imageWithCIImage:output];
return filteredImage;
}
- (UIImage *)oldImageWithIntensity:(CGFloat)intensity
{
if( !self.originalImage )
return nil;
CIFilter *sepia = [CIFilter filterWithName:@"CISepiaTone"];
[sepia setValue:self.beginImage forKey:kCIInputImageKey];
[sepia setValue:@(intensity) forKey:@"inputIntensity"];
CIFilter *random = [CIFilter filterWithName:@"CIRandomGenerator" ;
CIFilter *lighten = [CIFilter filterWithName:@"CIColorControls"];
[lighten setValue:random.outputImage forKey:kCIInputImageKey];
[lighten setValue:@(1 - intensity) forKey:@"inputBrightness"];
[lighten setValue:@0.0 forKey:@"inputSaturation"];
CIImage *croppedImage = [lighten.outputImage imageByCroppingToRect:[self.beginImage extent]];
CIFilter *composite = [CIFilter filterWithName:@"CIHardLightBlendMode"];
[composite setValue:sepia.outputImage forKey:kCIInputImageKey];
[composite setValue:croppedImage forKey:kCIInputBackgroundImageKey];
CIFilter *vignette = [CIFilter filterWithName:@"CIVignette"];
[vignette setValue:composite.outputImage forKey:kCIInputImageKey ;
[vignette setValue:@(intensity * 2) forKey:@"inputIntensity"];
[vignette setValue:@(intensity * 30) forKey:@"inputRadius"];
UIImage *filteredImage = [self imageWithCIImage:vignette outputImage];
return filteredImage;
}
這上面代碼實(shí)現(xiàn)了初始化和執(zhí)行Core Image 濾光效果的方法。 因?yàn)榻忉屔厦?code>Core Image代碼的功能,超出本教程的范圍, 所以,您能在這個(gè)Core Image Tutorial教程里,學(xué)習(xí)Core Image 和濾光的相關(guān)知識(shí)。
此時(shí),你的靜態(tài)庫(kù)擁有ImageFilters 類,這個(gè)類公開如下3個(gè)方法:
- initWithImage :初始化類
- grayScaleImage :創(chuàng)建圖片的灰度效果
- oldImageWithIntensity :創(chuàng)建圖片的陳舊效果
現(xiàn)在編譯并且運(yùn)行你的庫(kù)(將Run按鈕旁邊的編譯目標(biāo)改為iOS Device,這樣便于后面找到.a文件), 你將會(huì)注意到,點(diǎn)擊Xcode的“Run”按鈕,只執(zhí)行編譯; 實(shí)際上,你不能通過(guò)運(yùn)行你的靜態(tài)庫(kù)來(lái)看到結(jié)果,因?yàn)檫€沒有app支持它。
靜態(tài)庫(kù)使用.a 作為擴(kuò)展名,而不是.app 、.ipa 等。 生成的靜態(tài)庫(kù)在Xcode導(dǎo)航欄的Products文件夾里, 右擊 或者 “按住CTRL,單擊” libImageFilters.a ,在彈出的菜單里選擇Show in Finder,如下圖:
Xcode將會(huì)在iFinder打開文件夾,你會(huì)看到如下:
一個(gè)共享庫(kù)的最終最終產(chǎn)品有2個(gè)部分:
- 頭文件(Header files):在include文件夾里,你找到靜態(tài)庫(kù)的所有公開頭文件(.h文件)。
因?yàn)槟阒挥?個(gè)公共類,所以這個(gè)文件夾只有單獨(dú)的ImageFilters.h 文件。 你在之后的app項(xiàng)目里面,為了讓Xcode在編譯時(shí)知道 輸出類(the exported classes),需要用到這個(gè)頭文件。
- 二進(jìn)制庫(kù)文件(Binary Library):Xcode生成的靜態(tài)庫(kù)文件是
ImageFilters.a 。當(dāng)你想要在里使用這個(gè)庫(kù)的時(shí)候,你需要使用這個(gè)文件來(lái)連接靜態(tài)庫(kù)。
導(dǎo)入新框架(framework)到app,與導(dǎo)入靜態(tài)庫(kù)很相似。 你只需要簡(jiǎn)單的導(dǎo)入框架頭文件(framework header)和連接到app的框架代碼(framework code)。
靜態(tài)庫(kù)使用小警告:默認(rèn)情況下,編譯生成的庫(kù)文件僅僅適用于Xcode當(dāng)前指定的架構(gòu)(真機(jī)的框架為ARM,模擬器框架為i386) 。 假如你目前的編譯目標(biāo)為模擬器,庫(kù)文件將包含適用于i386架構(gòu)的對(duì)象代碼; 假如你目前的編譯目標(biāo)為真機(jī)設(shè)備,庫(kù)文件將包含適用于ARM架構(gòu)的對(duì)象代碼; 你需要編譯出兩個(gè)版本的靜態(tài)庫(kù)文件,當(dāng)你的編譯目標(biāo)在真機(jī)和模擬器間切換時(shí),要連接使用對(duì)應(yīng)的庫(kù)文件。
那要怎么做才能解決上面的問(wèn)題呢?
幸運(yùn)的是,有一個(gè)很好的辦法, app項(xiàng)目不需要?jiǎng)?chuàng)建多個(gè)配置或者最終產(chǎn)品(build products),就能支持多個(gè)平臺(tái)。 您可以創(chuàng)建一個(gè)universal binary,它包含這兩個(gè)架構(gòu)的對(duì)象代碼。
Universal Binaries(通用二進(jìn)制)
通用二進(jìn)制 是一種包含多個(gè)架構(gòu)對(duì)象代碼的特殊二進(jìn)制文件。 在Mac電腦的cpu從PowerPC (PPC) 過(guò)渡到 Intel (i386) 時(shí),你對(duì) 通用二進(jìn)制 可能會(huì)熟悉。 在過(guò)渡期間,Mac上的app通常裝載了通用二進(jìn)制,通用二進(jìn)制在一個(gè)二進(jìn)制文件內(nèi)包含了兩個(gè)平臺(tái)的可執(zhí)行代碼,以便app在 Intel 和 PowerPC 架構(gòu)的Mac電腦上均可運(yùn)行。
支持ARM和i386架構(gòu)的概念并沒有太多區(qū)別。既然這樣,靜態(tài)庫(kù)文件將包含適用于ios設(shè)備(ARM)和模擬器(i386)代碼。Xcode將會(huì)識(shí)別通用二進(jìn)制,并且你沒戲編譯app時(shí),Xcode會(huì)根據(jù)當(dāng)前的編譯目標(biāo),選擇適當(dāng)?shù)募軜?gòu)。
為了創(chuàng)建通用二進(jìn)制庫(kù),你需要使用一個(gè)系統(tǒng)工具lipo。
不用擔(dān)心小貓咪,lipo并不是指的上圖中的脂肪(這句話屬于美式幽默,我也不太懂)。
lipo 是一種允許你在通用二進(jìn)制文件進(jìn)行操作(操作包括:創(chuàng)建通用二進(jìn)制文件、顯示文件內(nèi)容等等)的命令行工具。 在這個(gè)教程里,你將會(huì)使用lipo,將不同架構(gòu)的二進(jìn)制文件 合并成 包含多個(gè)架構(gòu)的內(nèi)容的單獨(dú)二進(jìn)制文件。 你能在命令行里直接使用lipo,但是在這個(gè)教程里面,你將會(huì)通過(guò)運(yùn)行 一個(gè)命令行腳本來(lái)讓Xcode為你工作,這個(gè)命令行能創(chuàng)建通用二進(jìn)制文件。
在Xcode,一個(gè)聚合目標(biāo)(An Aggregate Target)將會(huì)一次編譯多個(gè)目標(biāo)(target),包括命令行腳本。 在Xcode菜單欄里操作File/New/Target,出現(xiàn)如下對(duì)話框,選擇 iOS/Other,再點(diǎn)擊Aggregate,如下圖
在 Product Name 輸入UniversalLib,確保Project 欄選中ImageFilters項(xiàng)目,如下圖
在項(xiàng)目導(dǎo)航欄上點(diǎn)擊ImageFilters(如下圖操作1),然后選擇UniversalLib目標(biāo)(如下圖操作2),切換到到Build Phases標(biāo)簽(如下圖操作3), 這里就是你將創(chuàng)建行為的地方,當(dāng)目標(biāo)被編譯時(shí),這里的行為將會(huì)運(yùn)行。
點(diǎn)擊右下角Add Build Phase按鈕,在彈出菜單里選擇Add Run Script,如下圖:
現(xiàn)在你要?jiǎng)?chuàng)建一個(gè)腳本。展開Run Script模塊,將下面的shell代碼黏貼進(jìn)去。
# define output folder environment variable
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
# Step 1. Build Device and Simulator versions
xcodebuild -target ImageFilters ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ ROOT="${BUILD_ROOT}"
xcodebuild -target ImageFilters -configuration ${CONFIGURATION} -sdk iphonesimulator -arch i386 BUILD_DIR="${BUILD_DIR}" BUILD_ ROOT="${BUILD_ROOT}"
# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
# Step 2. Create universal binary file using lipo
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"
# Last touch. copy the header files. Just for convenience
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/include" "${UNIVERSAL_ OUTPUTFOLDER}/"
上面的代碼并不復(fù)雜,下面將逐行解釋代碼如何運(yùn)行:
- UNIVERSAL_OUTPUTFOLDER :通用二進(jìn)制文件“Debug-universal”將被拷貝到的目標(biāo)文件夾的路徑名稱。
- Step 1 代碼的第二行,你將調(diào)用xcodebuild,并且指導(dǎo)它編譯ARM架構(gòu)二進(jìn)制文件(你將會(huì)在這行看到參數(shù)-sdk iphoneos)。
- 下一行代碼再次調(diào)用xcodebuild,并且在另一個(gè)文件夾為iPhone模擬器編譯生成i386架構(gòu)二進(jìn)制文件,但這次的參數(shù)為-sdk iphonesimulator -arch i386。
(假如你對(duì)此很感興趣,你您能在man page學(xué)習(xí)更多xcodebuild的相關(guān)知識(shí))
- Step 2 現(xiàn)在你已經(jīng)擁有分別針對(duì)兩種架構(gòu)的兩個(gè).a靜態(tài)庫(kù)文件,你只要調(diào)用 lipo -create 并且設(shè)置它創(chuàng)建、輸出一個(gè)通用二進(jìn)制文件。
- 在最后一行,你將頭文件拷貝到 用于生成通用二進(jìn)制的文件夾內(nèi)(使用cpshell命令行)。
你的“Run Script ”窗口應(yīng)該如下圖一樣:
現(xiàn)在你要準(zhǔn)備去編譯通用版本的靜態(tài)庫(kù)。在策略選擇下拉表,選擇聚合目標(biāo)UniversalLib,如下圖(你的Xcode有一點(diǎn)可能和下圖不同,下圖中的“IOS Device”,在你的Xcode可能顯示為你真實(shí)的設(shè)備名稱)。
點(diǎn)擊Run按鈕,編譯聚合策略(aggregate scheme)里選擇的目標(biāo)。
為了看到結(jié)果,對(duì)項(xiàng)目導(dǎo)航欄里的Product文件夾下的libImageFilters.a右擊,選擇Show in Finder, 為了能看到文件夾的上下層次,切換Finder里顯示文件夾的樣式為列表樣式,你將會(huì)看到一個(gè)新文件夾Debug-Universal(如果你在編譯Release版本,文件夾名可能會(huì)是Release-Universal*),這個(gè)文件夾包含通用版本(Universal)靜態(tài)庫(kù),如下圖:
你會(huì)發(fā)現(xiàn),除了模擬器和設(shè)備文件夾,通用靜態(tài)庫(kù)文件和頭文件都出現(xiàn)了。(這行翻譯可能有誤,原文如下)。
You’ll find the usual headers and static library files are present, except this one links with both the simulator and the device.
以上,就是你為了創(chuàng)建自己的靜態(tài)庫(kù)而需要學(xué)習(xí)的東西。
簡(jiǎn)單來(lái)說(shuō),一個(gè)靜態(tài)庫(kù)項(xiàng)目和一個(gè)app項(xiàng)目很相似。你可以有一個(gè)或多個(gè)類,最終編譯出來(lái)的產(chǎn)品是頭文件個(gè)一個(gè)裝有代碼的.a文件,這個(gè).a文件就是能被連接到多個(gè)app里的靜態(tài)庫(kù)。
在自己的App里使用靜態(tài)庫(kù)
在自己的App里使用ImageFilters類與直接從源碼使用 差不多:導(dǎo)入頭文件并且開始使用類。 (這行翻譯可能不太準(zhǔn)確,原文如下)
Using the ImageFilters class in your own app is not very different from using it directly from source: you import the header file and start using the class!
問(wèn)題是,Xcode并不知道頭文件或者二進(jìn)制文件的位置。
把靜態(tài)庫(kù)放進(jìn)項(xiàng)目里,這有兩個(gè)方法:
- 方法1: 直接引用頭文件和庫(kù)的二進(jìn)制文件(.a文件)。
- 方法2: 在你自己的項(xiàng)目里導(dǎo)入靜態(tài)庫(kù)項(xiàng)目,把靜態(tài)庫(kù)作為你項(xiàng)目的子項(xiàng)目。
選擇其中一個(gè)方法或者其他方法,依賴于你自己的選擇: 在你的項(xiàng)目中,是否需要靜態(tài)庫(kù)的源碼和項(xiàng)目文件。
在這個(gè)教程里,這兩個(gè)方法都在下面單獨(dú)詳細(xì)描述了。你可以嘗試其中的一個(gè)方法,但是最好按照下面描述的順序,把兩個(gè)方法都了解一下。 在兩個(gè)方法的開始部分,你將被要求下載一個(gè)zip文件,這個(gè)文件是教程Core Image Tutorial里的app的一個(gè)修改版本。(修改:使用了來(lái)自于靜態(tài)庫(kù)的新類ImageFilters)。
既然這個(gè)教程的目標(biāo)是叫你怎樣使用靜態(tài)庫(kù),這個(gè)修改版本包含所有app需要的源碼。 這樣,你就能專注于項(xiàng)目使用靜態(tài)庫(kù)的配置。
方法1:引用頭文件和庫(kù)的二進(jìn)制文件(.a文件)
對(duì)于這部分教程,你需要下載 starter project for this section。 拷貝下載好的zip文件到磁盤的任意一個(gè)文件夾下,解壓zip。你將看到如下的文件夾結(jié)構(gòu):
為了讓你方便,靜態(tài)庫(kù)頭文件和.a文件的副本已經(jīng)被包含在項(xiàng)目中,你就不用再次拷貝了, 但是項(xiàng)目并沒有配置好來(lái)使用靜態(tài)庫(kù),這就是你要做的
- 注意:經(jīng)典Unix慣例,引用外部包,
- 引用的文件夾里面有一個(gè)包含頭文件的文件夾“include”,
- 還有一個(gè)包含.a文件的文件夾 “l(fā)ib”。
- 這個(gè)文件夾結(jié)構(gòu)僅僅是一個(gè)慣例,不是強(qiáng)制性的。
- 在你的項(xiàng)目里引用靜態(tài)庫(kù)時(shí),你并不需要遵循這個(gè)文件結(jié)構(gòu)。在你自己的app里,你可以把頭文件和庫(kù)文件放在項(xiàng)目的任意位置,接下來(lái),只要在你配置Xcode的時(shí)候,設(shè)置合理的目錄路徑就行。(這個(gè)目錄路徑必須能夠讓Xcode找到所需的.a文件和頭文件)。
打開項(xiàng)目,編譯、運(yùn)行你的app,你將會(huì)看到如下編譯錯(cuò)誤提示:
如我所料,app無(wú)法找到頭文件。 為了解決這個(gè)問(wèn)題,你需要在項(xiàng)目里增加頭文件搜索路徑(Header Search Path),來(lái)指出頭文件所在文件夾的位置。使用靜態(tài)庫(kù)的時(shí)候,總是首先配置頭文件搜索路徑(Header Search Path)
如下圖所示,按照?qǐng)D示的1~4的步驟操作,在第3步點(diǎn)擊了Build Settings標(biāo)簽后,在搜索欄輸入關(guān)鍵字“header search”,以便快速定位到我們配置的那行參數(shù)。
雙擊Header Search Paths這行的后半空白部分,將會(huì)彈出下圖界面,點(diǎn)擊下圖左下角的“+”按鈕,然后輸入一下信息:
$SOURCE_ROOT/include
$SOURCE_ROOT是Xcode的環(huán)境參數(shù),指的是項(xiàng)目的根文件夾(the project’s root folder), Xcode將會(huì)使用包含該項(xiàng)目的真實(shí)文件夾的路徑地址來(lái)代替此參數(shù),這樣,即使你把整個(gè)項(xiàng)目移動(dòng)到別的文件夾或者移動(dòng)到別的電腦上,你也不用再更改路徑參數(shù)了,因?yàn)閄code利用此參數(shù)來(lái)幫你解決了這些麻煩的問(wèn)題。
點(diǎn)擊上圖彈出框的外圍部分來(lái)關(guān)閉彈出框,你將會(huì)看到,Xcode已經(jīng)把參數(shù)“$SOURCE_ROOT”轉(zhuǎn)換為項(xiàng)目所在文件夾的真實(shí)路徑地址,如下圖:
再次編譯運(yùn)行你的app,你會(huì)發(fā)現(xiàn)還會(huì)存在error,如下所示:
看起來(lái)情況不太好,但是Xcode也給出了部分提示信息。如果你仔細(xì)看看error信息,上面編譯出現(xiàn)的“編譯error信息”已經(jīng)消失,取而代之,現(xiàn)在的error信息是連接錯(cuò)誤(linker errors)。 這就說(shuō)明,Xcode已經(jīng)找到頭文件并且用它來(lái)編譯app,但是程序連接階段,Xcode找不到ImageFliter類對(duì)象代碼。 為什么呢?
這很簡(jiǎn)單,你還沒告訴Xcode在哪里你呢個(gè)找到包含類實(shí)現(xiàn)代碼的庫(kù)文件(.a文件)。 (看樣子,這也不是什么太糟糕的問(wèn)題)
如下圖所示,按照1~5的步驟操作,
- 1:點(diǎn)擊項(xiàng)目文件
- 2:選擇CoreImageFun目標(biāo)
- 3:切換到Build Phases 標(biāo)簽
- 4:點(diǎn)擊Link Binary With Libraries部分最左邊的小箭頭,展開該部分
- 5:點(diǎn)擊該部分左下角的“+”按鈕,彈出“添加庫(kù)”界面
下圖為“添加庫(kù)”界面,點(diǎn)擊左下角的Add Other…按鈕,定位到該項(xiàng)目文件夾里的lib文件夾,找到libImageFilters.a庫(kù)文件,添加進(jìn)去。
當(dāng)你做完以上步驟,下圖就應(yīng)該是你XcodeBuild Phases標(biāo)簽界面的樣子(注意,libImageFilters.a已經(jīng)被添加進(jìn)去了。)
最后一步,在Xcode里添加-ObjC編譯標(biāo)識(shí), 這個(gè)標(biāo)識(shí)有時(shí)可以排除部分靜態(tài)庫(kù)代碼,只把有效的代碼導(dǎo)入項(xiàng)目。(見下段注解)
-ObjC編譯標(biāo)識(shí): (此段為譯者注)
例如,你的項(xiàng)目使用了第三方開源類JSONKit,而你的靜態(tài)庫(kù)也用到了這個(gè)JSONKit,那么你把靜態(tài)庫(kù)導(dǎo)入到項(xiàng)目后,這就產(chǎn)生沖突,所以你在把靜態(tài)庫(kù)導(dǎo)入項(xiàng)目時(shí),靜態(tài)庫(kù)里的JSONKit.h頭文件就不必導(dǎo)入,因?yàn)槟愕捻?xiàng)目里已經(jīng)有了JSONKit.h,JSONKit.m文件,使用-ObjC編譯標(biāo)識(shí),Xcode就會(huì)變得聰明,Xcode知道你的項(xiàng)目里已經(jīng)有了JSONKit的實(shí)現(xiàn)代碼,即JSONKit.m文件,所以,靜態(tài)庫(kù)的JSONKit.m文件就不會(huì)被導(dǎo)入,這樣,你的項(xiàng)目和靜態(tài)庫(kù)就共用你項(xiàng)目里面的JSONKit.m文件。你可以做一個(gè)相關(guān)實(shí)驗(yàn)嘗試一下,把項(xiàng)目和靜態(tài)庫(kù)里的JSONKit.m文件里的內(nèi)容寫的不一樣,看看,最終app里到底使用的哪個(gè)文件。
-ObjC編譯標(biāo)識(shí)功能很多,使用這個(gè)標(biāo)識(shí),可以讓靜態(tài)庫(kù)里的類和類別(categories)文件被合理的加載進(jìn)來(lái)。 你可以在Technical Q&A QA1490學(xué)習(xí)更多-ObjC編譯標(biāo)識(shí)相關(guān)知識(shí)。
添加-ObjC編譯標(biāo)識(shí)的方法如下圖,點(diǎn)擊Build Settings標(biāo)簽,在右上角的搜索框輸入Other linker 即可搜索到。
在Other linker Flags(不用展開該行)這行的后半空白部分雙擊,彈出下圖界面,點(diǎn)擊彈出框左下角的“+”按鈕,輸入-ObjC 即可
最后,編譯、運(yùn)行你的app;你不會(huì)再看到任何編譯error,編譯成功,app將會(huì)運(yùn)行起來(lái),如下圖:
你可以嘗試點(diǎn)擊app上的按鈕和滑動(dòng)條,看看具體效果。 執(zhí)行這些圖片變化效果的代碼并不在app,而是在靜態(tài)庫(kù)。
恭喜你!你剛剛在一個(gè)真實(shí)的app里編譯、運(yùn)行了你的第一個(gè)靜態(tài)庫(kù)。 包含頭文件和靜態(tài)庫(kù)文件的方法,已經(jīng)被用在很多知名的第三方庫(kù)里, 例如AdMob, TestFlight或者那些不想提供源代碼的商業(yè)庫(kù)(百度地圖、微博分享等等)。
方法2:將靜態(tài)庫(kù)作為子項(xiàng)目加載
對(duì)于這部分教程,你需要下載里一個(gè)文件, 點(diǎn)此下載
將下載好的zip文件拷貝你希望的位置,解壓zip,你會(huì)看到如下圖的文件結(jié)構(gòu)
如果你看了上面的方法1,你會(huì)注意到,這次解壓后的項(xiàng)目文件夾的根目錄和方法1的不同: 這個(gè)項(xiàng)目文件夾根目錄,沒有任何頭文件和.a文件(因?yàn)榉椒?不需要這些東西)。 取而代之,你將會(huì)看到,在本教程開始部分創(chuàng)建的ImageFilters靜態(tài)庫(kù)項(xiàng)目 作為子項(xiàng)目被添加到這個(gè)項(xiàng)目里。
在你做其它事情之前,你先編譯、運(yùn)行這個(gè)app,你將會(huì)被下圖的error問(wèn)候:
如果你看過(guò)本教程的方法1,你可能已經(jīng)知道該如何修復(fù)這個(gè)error。 在你剛剛下載解壓的項(xiàng)目里,你在ViewController類里使用了ImageFilters類 ,但你仍然沒有告訴Xcode頭文件在哪里。你運(yùn)行該項(xiàng)目時(shí),Xcode找不到ImageFilters.h文件,所以編譯失敗。
為了把ImageFilters靜態(tài)庫(kù)項(xiàng)目導(dǎo)入進(jìn)來(lái)作為子項(xiàng)目,有2個(gè)方法:
- 導(dǎo)入方法1:打開ImageFilters項(xiàng)目,你只需要從靜態(tài)庫(kù)項(xiàng)目窗口 拖曳 靜態(tài)庫(kù)項(xiàng)目的
項(xiàng)目文件(一般為ImageFilters項(xiàng)目導(dǎo)航欄的最上面的那個(gè)文件) 到主項(xiàng)目(這里是CoreImageFun項(xiàng)目)窗口的導(dǎo)航欄中的任何地方即可,因?yàn)槟壳?strong>ImageFilters項(xiàng)目已經(jīng)在窗口中打開,所以在主項(xiàng)目窗口里顯示的ImageFilters文件只是單個(gè)文件,而不是樹狀結(jié)構(gòu)。你要關(guān)閉這兩個(gè)項(xiàng)目,然后再打開主項(xiàng)目CoreImageFun,你就會(huì)發(fā)現(xiàn)它已經(jīng)變?yōu)闃錉罱Y(jié)構(gòu)。如下圖
- 導(dǎo)入方法2:先關(guān)閉ImageFilters項(xiàng)目,然后在文件夾中,找到靜態(tài)庫(kù)的項(xiàng)目文件(即ImageFilters.xcodeproj文件),拖曳該文件到主項(xiàng)目CoreImageFun的導(dǎo)航欄中即可。如下圖
注意:這兩個(gè)方法的最終效果是一樣的(如下圖),在執(zhí)行上面的方法2,盡量確保子項(xiàng)目(這里指的是ImageFilters靜態(tài)庫(kù)項(xiàng)目)并沒有在Xcode中打開, 否則的話,你需要關(guān)閉這兩個(gè)項(xiàng)目,再重新打開主項(xiàng)目。因?yàn)橥粋€(gè)項(xiàng)目,不能同時(shí)在兩個(gè)窗口中打開。
現(xiàn)在Xcode知道了靜態(tài)庫(kù)子項(xiàng)目,你可以把添加靜態(tài)庫(kù)到項(xiàng)目依賴(Dependencies),如下圖操作。 這就意味著,Xcode將確保在編譯app前,靜態(tài)庫(kù)的代碼是最新的(就是說(shuō),如果靜態(tài)庫(kù)項(xiàng)目有代碼變更,主項(xiàng)目會(huì)自動(dòng)重新編譯靜態(tài)庫(kù)項(xiàng)目)。
Xcode添加項(xiàng)目依賴,如下圖操作1~4,
點(diǎn)擊上圖步驟4的“+”號(hào)按鈕會(huì)彈出另一個(gè)窗口,彈出的窗口如下圖,在下拉列表里選擇ImageFilters目標(biāo)(而不是universalLib),點(diǎn)擊右下角的“add”,完成。
完成上面的添加依賴的步驟后,Build Phases標(biāo)簽的Target Dependencies部分,將會(huì)增加一條,如下圖:
最后一步,配置項(xiàng)目,將靜態(tài)庫(kù)連接到項(xiàng)目。如下圖,展開Link Binary with libraries部分.
點(diǎn)擊上圖的“+”號(hào)按鈕,彈出 下圖的界面, 選擇libImageFilters.a項(xiàng)目,點(diǎn)擊add
添加完成后,Link Binary with Libraries部分如下圖所示:
最后一步,添加-ObjC編譯標(biāo)識(shí),添加步驟與上面的方法1的最后一步一樣,如下圖:
在上圖Other linker Flags行的后半空白部分雙擊,彈出如下圖, 點(diǎn)擊彈出框左下角“+”按鈕,添加 -ObjC 。
編譯運(yùn)行你的app,app將會(huì)運(yùn)行成功,如下圖:
你可以操作一下剛剛運(yùn)行成功的app。 與方法1一樣,圖片效果的邏輯代碼,都在靜態(tài)庫(kù)中。
如果你按照方法1試驗(yàn)過(guò)添加靜態(tài)庫(kù)的過(guò)程(使用頭文件和靜態(tài)庫(kù)),在處理方式上,和方法2有很多不同之處。 在方法2,你沒有為Xcode配置header search paths參數(shù)。 另一個(gè)不同之處,你沒有使用過(guò)通用靜態(tài)庫(kù)(Universal library)。
為什么會(huì)不同? 把靜態(tài)庫(kù)項(xiàng)目添加為子項(xiàng)目,Xcode就幾乎為你解決了所有事情。 添加子項(xiàng)目和依賴之后,Xcode就知道了在哪里找到頭文件和靜態(tài)庫(kù)文件, 而且靜態(tài)庫(kù)的架構(gòu)會(huì)根據(jù)你app選擇的參數(shù)來(lái)編譯,這真是夠方便的。
如果你使用自己的靜態(tài)庫(kù),或者你需要訪問(wèn)源碼和項(xiàng)目文件, 那么,導(dǎo)入靜態(tài)庫(kù)到項(xiàng)目較為方便的方法:方法2(即添加靜態(tài)庫(kù)項(xiàng)目作為子項(xiàng)目); 因?yàn)槟慵勺禹?xiàng)目作為項(xiàng)目依賴,你需要操作、擔(dān)心的事情就很少了,歐耶!
資源文件打包(這部分為譯者添加)
在靜態(tài)庫(kù)里經(jīng)常會(huì)遇到 圖片 、xib 、各種外部文件 等等,這些不能放在靜態(tài)庫(kù)里, 通常的做法是:把這些文件打成一個(gè)bundle包(擴(kuò)展名為.bundle的文件)。 打bundle包,也可以建一個(gè)target,就像合并兩種架構(gòu)靜態(tài)庫(kù)的target的做法差不多。 具體做法點(diǎn)此
接下來(lái)去哪里?
你可以在這里下載這個(gè)教程的所有項(xiàng)目代碼。
對(duì)于靜態(tài)庫(kù)的基本概念、怎樣在自己的app使用靜態(tài)庫(kù),希望這個(gè)教程會(huì)對(duì)你有所幫助。
接下來(lái),用上面的知識(shí)去編譯你自己的靜態(tài)庫(kù)! 你肯定有需要添加到所有項(xiàng)目里的通用類,把這些代碼放在你自己的靜態(tài)庫(kù)里重用會(huì)是個(gè)很好的注意。 你還能夠根據(jù)功能分類,創(chuàng)建多個(gè)靜態(tài)庫(kù): 一個(gè)靜態(tài)庫(kù)放網(wǎng)絡(luò)交互的代碼, 一個(gè)放UI相關(guān)類的代碼,等等。 這樣你就能把你需要的模塊添加到項(xiàng)目里。
為了進(jìn)一步鞏固、更深一步學(xué)習(xí)本教程的內(nèi)容, 我也推薦你看看蘋果官方文檔里有關(guān)靜態(tài)庫(kù)的內(nèi)容 Introduction to Using Static Libraries in iOS
希望你能喜歡這個(gè)教程,如果你有任何問(wèn)題和想法,請(qǐng)加入論壇討論。
以下為譯者注
這篇文章使用Markdown語(yǔ)言編寫,使用了如下工具:
本人英語(yǔ)也不是太好,翻譯質(zhì)量不是太高,如有不妥之處,歡迎在下面留言,指點(diǎn)批評(píng)。
|