WWDC2014之iOS使用動態庫

http://blog.csdn.net/liangliang103377/article/details/40077917html

 

WWDC2014之iOS使用動態庫

JUN 12TH, 2014ios

蘋果的開放態度

WWDC2014上發佈的Xcode6 beta版有了很多更新,其中令我驚訝的一個是蘋果在iOS上開放了動態庫,在Xcode6 Beta版的更新文檔中是這樣描述的:git

Frameworks for iOS. iOS developers can now create dynamic frameworks. Frameworks are a collection of code and resources to encapsulate functionality that is valuable across multiple projects. Frameworks work perfectly with extensions, sharing logic that can be used by both the main application, and the bundled extensions.程序員

詳情見官方文檔New Features in Xcode 6 Betagithub

framework是Cocoa/Cocoa Touch程序中使用的一種資源打包方式,能夠將將代碼文件、頭文件、資源文件、說明文檔等集中在一塊兒,方便開發者使用,做爲一名Cocoa/Cocoa Touch程序員天天都要跟各類各樣的Framework打交道。Cocoa/Cocoa Touch開發框架自己提供了大量的Framework,好比Foundation.framework/UIKit.framework/AppKit.framework等。須要注意的是,這些framework無一例外都是動態庫。web

但殘忍的是,Cocoa Touch上並不容許咱們使用本身建立的framework。不過因爲framework是一種優秀的資源打包方式,擁有無窮智慧的程序員們便想出了以framework的形式打包靜態庫的招數,所以咱們平時看到的第三方發佈的framework無一例外都是靜態庫,真正的動態庫是上不了AppStore的。objective-c

WWDC2014給個人一個很大感觸是蘋果對iOS的開放態度:容許使用動態庫、容許第三方鍵盤、App Extension等等,這些在以前是想都不敢想的事。xcode

iOS上動態庫能夠作什麼

和靜態庫在編譯時和app代碼連接並打進同一個二進制包中不一樣,動態庫能夠在運行時手動加載,這樣就能夠作不少事情,好比:服務器

  • 應用插件化

目前不少應用功能越作越多,軟件顯得愈來愈臃腫。所以插件化就成了不少軟件發展的必經之路,好比支付寶這種平臺級別的軟件:網絡

首頁上密密麻麻的功能,並且還在增多,照這個趨勢發展下去,軟件包的大小將會不可想象。目前經常使用的解決方案是使用web頁面,但用戶體驗和Native界面是無法比的。

設想,若是每個功能點都是一個動態庫,在用戶想使用某個功能的時候讓其從網絡下載,而後手動加載動態庫,實現功能的的插件化,就不再用擔憂功能點的無限增多了,這該是件多麼美好的事!

  • 軟件版本實時模塊升級

還在忍受蘋果動輒一週,甚至更長的審覈週期嗎?有了動態庫媽媽就不再用擔憂你的軟件升級了!

若是軟件中的某個功能點出現了嚴重的bug,或者想在其中新增功能,你的這個功能點又是經過動態庫實現的,這時候你只須要在適當的時候從服務器上將新版本的動態庫文件下載到本地,而後在用戶重啓應用的時候便可實現新功能的展示。

  • 共享可執行文件

在其它大部分平臺上,動態庫均可以用於不一樣應用間共享,這就大大節省了內存。從目前來看,iOS仍然不容許進程間共享動態庫,即iOS上的動態庫只能是私有的,由於咱們仍然不能將動態庫文件放置在除了自身沙盒之外的其它任何地方。

不過iOS8上開放了App Extension功能,能夠爲一個應用建立插件,這樣主app和插件之間共享動態庫仍是可行的。

PS: 上述關於動態庫在iOS平臺的使用,在技術上都是可行的,但本人並無真正嘗試過作出一個上線AppStore的應用,所以並不保證按照上述方式使用動態庫必定能經過蘋果審覈!

2014-6-23修正:

@唐巧_boy提醒,sandbox會驗證動態庫的簽名,因此若是是動態從服務器更新的動態庫,應該是簽名不了的,所以應用插件化、軟件版本實時模塊升級估計很難實現,一切都只能等到iOS8正式版發佈之後再驗證了。感謝@唐巧_boy

