JSPatch實現原理詳解

本文轉載至 http://blog.cnbang.net/tech/2808/

JSPatch以小巧的體積作到了讓JS調用/替換任意OC方法,讓iOS APP具有熱更新的能力,在實現 JSPatch 過程當中遇到過不少困難也踩過不少坑,有些仍是挺值得分享的。本篇文章從基礎原理、方法調用和方法替換三塊內容介紹整個 JSPatch 的實現原理,並把實現過程當中的想法和碰到的坑也儘量記錄下來。git

基礎原理

能作到經過JS調用和改寫OC方法最根本的緣由是 Objective-C 是動態語言,OC上全部方法的調用/類的生成都經過 Objective-C Runtime 在運行時進行,咱們能夠經過類名/方法名反射獲得相應的類和方法:github

1
2
3
4
Class class = NSClassFromString ( "UIViewController" );
id viewController = [[ class alloc] init];
SEL selector = NSSelectorFromString ( "viewDidLoad" );
[viewController performSelector:selector];

也能夠替換某個類的方法爲新的實現:數組

1
2
static void newViewDidLoad( id slf, SEL sel) {}
class_replaceMethod( class , selector, newViewDidLoad, @"" );

還能夠新註冊一個類,爲類添加方法:ruby

1
2
3
Class cls = objc_allocateClassPair(superCls, "JPObject" , 0);
objc_registerClassPair(cls);
class_addMethod(cls, selector, implement, typedesc);

對於 Objective-C 對象模型和動態消息發送的原理已有不少文章闡述得很詳細,例如這篇,這裏就不詳細闡述了。理論上你能夠在運行時經過類名/方法名調用到任何OC方法,替換任何類的實現以及新增任意類。因此 JSPatch 的原理就是:JS傳遞字符串給OC,OC經過 Runtime 接口調用和替換OC方法。這是最基礎的原理,實際實現過程還有不少怪要打,接下來看看具體是怎樣實現的。
微信

方法調用

1
2
3
4
require( 'UIView' )
var view = UIView.alloc().init()
view.setBackgroundColor(require( 'UIColor' ).grayColor())
view.setAlpha(0.5)

引入JSPatch後,能夠經過以上JS代碼建立了一個 UIView 實例,並設置背景顏色和透明度,涵蓋了require引入類,JS調用接口,消息傳遞,對象持有和轉換,參數轉換這五個方面,接下來逐一看看具體實現。微信公衆平臺

1.require

調用 require(‘UIView’) 後,就能夠直接使用 UIView 這個變量去調用相應的類方法了,require 作的事很簡單,就是在JS全局做用域上建立一個同名變量,變量指向一個對象,對象屬性__isCls代表這是一個 Class,__clsName保存類名,在調用方法時會用到這兩個屬性。函數

1
2
3
4
5
6
7
8
9
var _require = function (clsName) {
   if (!global[clsName]) {
     global[clsName] = {
       __isCls: 1,
       __clsName: clsName
     }
   }
   return global[clsName]
}

因此調用require(‘UIView’)後,就在全局做用域生成了 UIView 這個變量,指向一個這樣一個對象:post

1
2
3
4
{
   __isCls: 1,
   __clsName: "UIView"
}

2.JS接口

接下來看看 UIView.alloc() 是怎樣調用的。測試

舊實現

對於這個調用的實現,一開始個人想法是,根據JS特性,若要讓 UIView.alloc() 這句調用不出錯,惟一的方法就是給 UIView 這個對象添加 alloc 方法,否則是不可能調用成功的,JS對於調用沒定義的屬性/變量,只會立刻拋出異常,而不像OC/Lua/ruby那樣會有轉發機制。因此作了一個複雜的事,就是在require生成類對象時,把類名傳入OC,OC經過 Runtime 方法找出這個類全部的方法返回給JS,JS類對象爲每一個方法名都生成一個函數,函數內容就是拿着方法名去OC調用相應方法。生成的 UIView 對象大體是這樣的:優化

1
2
3
4
5
6
7
8
{
     __isCls: 1,
     __clsName: "UIView" ,
     alloc: function () {…},
     beginAnimations_context: function () {…},
     setAnimationsEnabled: function (){…},
     ...
}

