iOS插件化

前言

framework是Cocoa/Cocoa Touch程序中使用的一種資源打包方式,可以將將代碼文件、頭文件、資源文件、說明文檔等集中在一起,方便開發者使用,作為一名Cocoa/Cocoa Touch程序員每天都要跟各種各樣的Framework打交道。

Cocoa/Cocoa Touch開發框架本身提供了大量的Framework,比如Foundation.framework/UIKit.framework/AppKit.framework等。需要注意的是,這些framework無一例外都是動態庫。

但殘忍的是,Cocoa Touch上并不允許我們使用自己創建的framework。不過由于framework是一種優秀的資源打包方式,擁有無窮智慧的程序員們便想出了以framework的形式打包靜態庫的招數,因此我們平時看到的第三方發布的framework無一例外都是靜態庫,真正的動態庫是上不了AppStore的。

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

iOS上動態庫可以做什么?

和靜態庫在編譯時和app代碼鏈接并打進同一個二進制包中不同,動態庫可以在運行時手動加載,這樣就可以做很多事情,比如:

  • 應用插件化

目前很多應用功能越做越多,軟件顯得越來越臃腫。因此插件化就成了很多軟件發展的必經之路,比如支付寶這種平臺級別的軟件:

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

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

  • 軟件版本實時模塊升級

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

如果軟件中的某個功能點出現了嚴重的bug,或者想在其中新增功能,你的這個功能點又是通過動態庫實現的,這時候你只需要在適當的時候從服務器上將新版本的動態庫文件下載到本地,然后在用戶重啟應用的時候即可實現新功能的展現。

  • 共享可執行文件

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

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

PS: 上述關于動態庫在iOS平臺的使用,在技術上都是可行的,但本人并沒有真正嘗試過做出一個上線AppStore的應用,因此并不保證按照上述方式使用動態庫一定能通過蘋果審核!

  • 創建動態庫

這邊是PiaoJinDylib,創建你測試類PiaoJin

頭文件部分

 #import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface PiaoJin : NSObject

- (void)love;

@end

NS_ASSUME_NONNULL_END

實現部分

#import "PiaoJin.h"

#import <UIKit/UIKit.h>

@implementation PiaoJin

- (void)love {
NSLog(@"love you more than I can say!");

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"love you more than I can say by 飄金!" message:**nil** delegate:**nil** cancelButtonTitle:**nil** otherButtonTitles:@"確定", **nil**];

 [alertView show];

}

@end

在PiaoJinDylib中引入

#import <UIKit/UIKit.h>

//! Project version number for PiaoJinDylib.

FOUNDATION_EXPORT double PiaoJinDylibVersionNumber;

//! Project version string for PiaoJinDylib.

FOUNDATION_EXPORT const unsigned  char PiaoJinDylibVersionString[];

*// In this header, you should import all the public headers of your framework using statements like #import <PiaoJinDylib/PublicHeader.h>*

#import "PiaoJin.h"

設置開放的頭文件

一個庫里面可以后很多的代碼,但是我們需要設置能夠提供給外界使用的接口,可以通過Target—>Build Phases—>Headers來設置,如下圖所示:

我們只需將希望開放的頭文件放到Public列表中即可,比如我開放了PiaoJinDylib.h和PiaoJin.h兩個頭文件,在生成的framework的Header目錄下就可以看到這兩個頭文件.一切完成,Run以后就能成功生成framework文件了。

前面只是我們只是創建了一個動態庫文件,但是和靜態庫類似,該動態庫并同時不支持真機和模擬器,可以通過以下步驟創建通用動態庫:

創建Aggregate Target(PiaoJinDylib工程下)

給Aggregate的Target的命名是CommonDylib。

設置Target Dependencies

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

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

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

添加Run Script

按以下路徑為CommonDylib添加Run Script:

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

添加的腳本為:

if  [ "${ACTION}" = "build" ]

then

INSTALL_DIR=${SRCROOT}/Products/${PROJECT_NAME}.framework

DEVICE_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework

SIMULATOR_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework

if  [ -d "${INSTALL_DIR}" ]

then

rm -rf "${INSTALL_DIR}"

fi

mkdir -p "${INSTALL_DIR}"

cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"

#ditto "${DEVICE_DIR}/Headers" "${INSTALL_DIR}/Headers"*

lipo -create "${DEVICE_DIR}/${PROJECT_NAME}" "${SIMULATOR_DIR}/${PROJECT_NAME}" -output "${INSTALL_DIR}/${PROJECT_NAME}"

open "${DEVICE_DIR}"

open "${SRCROOT}/Products"

fi

添加以后的效果:

腳本的主要功能是:

1.分別編譯生成真機和模擬器使用的framework;

2.使用lipo命令將其合并成一個通用framework;

3.最后將生成的通用framework放置在工程根目錄下新建的Products目錄下。

如果一切順利,對CommonDylib target執行run操作以后就能生成一個如圖所示的通用framework文件了:

使用動態庫

實際過程中動態庫是需要從服務器下載并且保存到app的沙盒中的,這邊直接模擬已經下載好了動態庫并且保存到沙盒中:

image

使用動態庫

  • 使用NSBundle加載動態庫
/**

 使用NSBundle加載動態庫

 */
- (IBAction)loadFrameworkForNSBundle:(UIButton *)sender {

//從服務器去下載并且存入Documents下(只要知道存哪里即可),事先要知道framework名字,然后去加載

NSString *destinationPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, **YES**) lastObject];

NSString *frameworkPath = [destinationPath stringByAppendingPathComponent:@"PiaoJinDylib.framework"];

NSError *err = **nil**;

NSBundle *bundle = [NSBundle bundleWithPath:frameworkPath];

NSString *str = @"加載動態庫失敗!";

if ([bundle loadAndReturnError:&err]) {

  self.bundle = bundle;

  NSLog(@"bundle load framework success.");

  str = @"加載動態庫成功!";

} else {

  NSLog(@"bundle load framework err:%@",err);
}

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:str message:**nil**delegate:**nil** cancelButtonTitle:**nil** otherButtonTitles:@"確定", **nil**];

[alertView show];

}
  • 使用dlopen加載動態庫

以PiaoJinDylib.framework為例,動態庫中真正的可執行代碼為PiaoJinDylib.framework/PiaoJinDylib文件,因此使用dlopen時指定加載動態庫的路徑為PiaoJinDylib.framework/PiaoJinDylib。

/**

 使用dlopen加載動態庫

 */

- (IBAction)loadFrameworkForDlopen:(UIButton *)sender {

//從服務器去下載并且存入Documents下(只要知道存哪里即可),事先要知道framework名字,然后去加載(注意需要加上PiaoJinDylib)*

NSString *destinationPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, **YES**) lastObject];

NSString *frameworkPath = [destinationPath stringByAppendingPathComponent:@"PiaoJinDylib.framework/PiaoJinDylib"];

libHandle = NULL;

  libHandle = dlopen([frameworkPath cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW);

NSString *str = @"加載動態庫失敗!";

if(libHandle == NULL) {

char *error = dlerror();

NSLog(@"dlopen error: %s", error);

} else {

NSLog(@"dlopen load framework success.");

str = @"加載動態庫成功!";

}

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:str message:**nil**delegate:**nil** cancelButtonTitle:**nil** otherButtonTitles:@"確定", **nil**];

[alertView show];

}
  • 調用動態庫中的方法
/**

 利用運行時調用動態庫方法

 */
- (IBAction)callFrameworkMethod:(UIButton *)sender {

Class piaoJinClass = NSClassFromString(@"PiaoJin");

if (piaoJinClass) {

//事先要知道有什么方法在這個FrameWork中*

id object = [[piaoJinClass alloc] init];

//由于沒有引入相關頭文件故通過performSelector調用*

[object performSelector:**@selector**(love)];

} else {

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"調用方法失敗!"message:**nil** delegate:**nil** cancelButtonTitle:**nil** otherButtonTitles:@"確定", **nil**];

[alertView show];
}
}
  • 監測動態庫的加載和移除

我們可以通過下述方式,為動態庫的加載和移除添加監聽回調:

+ (void)load 
{ 
  _dyld_register_func_for_add_image(&image_added); 
  _dyld_register_func_for_remove_image(&image_removed); 
} 

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

后記

這邊只是一件最簡單的例子,實際項目中肯定需要與動態庫所代表的模塊進行交互,數據的共享等等,這些都是需要去根據實際項目場景再去設計解決的!

如果是真機調試可以通過運行需打開iTunes導入到PiaoJinFrameWorkDemo應用中。

Demo 已經上傳GitHub,有需要的碼友可以去看看

參考文檔:

WWDC2014之iOS使用動態庫

更多文章

CocoaPods開源庫的搭建
CocoaPods搭建私有庫
CocoaPods搭建私有庫遇到問題
CocoaPods私有庫的升級維護
SKStoreReviewController之程序內評價
App應用程序圖標的動態更換
開源框架 MGJRouter_Swift
iOS的MVP設計模式
iOS插件化
iOS FMDB的使用
Swift之ReactiveSwift
OC之ReactiveCocoa
OC之ReactiveCocoa進階
iOS 性能考慮

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375

推薦閱讀更多精彩內容

  • framework是Cocoa/Cocoa Touch程序中使用的一種資源打包方式,可以將將代碼文件、頭文件、資源...
    飄金閱讀 8,713評論 14 15
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,155評論 4 61
  • 是因為農村孩子多嗎 為啥總能遇上農村孩子呢 我還自作多情人家對我挺感興趣 我喜歡腫么樣的男生呢 the best ...
    角落蜷縮閱讀 151評論 0 0
  • 1918年,國內一些小地方的學堂經費不足,但每年仍花六十塊錢去請中學堂學生兼職教英文唱歌,然后花二十塊錢買手風琴。...
    螢火蟲少女閱讀 343評論 0 1
  • 韋小寶就是金庸先生《鹿鼎記》中主人公。他是一名教育專家。 為什么這么說,有如下證據。 證據一:韋小寶從小懂得很多市...
    徐鴻圖閱讀 243評論 0 1