建立動態庫

一、建立動態庫

  • 建立工程文件

在下圖所示界面可以找到Cocoa Touch動態庫的建立入口:

framework

跟隨指引一步步操做便可建立一個新的動態庫工程,個人工程名字叫Dylib,Xcode會同時建立一個和工程target同名的.h文件,好比個人就是Dylib.h。

  • 向工程中添加文件

接下來就能夠在工程中隨意添加文件了。我在其中新建了一個名爲Person的測試類,提供的接口以下:

1
2 3 4 5 
@interface Person : NSObject  - (void)run;  @end 

對應的實現部分:

1
2 3 4 5 6 7 8 9 10 11 
@implementation Person  - (void)run {  NSLog(@"let's run.");   UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"The Second Alert" message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:@"done", nil];  [alert show]; }  @end 
  • 設置開放的頭文件

一個庫裏面能夠後不少的代碼,可是咱們須要設置可以提供給外界使用的接口,能夠經過Target—>Build Phases—>Headers來設置,以下圖所示:

header

咱們只需將但願開放的頭文件放到Public列表中便可,好比我開放了Dylib.hPerson.h兩個頭文件,在生成的framework的Header目錄下就能夠看到這兩個頭文件,以下圖所示:

public_header

一切完成,Run之後就能成功生成framework文件了。

二、通用動態庫

通過第一步咱們只是建立了一個動態庫文件,可是和靜態庫相似,該動態庫並同時不支持真機和模擬器,能夠經過如下步驟建立通用動態庫:

  • 建立Aggregate Target

按下圖所示,在動態庫工程中添加一個類型爲Aggregate的target:

aggregate

按提示一步步操做便可,我給Aggregate的Target的命名是CommonDylib

  • 設置Target Dependencies

按如下路徑設置CommonDylib對應的Target Dependencies:

1
TARGETS-->CommonDylib-->Build Phases-->Target Dependencies 

將真正的動態庫Dylib Target添加到其中。

  • 添加Run Script

按如下路徑爲CommonDylib添加Run Script:

1
TARGETS-->CommonDylib-->Build Phases-->Run Script 

添加的腳本爲:

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 
# Sets the target folders and the final framework product. FMK_NAME=${PROJECT_NAME}  # Install dir will be the final output to the framework. # The following line create it in the root folder of the current project. INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework  # Working dir will be deleted after the framework creation. WRK_DIR=build DEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework SIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework  # -configuration ${CONFIGURATION} # Clean and Building both architectures. xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphoneos clean build xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphonesimulator clean build  # Cleaning the oldest. if [ -d "${INSTALL_DIR}" ] then rm -rf "${INSTALL_DIR}" fi  mkdir -p "${INSTALL_DIR}"  cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"  # Uses the Lipo Tool to merge both binary files (i386 + armv6/armv7) into one Universal final product. lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/${FMK_NAME}"  rm -r "${WRK_DIR}" 

添加之後的效果如圖所示:

commonlib_setting

該腳本是我根據一篇文章中介紹的腳本改寫的,感謝原文做者

腳本的主要功能是:

1.分別編譯生成真機和模擬器使用的framework; 2.使用lipo命令將其合併成一個通用framework; 3.最後將生成的通用framework放置在工程根目錄下新建的Products目錄下。

若是一切順利,對CommonDylib target執行run操做之後就能生成一個如圖所示的通用framework文件了:

products

使用動態庫

添加動態庫到工程文件

通過以上步驟的努力,生成了最終須要的framework文件,爲了演示動態庫的使用,新建了一個名爲FrameworkDemo的工程。經過如下方式將剛生成的framework添加到工程中:

1
Targets-->Build Phases-->Link Binary With Libraries 

同時設置將framework做爲資源文件拷貝到Bundle中:

1
Targets-->Build Phases-->Copy Bundle Resources 

如圖所示:

framework_demo_setting

僅僅這樣作是不夠的,還須要爲動態庫添加連接依賴。

自動連接動態庫