實際上不只要遍歷當前類的全部方法,還要循環找父類的方法直到頂層,整個繼承鏈上的全部方法都要加到JS對象上,一個類就有幾百個方法,這樣把方法所有加到JS對象上,碰到了挺嚴重的問題,引入幾個類就內存暴漲,沒法使用。後來爲了優化內存問題還在JS搞了繼承關係,不把繼承鏈上全部方法都添加到一個JS對象,避免像基類 NSObject 的幾百個方法反覆添加在每一個JS對象上,每一個方法只存在一份,JS對象複製了OC對象的繼承關係,找方法時沿着繼承鏈往上找,結果內存消耗是小了一些,但仍是大到難以接受。

新實現

當時繼續苦苦尋找解決方案,若按JS語法,這是惟一的方法,但若不按JS語法呢?忽然腦洞開了下,CoffieScript/JSX均可以用JS實現一個解釋器實現本身的語法,我也能夠經過相似的方式作到,再進一步想到其實我想要的效果很簡單,就是調用一個不存在方法時,能轉發到一個指定函數去執行,就能解決一切問題了,這其實能夠用簡單的字符串替換,把JS腳本里的方法調用都替換掉。最後的解決方案是,在OC執行JS腳本前,經過正則把全部方法調用都改爲調用 __c() 函數,再執行這個JS腳本,作到了相似OC/Lua/Ruby等的消息轉發機制:

1
2
3
UIView.alloc().init()
->
UIView.__c( 'alloc' )().__c( 'init' )()

給JS對象基類 Object 的 prototype 加上 __c 成員,這樣全部對象均可以調用到 __c,根據當前對象類型判斷進行不一樣操做:

1
2
3
4
5
6
7
8
Object.prototype.__c = function (methodName) {
   if (! this .__obj && ! this .__clsName) return this [methodName].bind( this );
   var self = this
   return function (){
     var args = Array.prototype.slice.call(arguments)
     return _methodFunc(self.__obj, self.__clsName, methodName, args, self.__isSuper)
   }
}

_methodFunc() 就是把相關信息傳給OC,OC用 Runtime 接口調用相應方法,返回結果值,這個調用就結束了。

這樣作不用去OC遍歷對象方法,不用在JS對象保存這些方法,內存消耗直降99%,這一步是作這個項目最爽的時候,用一個很是簡單的方法解決了嚴重的問題,替換以前又複雜效果又差的實現。

3.消息傳遞

解決了JS接口問題,接下來看看JS和OC是怎樣互傳消息的。這裏用到了 JavaScriptCore 的接口,OC端在啓動JSPatch引擎時會建立一個 JSContext 實例,JSContext 是JS代碼的執行環境,能夠給 JSContext 添加方法,JS就能夠直接調用這個方法:

1
2
3
4
5
JSContext *context = [[JSContext alloc] init];
context[ @"hello" ] = ^( NSString *msg) {
     NSLog ( @"hello %@" , msg);
};
[_context evaluateScript: @"hello('word')" ];     //output hello word

JS經過調用 JSContext 定義的方法把數據傳給OC,OC經過返回值傳會給JS。調用這種方法,它的參數/返回值 JavaScriptCore 都會自動轉換,OC裏的 NSArray, NSDictionary, NSString, NSNumber, NSBlock 會分別轉爲JS端的數組/對象/字符串/數字/函數類型。上述 _methodFunc() 方法就是這樣把要調用的類名和方法名傳遞給OC的。

4.對象持有/轉換

UIView.alloc() 經過上述消息傳遞後會到OC執行 [UIView alloc],並返回一個UIView實例對象給JS,這個OC實例對象在JS是怎樣表示的呢?怎樣能夠在JS拿到這個實例對象後能夠直接調用它的實例方法 (UIView.alloc().init())?

對於一個自定義id對象,JavaScriptCore 會把這個自定義對象的指針傳給JS,這個對象在JS沒法使用,但在回傳給OC時OC能夠找到這個對象。對於這個對象生命週期的管理,按個人理解若是JS有變量引用時,這個OC對象引用計數就加1 ,JS變量的引用釋放了就減1,若是OC上沒別的持有者,這個OC對象的生命週期就跟着JS走了,會在JS進行垃圾回收時釋放。

傳回給JS的變量是這個OC對象的指針,若是不通過任何處理,是沒法經過這個變量去調用實例方法的。因此在返回對象時,JSPatch 會對這個對象進行封裝。

首先,告訴JS這是一個OC對象:

