為什么不能runtime創(chuàng)建JSExport類型的Protocol?

JavaScriptCore引入后,js調(diào)用OC的方法有了新的實(shí)現(xiàn)方式。讓一個(gè)類遵循一個(gè)JSExport的協(xié)議,將想要暴露的方法在JSExport協(xié)議中聲明,即可在js中直接調(diào)用到OC的方法。
例如下面的代碼:

#import <JavaScriptCore/JavaScriptCore.h>

@protocol NSTestExport <JSExport>
-(NSString *)str;
-(void)setStr:(NSString *)str;
@end

@interface ViewController : UIViewController<NSTestExport>
@property (nonatomic, strong) NSString *str;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    _str = @"asdf";
        
    context = [[JSContext alloc] init];
    
    context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        NSLog(@"exception: %@",exception);
    };
    
    context[@"log"] = ^(NSString *msg){
        NSLog(@"log msg: %@",msg);
    };
    
    context[@"viewController"] = self;
    
    [context evaluateScript:@"viewController.setStr('dsfg')"];
    JSValue *string = [context evaluateScript:@"viewController.str()"];

     NSLog(@"log str: %@",[result toString]);
}

但是這樣的實(shí)現(xiàn)并不靈活,如果我有大量的類和其中的方法要在js中使用,那么我可能需要實(shí)現(xiàn)大量的JSExport協(xié)議,這樣會(huì)導(dǎo)致項(xiàng)目中的代碼量大大增加。于是想到了可以使用runtime動(dòng)態(tài)創(chuàng)建這些協(xié)議,然后添加到class中。

1、首先讓我們制作一個(gè)擴(kuò)展JSExport的新協(xié)議,假設(shè)我們有一個(gè)Class class我們要導(dǎo)出的變量:

const char *protocolName = class_getName(class);
    Protocol *protocol = objc_allocateProtocol(protocolName);
    protocol_addProtocol(protocol, objc_getProtocol("JSExport"));

2、然后我們從類中讀取方法列表和屬性列表,并將它們添加到protocol中:
實(shí)例方法:

{
    NSUInteger methodCount, classMethodCount;
    Method *methods, *classMethods;
    methods = class_copyMethodList(class, &methodCount);
    for (NSUInteger methodIndex = 0; methodIndex < methodCount; ++methodIndex) {
        Method method = methods[methodIndex];
        protocol_addMethodDescription(protocol, method_getName(method), method_getTypeEncoding(method), YES, YES);
    }
}

類方法:

{
    classMethods = class_copyMethodList(object_getClass(class), &classMethodCount);
    for (NSUInteger methodIndex = 0; methodIndex < classMethodCount; ++methodIndex) {
        Method method = classMethods[methodIndex];
        protocol_addMethodDescription(protocol, method_getName(method), method_getTypeEncoding(method), YES, NO);
    }
}

屬性:
添加屬性的方法基本和添加方法相同,但是我們還需要獲取每個(gè)屬性的特性,并添加到協(xié)議中

{
    NSUInteger propertyCount;
    objc_property_t *properties;
    properties = class_copyPropertyList(class, &propertyCount);
    for (NSUInteger propertyIndex = 0; propertyIndex < propertyCount; ++propertyIndex) {
        objc_property_t property = properties[propertyIndex];
        NSUInteger attributeCount;
//每個(gè)屬性的特性
        objc_property_attribute_t *attributes = property_copyAttributeList(property, &attributeCount);
        protocol_addProperty(protocol, property_getName(property), attributes, attributeCount, YES, YES);
        free(attributes);
    }
}

3、將新協(xié)議添加到類中

objc_registerProtocol(protocol);
//校驗(yàn)protocol是否遵循JSExport協(xié)議
BOOL conform = protocol_conformsToProtocol(protocol, @protocol(JSExport));
NSLog(@"conform: %d",conform);
        
BOOL success = class_addProtocol(class, protocol);

4、然后理論上我們應(yīng)該是可以在js中使用這個(gè)類中的方法了,接下來(lái)使用下面的代碼測(cè)試下。