添加完動態庫後,若是但願動態庫在軟件啓動時自動連接,能夠經過如下方式設置動態庫依賴路徑:

1
Targets-->Build Setting-->Linking-->Runpath Search Paths 

因爲向工程中添加動態庫時,將動態庫設置了Copy Bundle Resources,所以就能夠將Runpath Search Paths路徑依賴設置爲main bundle,即沙盒中的FrameworkDemo.app目錄,向Runpath Search Paths中添加下述內容:

1
@executable_path/ 

如圖所示:

run_search_path

其中的@executable_path/表示可執行文件所在路徑,即沙盒中的.app目錄,注意不要漏掉最後的/

若是你將動態庫放到了沙盒中的其餘目錄,只須要添加對應路徑的依賴就能夠了。

須要的時候連接動態庫

動態庫的另外一個重要特性就是即插即用性,咱們能夠選擇在須要的時候再加載動態庫。

  • 更改設置

若是不但願在軟件一啓動就加載動態庫,須要將

1
Targets-->Build Phases-->Link Binary With Libraries 

Dylib.framework對應的Status由默認的Required改爲Optional;或者更乾脆的,將Dylib.frameworkLink Binary With Libraries列表中刪除便可。

  • 使用dlopen加載動態庫

Dylib.framework爲例,動態庫中真正的可執行代碼爲Dylib.framework/Dylib文件,所以使用dlopen時若是僅僅指定加載動態庫的路徑爲Dylib.framework是無法成功加載的。

示例代碼以下:

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
- (IBAction)onDlopenLoadAtPathAction1:(id)sender {  NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/Dylib.framework/Dylib",NSHomeDirectory()];  [self dlopenLoadDylibWithPath:documentsPath]; }  - (void)dlopenLoadDylibWithPath:(NSString *)path {  libHandle = NULL;  libHandle = dlopen([path cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW);  if (libHandle == NULL) {  char *error = dlerror();  NSLog(@"dlopen error: %s", error);  } else {  NSLog(@"dlopen load framework success.");  } } 

以dlopen方式使用動態庫不知道是否能經過蘋果審覈。

  • 使用NSBundle加載動態庫

也可使用NSBundle來加載動態庫,實現代碼以下:

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
- (IBAction)onBundleLoadAtPathAction1:(id)sender {  NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/Dylib.framework",NSHomeDirectory()];  [self bundleLoadDylibWithPath:documentsPath]; }  - (void)bundleLoadDylibWithPath:(NSString *)path {  _libPath = path;  NSError *err = nil;  NSBundle *bundle = [NSBundle bundleWithPath:path];  if ([bundle loadAndReturnError:&err]) {  NSLog(@"bundle load framework success.");  } else {  NSLog(@"bundle load framework err:%@",err);  } } 

使用動態庫中代碼

經過上述任一一種方式加載的動態庫後,就可使用動態庫中的代碼文件了,以Dylib.framework中的Person類的使用爲例:

1
2 3 4 5 6 7 8 
- (IBAction)onTriggerButtonAction:(id)sender {  Class rootClass = NSClassFromString(@"Person");  if (rootClass) {  id object = [[rootClass alloc] init];  [(Person *)object run];  } } 

注意,若是直接經過下屬方式初始化Person類是不成功的:

1
2 3 4 5 6 7 
- (IBAction)onTriggerButtonAction:(id)sender {  Person *object = [[Person alloc] init];  if (object) {  [object run];  } } 

監測動態庫的加載和移除

咱們能夠經過下述方式,爲動態庫的加載和移除添加監聽回調:

1
2 3 4 5 
+ (void)load {  _dyld_register_func_for_add_image(&image_added);  _dyld_register_func_for_remove_image(&image_removed); } 

github上有一個完整的示例代碼

從這裏看出,原來就算空白工程軟件啓動的時候也會加載多達一百二十多個動態庫,若是這些都是靜態庫,那該有多可怕!!

Demo

本文使用的例子已經上傳到github上,須要的朋友請自取。

另外,本文對某些東西可能有理解錯誤的地方,還請指出。

相關文章
相關標籤/搜索