1
2
3
4
5
static NSDictionary *toJSObj( id obj)
{
     if (!obj) return nil ;
     return @{ @"__isObj" : @( YES ), @"cls" : NSStringFromClass ([obj class ]), @"obj" : obj};
}

用__isObj表示這是一個OC對象,對象指針也一塊兒返回。接着在JS端會把這個對象轉爲一個 JSClass 實例:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
var JSClass
var _getJSClass = function (className) {
   if (!JSClass) {
     JSClass = function (obj, className, isSuper) {
         this .__obj = obj
         this .__isSuper = isSuper
         this .__clsName = className
     }
   }
   return JSClass
}
 
var _toJSObj = function (meta) {
   var JSClass = _getJSClass()
   return new JSClass(meta[ "obj" ], meta[ "cls" ])
}

JS端若是發現返回是一個OC對象,會傳入 _toJSObj(),生成一個 JSClass 實例,這個實例保存着OC對象指針,類名等。這個實例就是OC對象在 JSPatch 對應的JS對象,生命週期是同樣的。

回到咱們第二點說的 JS接口, 這個 JSClass 實例對象一樣有 __c 函數,調用這個對象的方法時,一樣走到 __c 函數, __c 函數會把JSClass實例對象裏的OC對象指針以及要調用的方法名和參數回傳給OC,這樣OC就能夠調用這個對象的實例方法了。

接着看看對象是怎樣回傳給OC的。上述例子中,view.setBackgroundColor(require(‘UIColor’).grayColor()),這裏生成了一個 UIColor 實例對象,並做爲參數回傳給OC。根據上面說的,這個 UIColor 實例在JS中的表示是一個 JSClass 實例,因此不能直接回傳給OC,這裏的參數實際上會在 __c 函數進行處理,會把對象的 .__obj 原指針回傳給OC。

最後一點,OC對象可能會存在於 NSDictionary / NSArray 等容器裏,因此須要遍歷容器挑出OC對象進行格式化,OC須要把對象都替換成JS認得的格式,JS要把對象轉成 JSClass 實例,JS實例回傳給OC時須要把實例轉爲OC對象指針。因此OC流出數據時都會通過 formatOCObj() 方法處理,JS從OC獲得數據時都會通過 _formatOCToJS() 處理,JS傳參數給OC時會通過 _formatJSToOC() 處理,圖示:

JSPatch1

5.類型轉換

JS把要調用的類名/方法名/對象傳給OC後,OC調用類/對象相應的方法是經過 NSInvocation 實現,要能順利調用到方法並取得返回值,要作兩件事:

1.取得要調用的OC方法各參數類型,把JS傳來的對象轉爲要求的類型進行調用。
2.根據返回值類型取出返回值,包裝爲對象傳回給JS。

例如開頭例子的 view.setAlpha(0.5), JS傳遞給OC的是一個 NSNumber,OC須要經過要調用OC方法的 NSMethodSignature 得知這裏參數要的是一個 float 類型值,因而把NSNumber轉爲float值再做爲參數進行OC方法調用。這裏主要處理了 int/float/bool 等數值類型,並對 CGRect/CGRange 等類型進行了特殊轉換處理,剩下的就是實現細節了。

方法替換

JSPatch 能夠用 defineClass 接口任意替換一個類的方法,方法替換的實現過程也是頗爲曲折,一開始是用 va_list 的方式獲取參數,結果發現 arm64 下不可用,只能轉而用另外一種hack方式繞道實現。另外在給類新增方法、實現property、支持self/super關鍵字上也費了些功夫,下面逐個說明。

基礎原理

OC上,每一個類都是這樣一個結構體:

1
2
3
4
5
6
struct objc_class {
   struct objc_class * isa;
   const char *name;
   ….
   struct objc_method_list **methodLists; /*方法鏈表*/
};

其中 methodList 方法鏈表裏存儲的是Method類型:

1
2
3
4
5
6
typedef struct objc_method *Method;
typedef struct objc_ method {
   SEL method_name;
   char *method_types;
   IMP method_imp;
};

Method 保存了一個方法的所有信息,包括SEL方法名,type各參數和返回值類型,IMP該方法具體實現的函數指針。

