wendux / DSBridge-IOS

:earth_asia: A modern cross-platform JavaScript bridge, through which you can invoke each other's functions synchronously or asynchronously between JavaScript and native.
1.96k stars 305 forks source link

JSBUtil.m 中关于方法 allMethodFromClass #62

Open xjh093 opened 5 years ago

xjh093 commented 5 years ago

此 allMethodFromClass 方法会循环遍历到根类

在项目中,我的处理方式是,直接把JS要调用的方法写在了控制器内 并没有为控制器写一个专门的 JSHandle 类(考虑到有很多控制器) 导致了遍历层级很多,有很多不必要的方法

我的处理方式: 在 JSBUtil.h 添加一个协议

@protocol JSBUtilDelegate <NSObject>
/**
 All methods for JS in a class.
 */
+ (NSArray *)ds_allMethodsForJS;

然后在 JSBUtil.m 内修改方法 allMethodFromClass


/// 缓存类的方法
static NSMutableDictionary *_JSBUtilClassMethodsCache;

+(NSArray *)allMethodFromClass:(Class)class
{
    // 先从缓存获取
    NSString *key = NSStringFromClass(class);
    NSArray *allMethods = [_JSBUtilClassMethodsCache valueForKey:key];
    if (allMethods) {
        return allMethods;
    }

    // 再通过协议获取
    NSArray *array = nil;
    if ([class respondsToSelector:@selector(ds_allMethodsForJS)]) {
        array = [(id<JSBUtilDelegate>class) ds_allMethodsForJS];
    }

    // 最后再遍历获取
    if (array.count == 0) {
        NSString *className = NSStringFromClass(class);
        NSMutableSet *methods = [NSMutableSet set];
        while (class) {
            unsigned int count = 0;
            Method *method = class_copyMethodList(class, &count);
            for (unsigned int i = 0; i < count; i++) {
                SEL name1 = method_getName(method[i]);
                const char *selName= sel_getName(name1);
                NSString *strName = [NSString stringWithCString:selName encoding:NSUTF8StringEncoding];
                [methods addObject:strName];
            }
            free(method);

            Class cls = class_getSuperclass(class);
            className = NSStringFromClass(cls);
            class = [className hasPrefix:@"NS"]?nil:cls;
        }
        array = methods.allObjects;
    }

    if (!_JSBUtilClassMethodsCache) {
        _JSBUtilClassMethodsCache = @{}.mutableCopy;
    }

   // 缓存
    [_JSBUtilClassMethodsCache setValue:array forKey:key];

    return array;
}

最后在具体的控制器内,实现该方法:

+ (NSArray *)ds_allMethodsForJS{
    return @[@"method1:",
             @"method2:callback:",
             @"method3:callback:",
             @"method4:callback:"];
}

@wendux

xjh093 commented 5 years ago

// 再通过协议获取

这里可以循环遍历父类,查看是否实现协议,如此获取所有方法。

    NSArray *array;
    NSMutableSet *methodSet = [NSMutableSet set];
    Class cls = [class class];
    while ([cls respondsToSelector:@selector(ds_allMethodsForJS)]) {
        NSArray *array = [(id<JSBUtilDelegate>cls) ds_allMethodsForJS];
        [methodSet addObjectsFromArray:array];

        cls = class_getSuperclass(cls);
    }
    array = methodSet.allObjects;
wendux commented 5 years ago

你这个方法可以,遍历父类方法确实大多数时候没必要,不过我想到另一种方式,即,定义一个协议,包含一个接口如findJSApiFromSuper,api类可以实现此协议, 用户如果返回true,再去遍历父类,如果返回fase或者没有实现此协议则不再遍历父类,至于缓存,有的话也是好的,你觉得呢?

:octocat: From gitme Android

xjh093 commented 5 years ago

你说的这个协议:findJSApiFromSuper,返回true,再去遍历父类 我觉得有点绕了,不过也是一种实现方式。

因为项目使用的是 VC+WebView,其实就是一个控制器管理一个webView 多引入一个类(专门的 JSHandle 类),逻辑就更复杂一些 其实有些控制器内,提供给JS的方法就只有一个,但是其它方法却很多 所以才考虑到,为什么不专把JS要的方法直接提供给它呢? 这个思路也是从 YYModel 中得到的,MJExtension 也有类似实现

wendux commented 5 years ago

直接禁止遍历父类方法符合百分之99的场景,这样直接传controller this也不会有额外开销, js Api多时,组合应该优先于继承。下个版本将取消遍历父类方法。

:octocat: From gitme Android

xjh093 commented 5 years ago

剩下的1%,我觉得还是需要的。 BaseViewController 可能会有一些公共的 JS 方法。 如果无法遍历父类,那每个子控制器都要重复添加 JS 方法了。

wendux commented 5 years ago

js Api不应该放在全局的Contolle基类中,只应该放在有webview的controller中。

:octocat: From gitme Android

xjh093 commented 5 years ago

我考虑的是,剩下的1%,可以预留扩展,让框架更健全一些。

JxJPu9bD commented 5 years ago

其实我就是这1%中的,专门修改了2.6.0代码准备提交PR时发现已经有提交过了,最好的方式应该就是增加一个协议的了.灵活些

JxJPu9bD commented 5 years ago

我考虑的是,剩下的1%,可以预留扩展,让框架更健全一些。

另外,WKWebView 加载本地HTML并携带参数这个问题有解决方案吗?我搜了很多,只有在模拟器中可用,真机不可用.

xjh093 commented 5 years ago

携带参数?

JxJPu9bD commented 5 years ago

携带参数? 比如 index.html?param=3 这样

xjh093 commented 5 years ago

这个跟前端商量好,不就可以了么?

example: [self loadURL:kH5Path(@"/info?uid=12345")];

kH5Path: 全路径的前面部分 /info: 比如说是个人信息界面 uid=12345:就是参数