初探JSPatch

前言

iOS平臺的有很多熱修復框架,原理都是差不多吊圾,都是利用 Runtime 進行屬性、方法修改赊琳。
JSPatch 是現(xiàn)今比較主流街夭、輕量級的熱修復框架。利用內(nèi)置的 JavaScript 引擎(JavaScriptCore)結(jié)合 JavaScript 在運行時進行對 Object-C 對象修改躏筏。

接入文檔

JSPatch 的官方接入文檔寫的很詳細板丽,不過也很簡潔。對于 Objective-C 項目已經(jīng)足夠使用了但是對于 Swift 項目的接入詳情還是略顯簡略趁尼。目前埃碱,由于 Apple 公司對熱修復的打壓以及等等其他原因,使得 JSPatch 分為JSPatch平臺版Github 的開源代碼版酥泞。

Github 的開源代碼版:

# Your Podfile
platform :ios, '6.0'
pod 'JSPatch'

JSPatch 平臺版:
JSPatch 平臺版只支持手動集成方式, 沒有放到CocoaPods專門管理砚殿。

  1. JSPatchPlatform.framework 拖入項目中,勾選 "Copy items if needed"芝囤,并確保 "Add to target" 勾選了相應的 target似炎。

  2. 添加依賴框架:TARGETS -> Build Phases -> Link Binary With Libraries -> + 添加 libz.dylibJavaScriptCore.framework辛萍。

  3. 生成和配置RSA密鑰。

openssl >
genrsa -out rsa_private_key.pem 1024
pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM –nocrypt
rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
  1. 啟動運行
#import <JSPatchPlatform/JSPatch.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   [JSPatch startWithAppKey:@"你的AppKey"];
   [JSPatch setupRSAPublicKey:@"你的公鑰"];
   [JSPatch sync];
   ...
}
@end

注意事項:
Swift 項目羡藐,由于 JSPatch 平臺版由于 JSPatchPlatform.framework 里的 "Header"文件定義了與熱修復類贩毕、方法相同的宏,導致 Swift 無法直接橋接仆嗦。

#define JSPatch Eb_tCode
#define startWithAppKey stwa_43
#define setupRSAPublicKey strs_3x
#define setupTestScriptFileName sttsc_3
#define updateConfigWithAppKey udcak
#define testScriptInBundle tests_sinbund
#define JPCallbackType jtspc_b
#define JPErrorCode DRkcos
#define setupCallback sefjtpsytecal

解決方法:
定義一個 Object-C 的橋接對象辉阶,進行橋接。

#import <JSPatchPlatform/JSPatch.h>

@interface Patch : NSObject
/**
開始配置熱修復
 */
+ (void)start;

/**
 同步補丁
 */
+ (void)sync;

@end

@implementation Patch

+ (void)start {
    [JSPatch startWithAppKey:appKey];
    [JSPatch setupRSAPublicKey:@"你的公鑰"];
}

+ (void)sync {
    [JSPatch sync];
}

@end

橋接頭文件導入 Patch.h瘩扼,之后就可以在Swift中調(diào)用:

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptionslaunchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        
        Patch.start() //配置熱修復
        Patch.sync()  //同步下載補丁谆甜,這個方法可放在其他地方調(diào)用
        return true
    }
}

編寫工具

JSPatch 編寫工具體驗上都不太好,一般編寫和調(diào)試的工具都是分開集绰。調(diào)試工具一般能調(diào)試JavaScript的瀏覽器即可规辱。編寫工具種類比較多,只要能友好的編寫 JavaScript 的就行倒慧。

編寫工具推薦

  • Sublime Text 輕量級文本編輯器
  • Atom 很多東西需要翻墻使用
  • AppCode 重量級的IDE適合當做Xcode使用

調(diào)試工具推薦

  • Safari瀏覽器
  • Google瀏覽器

基本使用

JSPatch 基本使用按摘,官方文檔也已經(jīng)有詳細說明∪伊拢可以說學習 JSPatch 的門檻比較低炫贤,官網(wǎng)提供的一些工具方便并提升了開發(fā)效率,不過有一點需要注意的是不要太依賴官方的工具(只支持常規(guī)的語法付秕,而且很容易出錯)兰珍,所以需要對腳本進行語法檢查。本文主要補充一些 Swift 項目的使用以及注意事項說明询吴。

Objective-C 項目

JSPatch 雖然已經(jīng)很方便對代碼進行熱修復掠河,但是對一些的支持并不是很好,比如:

Struct 支持部分系統(tǒng)結(jié)構(gòu)體猛计,其他的需要在項目中和腳本中寫
C 函數(shù) 使用 JPCFunction 擴展支持
Block 使用 JPBlock 擴展支持
GCD 使用 JPDispatch 擴展支持
指針 使用 JPMemory 擴展支持
常量唠摹、枚舉、宏奉瘤、全局變量 無法支持