經過 Selector 調用方法時,會從 methodList 鏈表裏找到對應Method進行調用,這個 methodList 上的的元素是能夠動態替換的,能夠把某個 Selector 對應的函數指針IMP替換成新的,也能夠拿到已有的某個 Selector 對應的函數指針IMP,讓另外一個 Selector 跟它對應,Runtime 提供了一些接口作這些事,以替換 UIViewController 的 -viewDidLoad: 方法爲例:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
static void viewDidLoadIMP ( id slf, SEL sel) {
    JSValue *jsFunction = …;
    [jsFunction callWithArguments: nil ];
}
 
Class cls = NSClassFromString ( @"UIViewController" );
SEL selector = @selector (viewDidLoad);
Method method = class_getInstanceMethod(cls, selector);
 
//得到viewDidLoad方法的函數指針
IMP imp = method_getImplementation(method)
 
//得到viewDidLoad方法的參數類型
char *typeDescription = ( char *)method_getTypeEncoding(method);
 
//新增一個ORIGViewDidLoad方法,指向原來的viewDidLoad實現
class_addMethod(cls, @selector (ORIGViewDidLoad), imp, typeDescription);
 
//把viewDidLoad IMP指向自定義新的實現
class_replaceMethod(cls, selector, viewDidLoadIMP, typeDescription);

這樣就把 UIViewController 的 -viewDidLoad 方法給替換成咱們自定義的方法,APP裏調用 UIViewController 的 viewDidLoad 方法都會去到上述 viewDidLoadIMP 函數裏,在這個新的IMP函數裏調用JS傳進來的方法,就實現了替換 -viewDidLoad 方法爲JS代碼裏的實現,同時爲 UIViewController 新增了個方法 -ORIGViewDidLoad 指向原來 viewDidLoad 的IMP,JS能夠經過這個方法調用到原來的實現。

方法替換就這樣很簡單的實現了,但這麼簡單的前提是,這個方法沒有參數。若是這個方法有參數,怎樣把參數值傳給咱們新的IMP函數呢?例如 UIViewController 的 -viewDidAppear: 方法,調用者會傳一個Bool值,咱們須要在本身實現的IMP(上述的viewDidLoadIMP)上拿到這個值,怎樣能拿到?若是隻是針對一個方法寫IMP,是能夠直接拿到這個參數值的:

1
2
3
static void viewDidAppear ( id slf, SEL sel, BOOL animated) {
    [function callWithArguments:@(animated)];
}

但咱們要的是實現一個通用的IMP,任意方法任意參數均可以經過這個IMP中轉,拿到方法的全部參數回調JS的實現。

va_list實現(32位)

最初我是用可變參數va_list實現:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static void commonIMP( id slf, ...)
   va_list args;
   va_start(args, slf);
   NSMutableArray *list = [[ NSMutableArray alloc] init];
   NSMethodSignature *methodSignature = [cls instanceMethodSignatureForSelector:selector];
   NSUInteger numberOfArguments = methodSignature.numberOfArguments;
   id obj;
   for ( NSUInteger i = 2; i < numberOfArguments; i++) {
       const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
       switch (argumentType[0]) {
           case 'i' :
               obj = @(va_arg(args, int ));
               break ;
           case 'B' :
               obj = @(va_arg(args, BOOL ));
               break ;
           case 'f' :
           case 'd' :
               obj = @(va_arg(args, double ));
               break ;
           …… //其餘數值類型
           default : {
               obj = va_arg(args, id );
               break ;
           }
       }
       [list addObject:obj];
   }
   va_end(args);
   [function callWithArguments:list];
}

這樣不管方法參數是什麼,有多少個,均可以經過 va_list 的一組方法一個個取出來,組成NSArray在調用JS方法時傳回。很完美地解決了參數的問題,一直運行正常,直到我跑在arm64的機子上測試,一調用就crash。查了資料,才發現arm64下va_list 的結構改變了,致使沒法上述這樣取參數。詳見這篇文章

ForwardInvocation實現(64位)

後來找到另外一種很是hack的方法解決參數獲取的問題,利用了OC消息轉發機制。

當調用一個 NSObject 對象不存在的方法時,並不會立刻拋出異常,而是會通過多層轉發,層層調用對象的 -resolveInstanceMethod:, -forwardingTargetForSelector:, -methodSignatureForSelector:, -forwardInvocation: 等方法,這篇文章說得比較清楚,其中最後 -forwardInvocation: 是會有一個 NSInvocation 對象,這個 NSInvocation 對象保存了這個方法調用的全部信息,包括 Selector 名,參數和返回值類型,最重要的是有全部參數值,能夠從這個 NSInvocation 對象裏拿到調用的全部參數值。咱們能夠想辦法讓每一個須要被JS替換的方法調用最後都調到 -forwardInvocation:,就能夠解決沒法拿到參數值的問題了。

