前言
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專門管理砚殿。
將
JSPatchPlatform.framework
拖入項目中,勾選 "Copy items if needed"芝囤,并確保 "Add to target" 勾選了相應的 target似炎。添加依賴框架:TARGETS -> Build Phases -> Link Binary With Libraries -> + 添加
libz.dylib
和JavaScriptCore.framework
辛萍。生成和配置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
- 啟動運行
#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-C 的 Runtime 進行改寫勾拉、修改的;而 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)說明,也就是需要添加相應的@objc
,dynamic
待秃,@objcMembers
關(guān)鍵字拜秧。
- 屬性修改值,只需要
@objc
即可- JSPatch 調(diào)用的方法只具有
@objc
即可章郁,不需要dynamic
枉氮。- JSPatch 重寫的方法需要具備
@objc
和dynamic
性質(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 沒辦法獲取颓遏。
指針盡量不要使用,對于 Swift 和 JavaScript 語言來說滞时,指針使用麻煩叁幢,容易出錯。指針使用方法請看JPMemory使用文檔
方法里的代碼盡量不能太多坪稽,盡量不要超過 30 行曼玩。對臃腫代碼,尤其是邏輯比較重要的代碼進行方法拆分窒百。
重寫或者調(diào)用的方法的參數(shù)和返回類型也必須需要能橋接到 Objective-C 代碼中黍判。
項目中對于公用工具類最好具備動態(tài)屬性,而且如果是純 Swift 寫的就盡量中間封裝動態(tài)中間類贝咙。
注意事項
說明一下 Swift 4.0 之后的兩個修飾的關(guān)鍵字 @objc
和 @objcMembers
對比:
-
Swift 4.0 之后的
@objc
和dynamic
關(guān)鍵字功能分開样悟,也就是只添加 @objc 是不具有動態(tài)性的。 - @objcMembers 會在類庭猩、類擴展窟她、子類的所有非 private 的方法和屬性前添加 @objc 修飾,并且不會添加 dynamic 特性蔼水。
總結(jié)
熱修復只是用來線上緊急的 BugFix震糖,沒必要用來做其他功能開發(fā)不必要的操作。對于 Swift 項目趴腋,還是平常注意一下代碼編寫邏輯吊说,畢竟熱修復針對的是 Objective-C 項目。