建立 iOS 靜態函式庫
来源:互联网 发布:网络流行歌曲大全 对唱 编辑:程序博客网 时间:2024/06/05 12:01
如果你已經開發iOS一段時間了,你可能會有一些自己常用的類別或功能,在你其他專案中也會都會使用這些類別或功能。
最簡單的方法就是將這程式碼複製貼上。很快的這將會變成維護上的夢魘。每個app都各自擁有這些程式碼,每當bug修復時或更新時,很難同時將這些通用的程式碼同時更新。
這時就可透過靜態函式庫(static library)來解決這問題!靜態函式庫可以輕易的將類別,方法,定義和資源打包起來並且讓其他專案使用。
本教程你將會使用兩種方法建立自己的靜態函式庫。
開始本教程前,你需要熟悉Objective-C及iOS。Core Image的相關知識是非必需的,如果你對範例應用程式中的濾鏡效果程式碼的運作很有興趣,這個範例對你將有很大的幫助。
效率是減少、重複使用以及循環再造你的程式碼!
為什麼使用靜態函式庫
很多原因會讓你想建立靜態函式庫。舉例來說:
- 你想綁定一些經常使用的類別讓你或你的同事能簡單的使用它。
- 你想讓這些通用的程式碼集中管理,每當有錯誤修復或更新時能輕易的維護。
- 你想讓大家使用這個函式庫,但你不想讓其他人看到你的程式碼。
- 你想為這個函式庫加上版本的快照。
本教程,假設你已經看過Core Image的教程,並且提供這些原始碼供你使用方便。
你將會將這些程式碼加入到靜態函式庫,而使用靜態函式庫的應用程式不會有什麼改變,為一改變就是得到了上述的條列出的優點。
讓我們開始吧!
開始
開啟Xcode,選擇FileNewProject。如下圖,會看到樣板選擇對話視窗,選擇iOSFramework & LibraryCocoa Touch Static Library。
點選Next。如下圖,專案名稱輸入ImageFilters,勾選Use Automatic Reference Counting,Include Unit Tests不用勾選。
點選Next。最後選擇檔案儲存的路徑並按下Create。
Xcode已經幫你建立好靜態函式庫專案,甚至替你加上了ImageFilters類別。(Xcode是不是很棒呢?) 那裡存放的就是濾鏡效果的程式碼。
Note:你可以加入多個類別到靜態函式庫,或者刪除原本的類別。本教程中,將會將類別加入到ImageFilters
類別內。
由於你的Xcode專案現在是空的,讓我們加入一些程式碼進去吧!
Image Filters
這個函式庫是使用UIKit並針對iOS設計,所以第一件事情是在檔案的最前面要引入UIKit。開啓ImageFIlters.h檔案加入下列的程式碼:
#import <UIKit/UIKit.h>
接著將下列的程式碼宣告貼到 @interface ImageFilters : NSObject
下方。
@property (nonatomic,readonly) UIImage *originalImage; - (id)initWithImage:(UIImage *)image;- (UIImage *)grayScaleImage;- (UIImage *)oldImageWithIntensity:(CGFloat)level;
在header檔案宣告一個public interface(可稱為類別)。當其他開發者(包括自己)要使用這個函式庫,只要閱讀header檔案,就可以知道這個類別內有哪些靜態函式庫的方法可以使用。
現在加入實作。開啓ImageFilters.m並將下列程式碼加到#import "ImageFilters.h"
下方:
@interface ImageFilters() @property (nonatomic,strong) CIContext *context;@property (nonatomic,strong) CIImage *beginImage; @end
上面的程式碼宣告了一些類別內部使用的屬性。這些屬性並不是公開的(public),所以其他應用程式使用並沒有函式庫存取的權限。
最後你要實作該類別的方法。接著將下面的程式碼加到@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;}
這些程式碼實作了初始化(initialization)以及執行Core Image濾鏡的方法。Core Image已經超出本教程的範圍,你可以在Code Image Tutorial中得知更多Core Image濾鏡相關知識。
現在你有一個靜態函式庫,一個公開的ImageFilters類別,這個類別也提供三個方法:
- initWithImage:初始化濾鏡類別
- grayScaleImage:建立一個灰階的濾鏡
- oldImageWithIntensity:建立一個古老效果的濾鏡
現在建構(build)及執行(Run)你的函式庫。你會發現Xcode的執行(Run)按鈕只完成一次的建構(build);你無法看到函式庫執行的結果,因為他背後沒有一個應用程式!
函式庫建構好的檔案副檔名並不是.app或.ipa的,而是一個.a的副檔名檔案。如下圖,你可以在Products的資料夾下找到他。對著libImageFilters.a右鍵或按住control鍵並點擊它,接著選取Show in Finder。
如下圖,Xcode會在Finder中開啟到該檔案的位置:
最終函式庫有兩個部分:
Header files: 在include資料夾內,你會發現這個公開的函式庫。這個案例中只有一個公開的類別,所以在這個資料夾內只有一個ImageFilters.h檔案。你的應用程式專案會需要使用到這個檔案,因此Xcode在編譯的時候才會知道該類別。
Binary Library: ImageFilters.a是由Xcode產生的靜態函式庫檔案。當你的應用程式要使用該函式庫,你需要連結到這個檔案。
這兩個很相似。你只需要將framework header和framework程式碼與你的app做好連結。
共享的函式庫隨時可以使用。預設的情況下,函式庫檔案會編譯成目前的架構建構。如果你針對模擬器建構,函式庫將包含i386的架構程式碼;以及如果你建構的對象是實體裝置,你將會得到ARM架構的程式碼。你需要建構兩個版本的函式庫,根據你選擇的目標是模擬器或是實體裝置,選擇使用不同的連結。
怎麼辦?
很幸運,有一個更好的方法可以支援多個平台,這樣就不用建立多個版本了。你可以建立一個universal binary,它包含了兩個架構的程式碼。
Universal Binaries
universal binary是個特別的binary檔案,它包含了多個架構的程式碼。在Mac電腦上,你可能熟悉PowerPC(PPC)到Intel(i386)的universal binary。轉換時,Mac應用程式會附帶一個universal binary檔案,所以應用程式可以同時支援Intel與PowerPC。
支援ARM與i386的概念並沒有太大的不同。這個範例中,靜態函式庫包含的程式碼將會支援iOS裝置(ARM)與模擬器(i386)的架構。Xcode會將函式庫視為一個通用的(universal),當你每次建構你的應用程式時,將會根據建構的目標選擇適當的架構。
為了建立一個universal binary函式庫,你需要使用一個名為lipo。
別擔心,這不是那liop! :]
lipo
是一個命令列工具,它允許你在universal檔案執行操作。(例如建立universal binary,條列universal檔案內容等)。在本教程中你將會使用lipo
將兩個不同架構的binary檔案整合成一個binary檔案。你可以直接在命令列工具使用lipo
,但在本教程中,你將會使用Xcode幫你執行命令列腳本建立universal library。
Aggregate Target在Xcode會一次建構多個目標,包括命令列腳本。在Xcode目錄點選到File/New/Target。選擇iOS/Other,並點選Aggregate,如下圖:
這個目標稱為UniversalLib,並且確定專案選擇到ImageFilters,如下圖:
在專案導覽列點選ImageFilters專案,然後選擇到UniversalLib target。切換到Build Phases分頁;你設定的動作會在target建立時執行。
如下圖,現在點選Add Build Phase按鈕,在彈出的視窗選擇Add Run Script:
現在你需要設定腳本。展開Run Script模組,並且貼上下列的腳本:
# define output folder environment variableUNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal # Step 1. Build Device and Simulator versionsxcodebuild -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 existsmkdir -p "${UNIVERSAL_OUTPUTFOLDER}" # Step 2. Create universal binary file using lipolipo -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 conveniencecp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/include" "${UNIVERSAL_OUTPUTFOLDER}/"
這邊的腳本並不複雜,下面會一行一行的解釋:
- UNIVERSAL_OUTPUTFOLDER資料夾名稱,這裡會得到的資料夾名稱為”Debug-universal”
- Step 1. 第二行調用xcodebuild並指示它建構ARM架構binary(你會在這邊看到 -sdk iphoneos參數)
- 下一行會再次呼叫xcodebuild,並且建構一個Intel架構的binary檔案到iPhone Simulator資料夾內,關鍵的參數為-sdk iphonesimulator -arch i386。(如果你有興趣,在這[連結]你可以學到很多關於xcodebuild)
- Step 2.現在你已經有兩個不同架構的.a檔案,現在你只需要使用lipo-create並設置它來建構universal binary。
- 最後一行將複製header file到universal build資料夾(使用cp指令)
如下圖,執行腳本的視窗:
現在準備建構static library通用的版本。如下圖,在Scheme Selection下拉選單中選擇UniversalLib(下圖顯示的是”iOS Device”,如果你有連接實體裝置,將會顯示該裝置的名稱):
按下Play按鈕建構aggregate scheme的target。
對著libImageFilters.a再次右鍵Show in Finder可以看到建構的結果。將Finder的顯示方式轉成階層式,你會看到一個新的資料夾名為Debug-Universal(如果你建構的是Release版本,則會看到Release-Universal),該資料夾內包含了Universal版本的library,如下圖:
通常你會發現header檔案和static library這兩個檔案同時存在,除非他同時連接模擬器與實體裝置。
這些學習是為了能讓妳能建立你自己的universal static library!
總而言之,static library庫非常像是一個應用程式。你可以有1到多個類別(class),建構會得到header file和.a檔案與程式碼。這個.a檔是static library,它可以被不同的應用程式連接使用。
使用Static Library在你的應用程式
在你的應用程式使用ImageFilters類別與你引入header file並沒有什麼不同!問題在於Xcode不知道header或library檔案的位置。
有兩種方法將static library引入你的專案中:
- 方法1:依據header和library binary檔案(.a)的路徑
- 方法2:引入library檔案到專案中,如同一個子專案。
兩種方法選擇其中一種,這取決於你個人偏好以及你是否擁有static library的原始碼。
這兩種方法在本教程終會單獨說明。你可以試試其中一種方法,最好的情況是兩種方法都做過。在開始前,你需要先到Core Image教程下載一個zip壓縮檔,這個專案使用新的ImageFilters類別。
由於本教程主要目的是教你如何使用static library,該版本的應用程式包含了所有原始碼。這樣你僅需專注於library的配置。
方法1:Header和Library Binary檔案
在這個教程你需要下載這個專案。將這個zip檔案解壓縮,你可以看到這個專案資料夾的結構如下:
為了方便,這個專案已經將universal library .a檔案和 header file複製到該專案,但是該專案並沒有設定使用。這將由你來完成。
Note:Unix慣例會包含一個include資料夾放header file以及一個lib資料夾放library檔案(.a)。這些資料夾只是一個慣例,並沒有強制性。你不需要遵從這樣的結構或是複製這些檔案到你的專案資料夾。在你的應用程式,你可以有一個header和library檔案,而檔案的位置任你選擇,只要在Xocde專案設定指定到正確的位置即可。
開啟專案,試著建構及執行你的應用程式。你會看到這樣的錯誤訊息:
不出所料,應用程式找不到header檔案。為了解決這問題,你需要加入一個新的Header Search Path到專案內,這個是用來指向到header資料夾的路徑。使用static library通常第一步驟就是設定Header Search Path。
如下圖所示,(1)在專案導覽列,點選專案。(2)點選CoreImageFun target。(3)選擇Build Settings,接著在下方列表可以看到Header Search Path。(4)你可以在搜尋欄位輸入”header search”篩選出Header Search Path,如果你找不到的話。
雙擊Header Search Path,接著會跳出一個視窗。點擊 + 按鈕,並且輸入下列的文字:
$SOURCE_ROOT/include
跳出的新視窗如下圖:
$SOURCE_ROOT
是Xcode的環境變數,意思是指向專案的根目錄。Xcode將會將實際的專案路徑取代掉這個環境變數,這意味著專案的路徑為因為你放置到不同的地方會跟著改變。
點擊視窗的外圍,接著可以看到Xcode自動將路徑轉成正確的路徑,如下圖:
建構及執行你的應用程式,看看會如何。阿…看來有些連結的錯誤訊息出現:
看起來不太好,但是給你了一些資訊。如果你仔細看的話,你會看到編譯錯誤和以被替換的連結錯誤。這表示Xcode找到header檔案,並使用它編譯應用程式,但是在連結的階段,無法找到ImageFilter class中的程式碼。為什麼?
這很簡單 一 你還沒告訴Xcode在哪裡可以找到library檔案,而library檔案包含了class的實作。(你看,這不是很嚴重的問題。)
如下圖,(1)回到建構設定(build setting)。(2)選到CoreImageFun target。(3)選擇Build Phases分頁。(4)並展開Link Binary With Libraries。(5)點擊+ 按鈕。
接著會顯示下圖的畫面,點擊Add Other…,接著選到專案根目錄中lib子目錄下的libImageFilters.a檔案:
如下圖,當你完成時,應該與下圖一樣:
最後一步是加入-ObjC連結標誌。這個連結會試著有效率的引入需要的程式碼,有時可以排除static library程式碼。有了這個標誌,所有Objective-C class和library中的類別都可以正確加載。你可以在Apple技術問答學到更多Technical Q&A QA1490。
點選Build Setting分頁,接著可以看到Other linker Flags設定,如下圖:
在彈出的視窗,點擊 + 按鈕,並輸入-ObjC,如下圖:
最後建構並執行你的應用程式;你不會再得到錯誤訊息,然後應用程式就可以開啟了,如下圖:
拖拉slider改變濾鏡的效果,或者點擊GrayScale按鈕。執行圖片的濾鏡效果不是透過應用程式的程式碼,而是來自static library。
恭喜 一 你建構了你第一個使用static library的應用程式!你可以看到在這個方法使用了很多第三方的library,例如AdMob,TestFlight以及其他商業library,這些都不提供原始的程式碼。
方法2:子專案
在這個教程你需要下載這個專案。將這個zip檔案解壓縮。你可以看到這專案的資料夾結構,如下圖:
如果你看過第一個方法,你會發現這個專案變得不一樣。這個專案沒有任何header和static library檔案 一 這就是為什麼不用這些檔案。反而你會將你一開始建立的ImageFilters library專案加到此專案。
在開始之前先建構執行你的應用程式。你會看到一些錯誤訊息,如下圖:
如果你看過第一個方法,這時你可以知道該如何修正這個問題。同樣的專案,在ViewController類別使用ImageFilters類別,但你仍然要告訴Xcode header檔案的位置。Xcode試圖尋找ImageFilters.h檔案,但失敗了。
將ImageFilters library專案加入成為一個子專案,將library專案檔案拖拉到專案目錄結構。如果已經使用Xcode開啟,Xcode並不會正確的加入為子專案。所以在開始本教程前,請確認ImageFilters library專案已經關閉。
在Finder找到名為ImageFilters.xcodeproj的專案檔案。拖拉這個檔案到專案導覽列的CoreImageFun專案,如下圖:
完成之後,你的專案導覽列會變得如下圖:
現在Xcode知道library子專案,你可以加入library到專案中。在編譯應用程式前,Xcode會確保library是最新的版本。
(1)點選專案根目錄,(2)選擇CoreImageFun target。(3)點選Build Phases分頁,(4)展開Target Dependencies,如下圖:
點選 + 按鈕,加入一個新的dependency。如下圖,請確認選到ImageFilters target(並非UniversalLib)。
完成之後,得到的結果如下圖:
最後一個步驟是設定專案與static library的連接。展開Link Binary With Library,點選 + 按鈕,如下圖:
選擇libImageFilters.a並點選Add:
連接Link Binaray with Libraries後的結果如下圖:
最後一個步驟如同方法1,加入-ObjC連接。點選**Build Settings**分頁,看到**Other linker Flags**設定,如下圖:
在彈跳視窗點選 + 按鈕,並輸入-ObjC,如下圖:
建構及執行你的應用程式,不應該再有任何錯誤出現,而且應用程式開始運作:
拖拉slider或按GrayScale按鈕看看圖片的的變化效果。現在濾鏡的邏輯已經完全融入你的library。
如果你已經看過方法1的加入library方法(使用header和library檔案),你會發現與第二個方法有些不同。在第二個方法,你並不需要加入header檔案的路徑到專案設定。另一個不同的是你不需要使用Universal library。
為什麼有差別?當加入library為一個子專案,Xcode會幫你顧及所有事情。加入子專案後,Xcode知道header和binary的檔案位置,以及根據你的應用程式設定使用適當架構的library。這是相當方便的。
如果你使用自己的library,將它加入為子專案,你原有的專案就可以與這個子專案做連結。專案的整合如同專案互相依賴,你可以任意做你想做的事情,再也不用擔心其他問題。 Win-win!
- 建立 iOS 靜態函式庫
- iOS Socket建立链接
- IOS TabBarController上建立Item
- [iOS] 建立与使用Framework
- MOE建立ios调试环境
- ios代码建立跟控制器
- 为 iOS 建立 Travis CI
- 为 iOS 建立 Travis CI
- iOS 建立高级漂亮的button
- dw6 建立 ios sencha touch 开发环境
- 从零开始建立基于Beeframework的iOS工程
- ios--计算机视觉OpenCV--建立框架
- OpenGLES demo - 1. 建立IOS工程
- ios如何建立和调用静态库
- iOS-使用Objective-C建立UUID
- iOS 通过GCDAsyncSocket建立tcp链接
- 建立一个iOS jabber客户端:界面设置
- iOS---修改Xcode7建立的工程名称
- Machine Learning—k-nearest neighbor classification(k近邻分类)
- 【超实用的代码段】为什么比百度搜索Bigger更高!!
- 经济学家金岩石承认出轨 曾称京房价低于30万是耻辱
- 找工作笔试面试那些事儿(11)---数据库知识总结(2)范式
- ORACLE10G在Centos6.4上安装
- 建立 iOS 靜態函式庫
- 不平凡之路
- SQL语句 where,group by,having,order by执行顺序
- Linux内核 Documentation下的00-INDEX文档翻译
- android YUV转RGB
- 猴子分桃问题的解决方法
- HDU 5001 Walk 概率DP BFS 矩阵递推 暴力
- centos 6 下编译gcc4.8.3
- 一个回射服务器程序,采用reactor模型和epoll多路复用