具體實現,以替換 UIViewController 的 -viewWillAppear: 方法爲例:

  1. 把UIViewController的 -viewWillAppear: 方法經過 class_replaceMethod() 接口指向一個不存在的IMP: class_getMethodImplementation(cls, @selector(__JPNONImplementSelector)),這樣調用這個方法時就會走到 -forwardInvocation:。
  2. 爲 UIViewController 添加 -ORIGviewWillAppear: 和 -_JPviewWillAppear: 兩個方法,前者指向原來的IMP實現,後者是新的實現,稍後會在這個實現裏回調JS函數。
  3. 改寫 UIViewController 的 -forwardInvocation: 方法爲自定義實現。一旦OC裏調用 UIViewController 的 -viewWillAppear: 方法,通過上面的處理會把這個調用轉發到 -forwardInvocation: ,這時已經組裝好了一個 NSInvocation,包含了這個調用的參數。在這裏把參數從 NSInvocation 反解出來,帶着參數調用上述新增長的方法 -JPviewWillAppear: ,在這個新方法裏取到參數傳給JS,調用JS的實現函數。整個調用過程就結束了,整個過程圖示以下:

JSPatch2

最後一個問題,咱們把 UIViewController 的 -forwardInvocation: 方法的實現給替換掉了,若是程序裏真有用到這個方法對消息進行轉發,原來的邏輯怎麼辦?首先咱們在替換 -forwardInvocation: 方法前會新建一個方法 -ORIGforwardInvocation:,保存原來的實現IMP,在新的 -forwardInvocation: 實現裏作了個判斷,若是轉發的方法是咱們想改寫的,就走咱們的邏輯,若不是,就調 -ORIGforwardInvocation: 走原來的流程。

實現過程當中還碰到一個坑,就是從 -forwardInvocation: 裏的 NSInvocation 對象取參數值時,若參數值是id類型,咱們會這樣取:

1
2
id arg;
[invocation getArgument:&arg atIndex:i];

但這樣取某些時候會致使莫名其妙的crash,並且不是crash在這個地方,彷佛這裏的指針取錯致使後續的內存錯亂,crash在各類地方,這個bug查了我半天才定位到這裏,至今不知爲何。後來以這樣的方式解決了:

1
2
3
void *arg;
[invocation getArgument:&arg atIndex:i];
id a = (__bridge id )arg;

其餘就是實現上的細節了,例如須要根據不一樣的返回值類型生成不一樣的IMP,要在各到處理參數轉換等。

新增方法

在 JSPatch 剛開源時,是不支持爲一個類新增方法的,由於以爲能替換原生方法就夠了,新的方法純粹添加在JS對象上,只在JS端跑就好了。另外OC爲類新增方法須要知道各個參數和返回值的類型,須要在JS定一種方式把這些類型傳給OC才能完成新增方法,比較麻煩。後來挺多人比較關注這個問題,不能新增方法致使 action-target 模式沒法用,我也開始想有沒有更好的方法實現添加方法。一開始想到,反正新增的方法都是JS在用,不如新增的方法返回值和參數全統一成id類型,這樣就不用傳類型了,但仍是須要知道參數個數,後來跟Lancy聊天時找到了解決方案,JS能夠得到函數參數個數,直接封裝一下把參數個數一併傳給OC就好了。

如今 defineClass 定義的方法會通過JS包裝,變成一個包含參數個數和方法實體的數組傳給OC,OC會判斷若是方法已存在,就執行替換的操做,若不存在,就調用 class_addMethod() 新增一個方法,經過傳過來的參數個數和方法實體生成新的 Method,把 Method 的參數和返回值類型都設爲id。

這裏有個問題,若某個類實現了某protocol,protocol方法裏有可選的方法,它的參數不全是id類型,例如 UITableViewDataSource 的一個方法:

1
- ( NSInteger )tableView:(UITableView *)tableView sectionForSectionIndexTitle:( NSString *)title atIndex:( NSInteger )index;

若原類沒有實現這個方法,在JS裏實現了,會走到新增方法的邏輯,每一個參數類型都變成id,與這個 protocol 方法不匹配,產生錯誤。後續會處理 protocol 的問題,若新增的方法是 protocol 實現的方法,會取這個方法的 NSMethodSignature 得到正確的參數類型進行添加。