參照 官方文檔

Swift 項目

JSPatch 是利用 Objective-CRuntime 進行改寫勾拉、修改的;而 Swift 是利用 C++ 的那一套靜態(tài)機制盗温,編譯的時候已經(jīng)決定了不能修改藕赞,所以 Swift 項目是不支持熱修復的。為了讓 Swift 項目也能支持熱修復卖局,所以需要把 Swift 用到的類 進行橋接Objective-C 對應的對象斧蜕,這樣就能實現(xiàn)熱修復了。

官方文檔說明:

1.  只支持調(diào)用繼承自 NSObject 的 Swift 類
2.  繼承自 NSObject 的 Swift 類砚偶,其繼承自父類的方法和屬性可以在 JS 調(diào)用批销,其他自定義方法和屬性同樣需要加 @objc 和 dynamic 關(guān)鍵字才行洒闸。
3.  若方法的參數(shù)/屬性類型為 Swift 特有(如 Character / Tuple),則此方法和屬性無法通過 JS 調(diào)用风钻。
4.  Swift 項目在 JSPatch 新增類與 OC 無異顷蟀,可以正常使用酒请。

編寫 JavaScript 腳本

由于 Swift 不能直接支持熱修復骡技,所以只能把需要修改的 Swift 語言寫的類、屬性羞反、方法轉(zhuǎn)成對應的 Objective-C 代碼布朦。一般編寫腳本步驟:

1. 利用Xcode混編項目,在 Objective-C 文件中使用將要改變的 Swift 的代碼昼窗。目的為了查看轉(zhuǎn)成 Swift 對象轉(zhuǎn)成 OC 對象的方法名是趴。
2. Swift 類名 = 項目名.類名
3. 將替換的 OC 代碼 -> JS 腳本

對于第二點,這里說明一下澄惊,比如我有一個項目 SwiftDemo 需要改寫 TestProject 類下面的實例方法 testLog唆途,就需要如下寫:

defineClass("SwiftDemo.TestProject", {
            testLog: function() {
                console.log("打印 JS Log") //不能用 NSLog('xx'),應該用 console.log('xx')
            }
})

總結(jié):
編寫 JavaScript 腳本主要的轉(zhuǎn)換流程 Swift -> Objective-C -> JavaScript掸驱。
無法實現(xiàn)這條鏈路轉(zhuǎn)換的都無法進行熱修復肛搬。

編寫項目

為了能把 Swift 代碼轉(zhuǎn)換為 Objective-C 代碼,需要對 Swift 代碼進行一系列的修改毕贼。所以温赔,本文對 Swift 代碼定義一些規(guī)范:

  • Struct 結(jié)構(gòu)體不能使用,因為無法橋接成 OC 對象鬼癣。無法擁有動態(tài)屬性

  • 聲明 Class 需要繼承 NSObject陶贼,并且對屬性和方法進行動態(tài)說明,也就是需要添加相應的 @objcdynamic待秃,@objcMembers 關(guān)鍵字拜秧。

  1. 屬性修改值,只需要 @objc 即可
  2. JSPatch 調(diào)用的方法只具有 @objc 即可章郁,不需要 dynamic枉氮。
  3. JSPatch 重寫的方法需要具備 @objcdynamic 性質(zhì)。

修改的 Swift 代碼如下:

open class TestProject: NSObject {

    @objc var pname: String = "原始名字" //不需要 dynamic 特性
    @objc private var name: String = "原始名字" //不需要 dynamic 特性
    @objc static var same: String = "原始名字" //不需要 dynamic 特性
        
    public override init() {
        super.init()
    }
    
    @objc func start() {
        self.testLog()
    }
    
    @objc dynamic func testLog() {
        //重寫需要 @objc dynamic 性質(zhì)
        print("原始打印log")
    }
    
    @objc fileprivate func orgMethod() { //調(diào)用的方法不用 dynamic
        print("原始orgMethod")
        print("pname = \(self.pname)")
        print("name = \(self.name)")
        print("static same = \(DCTestProject.same)")
        print("執(zhí)行完成")
    }
    
}

@objcMembers
open class TestProject: NSObject {

    var pname: String = "原始名字" //不需要 dynamic 特性
    @objc private var name: String = "原始名字" //不需要 dynamic 特性
    static var same: String = "原始名字" //不需要 dynamic 特性
        
    public override init() {
        super.init()
    }
    
    func start() {
        self.testLog()
    }
    
    dynamic func testLog() {
        //重寫需要 @objc dynamic 性質(zhì)
        print("原始打印log")
    }
    
