在iOS中建立靜態庫

若是您有不錯的原創或譯文,歡迎提交給咱們,更歡迎其餘朋友加入咱們的翻譯小組(聯繫qq:2408167315)。
 ===============================================================================
 
若是你做爲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
相關文章
相關標籤/搜索