Property實現

1
2
3
4
5
6
7
8
9
defineClass( 'JPTableViewController : UITableViewController' , {
   dataSource: function () {
     var data = self.getProp( 'data' )
     if (data) return data;
     data = [1,2,3]
     self.setProp_forKey(data, 'data' )
     return data;
   }
}

JSPatch 能夠經過 -getProp:, -setProp:forKey: 這兩個方法給對象添加成員變量。實現上用了運行時關聯接口 objc_getAssociatedObject() 和 objc_setAssociatedObject() 模擬,至關於把一個對象跟當前對象 self 關聯起來,之後能夠經過當前對象 self 找到這個對象,跟成員的效果同樣,只是必定得是id對象類型。

原本OC有 class_addIvar() 能夠爲類添加成員,但必須在類註冊以前添加完,註冊完成後沒法添加,這意味着能夠爲在JS新增的類添加成員,但不能爲OC上已存在的類添加,因此只能用上述方法模擬。

self關鍵字

1
2
3
4
5
6
defineClass( "JPViewController: UIViewController" , {
   viewDidLoad: function () {
     var view = self.view()
     ...
   },
}

JSPatch支持直接在 defineClass 裏的實例方法裏直接使用 self 關鍵字,跟OC同樣 self 是指當前對象,這個 self 關鍵字是怎樣實現的呢?實際上這個self是個全局變量,在 defineClass 裏對實例方法 方法進行了包裝,在調用實例方法以前,會把全局變量 self 設爲當前對象,調用完後設回空,就能夠在執行實例方法的過程當中使用 self 變量了。這是一個小小的trick。

super關鍵字

1
2
3
4
5
defineClass( "JPViewController: UIViewController" , {
   viewDidLoad: function () {
     self. super .viewDidLoad()
   },
}

OC裏 super 是一個關鍵字,沒法經過動態方法拿到 super,那麼 JSPatch 的super是怎麼實現的?實際上調用 super 的方法,OC作的事是調用父類的某個方法,並把當前對象當成 self 傳入父類方法,咱們只要模擬它這個過程就好了。

首先JS端須要告訴OC想調用的是當前對象的 super 方法,作法是調用 self.super時,會返回一個新的 JSClass 實例,這個實例一樣保存了OC對象的引用,同時標識 __isSuper=1。

01
02
03
04
05
06
07
08
09
10
11
JSClass = function (obj, className, isSuper) {
     this .__obj = obj
     this .__isSuper = isSuper
     this .__clsName = className
}
JSClass.prototype.__defineGetter__( 'super' , function (){
   if (! this .__super) {
     this .__super = new JSClass( this .__obj, this .__clsName, 1)
   }
   return this .__super
})

調用方法時,__isSuper 會傳給OC,告訴OC要調 super 的方法。OC作的事情是,若是是調用 super 方法,找到 superClass 這個方法的IMP實現,爲當前類新增一個方法指向 super 的IMP實現,那麼調用這個類的新方法就至關於調用 super 方法。把要調用的方法替換成這個新方法,就完成 super 方法的調用了。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
static id callSelector( NSString *className, NSString *selectorName, NSArray *arguments, id instance, BOOL isSuper) {
     ...
     if (isSuper) {
         NSString *superSelectorName = [ NSString stringWithFormat: @"SUPER_%@" , selectorName];
         SEL superSelector = NSSelectorFromString (superSelectorName);
        
         Class superCls = [cls superclass];
         Method superMethod = class_getInstanceMethod(superCls, selector);
         IMP superIMP = method_getImplementation(superMethod);
        
         class_addMethod(cls, superSelector, superIMP, method_getTypeEncoding(superMethod));
         selector = superSelector;
     }
     ...
}

總結

整個 JSPatch 實現原理就大體描述完了,剩下的一些小點,例如GCD接口,block實現,方法名下劃線處理等就不細說了,能夠直接看代碼。JSPatch 還在持續改進中,但願能成爲iOS平臺動態更新的最佳解決方案,歡迎你們一塊兒建設這個項目,github地址: https://github.com/bang590/JSPatch

——————

版權聲明:本文章在微信公衆平臺的發表權,已「獨家代理」給指定公衆賬號:iOS開發(iOSDevTips)。

相關文章
相關標籤/搜索