    @objc fileprivate func orgMethod() { 
        //調(diào)用的方法不用 dynamic, 但私有方法需要手動加 @objc
        print("原始orgMethod")
        print("pname = \(self.pname)")
        print("name = \(self.name)")
        print("static same = \(DCTestProject.same)")
        print("執(zhí)行完成")
    }
    
}

JSPatch 腳本如下:

defineClass("SwiftDemo.TestProject", {
            testLog: function() {
                console.log("打印 JS Log");
                self.setPname("打印 JS");
                self.setName("打印 JS");
                require('SwiftDemo.TestProject').setSame("打印 JS");
                self.orgMethod();
            }
})
  • Enum 枚舉盡量少用驱犹,需要一些特殊處理嘲恍,并且枚舉中不能有其他方法。即使橋接成OC枚舉雄驹,JavaScript沒辦法獲取佃牛。
@objc public enum NVActivityIndicatorType: Int {  
   case Blank  
   case BallPulse  
   case BallGridPulse  
   case BallClipRotate  
   case SquareSpin  
}  
  • Protocol 協(xié)議需要在相應的地方添加 @objc 關(guān)鍵字, 并且繼承 NSObjectProtocol 協(xié)議。
@objc protocol TestDelegate: NSObjectProtocol {
    @objc func TestClick(Str: String)
}
  • 元組類型不能使用医舆。

  • 需要在 JavaScript 調(diào)用或者修改的方法都必須具有動態(tài)屬性俘侠,而且方法所用到的參數(shù)以及返回的對象都必須具有動態(tài)屬性象缀。

  • 調(diào)用 C 函數(shù) 函數(shù)很麻煩需要做綁定操作,所以盡量少用爷速,而且不能保證所有的 C 函數(shù) 都能綁定調(diào)用央星。尤其是內(nèi)聯(lián)函數(shù)

  • 常量惫东、枚舉莉给、宏、全局變量不要使用廉沮,因為 JavaScript 沒辦法獲取颓遏。

  • 指針盡量不要使用,對于 SwiftJavaScript 語言來說滞时,指針使用麻煩叁幢,容易出錯。指針使用方法請看JPMemory使用文檔

  • 方法里的代碼盡量不能太多坪稽,盡量不要超過 30 行曼玩。對臃腫代碼,尤其是邏輯比較重要的代碼進行方法拆分窒百。

  • 重寫或者調(diào)用的方法的參數(shù)返回類型也必須需要能橋接到 Objective-C 代碼中黍判。

  • 項目中對于公用工具類最好具備動態(tài)屬性,而且如果是純 Swift 寫的就盡量中間封裝動態(tài)中間類贝咙。

注意事項

說明一下 Swift 4.0 之后的兩個修飾的關(guān)鍵字 @objc@objcMembers 對比:

  • Swift 4.0 之后的 @objcdynamic 關(guān)鍵字功能分開样悟,也就是只添加 @objc 是不具有動態(tài)性的。
  • @objcMembers 會在庭猩、類擴展窟她、子類的所有非 private 的方法和屬性前添加 @objc 修飾,并且不會添加 dynamic 特性蔼水。

總結(jié)

熱修復只是用來線上緊急的 BugFix震糖,沒必要用來做其他功能開發(fā)不必要的操作。對于 Swift 項目趴腋,還是平常注意一下代碼編寫邏輯吊说,畢竟熱修復針對的是 Objective-C 項目。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末优炬,一起剝皮案震驚了整個濱河市颁井,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蠢护,老刑警劉巖雅宾,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異葵硕,居然都是意外死亡眉抬,警方通過查閱死者的電腦和手機贯吓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜀变,“玉大人悄谐,你說我怎么就攤上這事】獗保” “怎么了爬舰?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贤惯。 經(jīng)常有香客問我洼专,道長,這世上最難降的妖魔是什么孵构? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮烟很,結(jié)果婚禮上颈墅,老公的妹妹穿的比我還像新娘。我一直安慰自己雾袱,他們只是感情好恤筛,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芹橡,像睡著了一般毒坛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上林说,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天煎殷,我揣著相機與錄音,去河邊找鬼腿箩。 笑死豪直,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的珠移。 我是一名探鬼主播弓乙,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钧惧!你這毒婦竟也來了暇韧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤浓瞪,失蹤者是張志新(化名)和其女友劉穎懈玻,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體追逮,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡酪刀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年粹舵,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骂倘。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡眼滤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出历涝,到底是詐尸還是另有隱情诅需,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布荧库,位于F島的核電站堰塌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏分衫。R本人自食惡果不足惜场刑,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蚪战。 院中可真熱鬧牵现,春花似錦、人聲如沸邀桑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽壁畸。三九已至贼急,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捏萍,已是汗流浹背太抓。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留照弥,地道東北人腻异。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像这揣,于是被迫代替她去往敵國和親悔常。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容