context = [[JSContext alloc] init];
    
    context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        NSLog(@"exception: %@",exception);
    };
        
    context[@"viewController"] = self;
    
    [context evaluateScript:@"viewController.setStr('dsfg')"];

然后我們發(fā)現(xiàn),js的執(zhí)行拋了異常。為什么呢?我們的實(shí)現(xiàn)邏輯并沒(méi)有問(wèn)題。
這里查看JavaScriptCore源代碼。

最后發(fā)現(xiàn)原因在與objCCallbackFunctionForMethod方法,改函數(shù)通過(guò)調(diào)用objCCallbackFunctionForInvocation返回了一個(gè)原生函數(shù)的指針JSObjectRef。objCCallbackFunctionForInvocation函數(shù)的調(diào)用語(yǔ)句如下:

objCCallbackFunctionForInvocation(context, invocation, isInstanceMethod ? CallbackInstanceMethod : CallbackClassMethod, isInstanceMethod ? cls : nil, _protocol_getMethodTypeEncoding(protocol, sel, YES, isInstanceMethod))。

這里使用了_protocol_getMethodTypeEncoding函數(shù)。到ObjcRuntimeExtras.h中看看函數(shù)的定義。

// Forward declare some Objective-C runtime internal methods that are not API.
const char *_protocol_getMethodTypeEncoding(Protocol *, SEL, BOOL isRequiredMethod, BOOL isInstanceMethod);

再到https://opensource.apple.com/source/objc4/objc4-551.1/runtime/objc-runtime-new.mm中找到了實(shí)現(xiàn):

/***********************************************************************
 * _protocol_getMethodTypeEncoding
 * Return the @encode string for the requested protocol method.
 * Returns nil if the compiler did not emit any extended @encode data.
 * Locking: acquires runtimeLock
 **********************************************************************/
const char *
_protocol_getMethodTypeEncoding(Protocol *proto_gen, SEL sel,
                                BOOL isRequiredMethod, BOOL isInstanceMethod)
{
    protocol_t *proto = newprotocol(proto_gen);
    if (!proto) return nil;
    fixupProtocolIfNeeded(proto);
    const char *enc;
    rwlock_read(&runtimeLock);
    enc = protocol_getMethodTypeEncoding_nolock(proto, sel,
                                                isRequiredMethod,
                                                isInstanceMethod);
    rwlock_unlock_read(&runtimeLock);
    return enc;
}

函數(shù)注釋中寫名了,Returns nil if the compiler did not emit any extended @encode data.所以我們只能在編譯階段創(chuàng)建好JSExport

本文作者: ctinusdev
原文鏈接: https://ctinusdev.github.io/2017/08/05/CantnotCreateJSExportAtRuntime/
轉(zhuǎn)載請(qǐng)注明出處!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文由我們團(tuán)隊(duì)的 糾結(jié)倫 童鞋撰寫。 寫在前面 本篇文章是對(duì)我一次組內(nèi)分享的整理,大部分圖片都是直接從keynot...
    知識(shí)小集閱讀 15,382評(píng)論 11 172
  • 注:本文copy自http://www.itdecent.cn/p/ac534f508fb0,純屬當(dāng)筆記使用。 概...
    BookKeeping閱讀 788評(píng)論 1 3
  • 寫在前面 本篇文章是對(duì)我一次組內(nèi)分享的整理,大部分圖片都是直接從keynote上截圖下來(lái)的,本來(lái)有很多炫酷動(dòng)效的,...
    等開(kāi)會(huì)閱讀 14,709評(píng)論 6 69
  • JavaScriptCore框架主要是用來(lái)實(shí)現(xiàn)iOS與H5的交互。由于現(xiàn)在混合編程越來(lái)越多,H5的相對(duì)講多,所以研...
    水靈芳蕥閱讀 1,492評(píng)論 1 8
  • 日本人是出了名的認(rèn)真。但不得不說(shuō),在看《全能住宅改造王》之前,我對(duì)他們的認(rèn)真還是缺乏認(rèn)識(shí)。 《全能住宅改造王》是從...
    魚鮮支閱讀 587評(píng)論 5 2

友情鏈接更多精彩內(nèi)容