JOBridge之二JS注冊(cè)類(lèi)和訪(fǎng)問(wèn)所有Native方法(可用代替JSPatch)

簡(jiǎn)述

在上篇文章:JOBridge之一任意方法的Swizzle(鏈接地址http://www.reibang.com/p/905e06eeda7b)卵洗,介紹了Swizzle任意方法(暫不包含有變長(zhǎng)參數(shù)中的匿名參數(shù),例如stringWithFormat:的第二個(gè)以上的參數(shù),對(duì)于這種情況的處理較為麻煩芹助,以后再處理)的詳細(xì)方案脐恩,其做法是定義一個(gè)無(wú)參數(shù)無(wú)返回值的C樁函數(shù),將被替換方法替換到該樁函數(shù)技矮,在樁函數(shù)內(nèi)部暫存參數(shù)化漆,調(diào)用解析函數(shù)根據(jù)方法簽名解析出所有參數(shù)放入數(shù)組,然后進(jìn)入JOCallJsFunction調(diào)用對(duì)應(yīng)的JS方法钦奋。本篇博客繼續(xù)講根據(jù)JS調(diào)用class注冊(cè)方法座云,如何注冊(cè)一個(gè)類(lèi)以及其屬性和方法;以及向JS上下文開(kāi)放所有的原生方法付材,并完成參數(shù)轉(zhuǎn)換朦拖、調(diào)用和返回值轉(zhuǎn)換等。此篇完成后JOBridge基本就可以使用了厌衔,利用其可以重新一個(gè)原生的頁(yè)面璧帝,實(shí)現(xiàn)動(dòng)態(tài)更新代碼,甚至于實(shí)時(shí)更新代碼(不過(guò)這種方式有一定風(fēng)險(xiǎn))富寿,下一篇講如何比較簡(jiǎn)單的開(kāi)放c函數(shù)調(diào)用入口和其他一些零散的東西和優(yōu)化點(diǎn)睬隶。廢話(huà)不多說(shuō),直接進(jìn)入正題页徐。

注冊(cè)類(lèi)

Native通過(guò)js上下文向js開(kāi)放注冊(cè)接口苏潜,js通過(guò)該接口注冊(cè)類(lèi)。

jsContext[@"interface"] = ^(JSValue *className, JSValue *properties, JSValue *classMethods, JSValue *metaClassMethods) {
    [JOClass classWithName:className properties:properties classMethods:classMethods metaClassMethods:metaClassMethods];
};

注冊(cè)時(shí)傳入類(lèi)名字变勇,繼承的父類(lèi)恤左,實(shí)現(xiàn)的協(xié)議,屬性列表搀绣,類(lèi)方法飞袋,元類(lèi)方法等參數(shù),然后調(diào)用JOClass的方法來(lái)注冊(cè)链患,該方法會(huì)依次調(diào)用parseClass巧鸭,parseMethods,parseProperties锣险,addDealloc來(lái)完成類(lèi)的注冊(cè)蹄皱,整個(gè)過(guò)程不難,簡(jiǎn)單說(shuō)明即可芯肤。

parseClass傳入的參className是包含類(lèi)名巷折,父類(lèi)名和協(xié)議的字符串,解析出名字后調(diào)用OC的運(yùn)行時(shí)方法創(chuàng)建類(lèi)和添加協(xié)議崖咨。

- (void)parseClass:(NSString *)className {
    
    NSString *aClassName = JOTools.trim([self getClassName:className]);
    NSString *superClassName = JOTools.trim([self getSuperClassName:className]);
    NSArray *protocolsName = [self getProtocolName:className];
    
    self.class = objc_getClass(aClassName.UTF8String);
    if (!self.class) {
        self.isNewClass = YES;
        self.superClass = objc_getClass(superClassName.UTF8String);
        if (!self.superClass) {
            self.class = NULL;
            return;
        }
        self.class = objc_allocateClassPair(self.superClass, aClassName.UTF8String, 2);
    }
    
    if (!self.protocols) self.protocols = [NSMutableArray array];
    for (NSString *obj in protocolsName) {
        Protocol *pro = objc_getProtocol(JOTools.trim(obj).UTF8String);
        class_addProtocol(self.class, pro);
        [self.protocols addObject:pro];
    }
}

注冊(cè)屬性和成員變量锻拘,

將js對(duì)象轉(zhuǎn)成字典,枚舉字典,調(diào)用JOAddPropertyAttribute具體添加屬性和成員變量署拟,完成后調(diào)用objc_registerClassPair注冊(cè)類(lèi)婉宰,此后類(lèi)模板固定不能再添加成員變量,但屬性和方法不受限制推穷。

- (void)parseProperties:(JSValue *)jsValue {
    NSDictionary *propertyList = [jsValue toDictionary];
    for (NSString *obj in propertyList.allKeys) {//這里全部使用關(guān)聯(lián)對(duì)象也可以心包,這不過(guò)先實(shí)現(xiàn)了class_addProperty
        JOAddPropertyAttribute(self.class, obj, propertyList[obj], self.isNewClass);
    }
    
    if (self.isNewClass) objc_registerClassPair(self.class);
}

JOAddPropertyAttribute方法如下:


//目前只支持OC類(lèi)型,基礎(chǔ)類(lèi)型暫時(shí)不支持馒铃,基礎(chǔ)類(lèi)型由NSNumber代替蟹腾,而NSNumber和js中的Number等價(jià)
void JOAddPropertyAttribute(Class class, NSString *name, NSArray *att, BOOL isNewClass) {
    if (!isNewClass) goto JOAssociatedTag;//使用關(guān)聯(lián)對(duì)象只需要添加方法,關(guān)聯(lián)對(duì)象目前只支持retain
    
    objc_property_attribute_t nonatomic = {"N", ""};
    objc_property_attribute_t ownership = {"&", ""};
    objc_property_attribute_t type = {"T", @encode(id)};

    if ([att.lastObject isEqualToString:@"weak"]) {
        ownership = (objc_property_attribute_t){"W",""};
    } else if ([att.lastObject isEqualToString:@"copy"]) {
        ownership = (objc_property_attribute_t){"C",""};
    }
//    else if ([att.lastObject isEqualToString:@"assign"]) {
//        type = (objc_property_attribute_t){"T", [[NSString stringWithFormat:@"@\"%@\"",att.firstObject] UTF8String]};
//    }
    
    objc_property_attribute_t attribute[] = { ownership, nonatomic, type};
    BOOL success = class_addProperty(class, [name UTF8String], attribute, 3);
    if (success) {
        //這里似乎要手動(dòng)調(diào)用class_addIvar才能將變量描述進(jìn)去区宇,僅用class_addProperty似乎不奏效娃殖。
        class_addIvar(class, [[NSString stringWithFormat:@"_%@",name] UTF8String], sizeof(id), log2(sizeof(id)), @encode(id));
JOAssociatedTag:
        class_addMethod(class, NSSelectorFromString(name), (IMP)JOGetter, "@@:");
        NSString *head = [[name substringToIndex:1] uppercaseString];
        NSString *set = [NSString stringWithFormat:@"set%@%@:", head, [name substringFromIndex:1]];
        class_addMethod(class, NSSelectorFromString(set), (IMP)JOSetter, "v@:@");
    }
}

這里為了簡(jiǎn)單,暫時(shí)僅支持OC類(lèi)型的屬性添加议谷。如果不是JS動(dòng)態(tài)創(chuàng)建類(lèi)炉爆,則只能使用關(guān)聯(lián)對(duì)象,直接goto到JOAssociatedTag卧晓,僅添加getter和setter方法芬首,這里要小心的是編譯器會(huì)添加release操作,goto別跳過(guò)去了逼裆,這會(huì)導(dǎo)致內(nèi)存泄露衩辟。

如果是新創(chuàng)建的類(lèi),則可以添加成員變量波附。創(chuàng)建三個(gè)objc_property_attribute_t屬性艺晴,調(diào)用class_addProperty添加屬性,如果成功則調(diào)用class_addIvar添加成員變量掸屡,比較重要的是內(nèi)存大小的計(jì)算log2(sizeof(id))封寞,其頭文件有說(shuō)明,對(duì)齊大小將以1左移align仅财,所以只可能是1,2,4,8...狈究,任何指針類(lèi)型都傳log2(sizeof(pointer_type))也就是8。

接下來(lái)通過(guò)動(dòng)態(tài)方法添加的方式添加一組getter和setter盏求,默認(rèn)實(shí)現(xiàn)為JOGetter和JOSetter抖锥。

id JOGetter(id obj, SEL sel) {
    NSString *key = [NSString stringWithFormat:@"_%@",NSStringFromSelector(sel)];
    Ivar ivar = class_getInstanceVariable([obj class], [key UTF8String]);
    if (ivar) {
        return object_getIvar(obj, ivar);
    } else {
        return objc_getAssociatedObject(obj, JOAssociatedKey(key));
    }
}

/*  object_setIvar不會(huì)retain對(duì)象,而object_setIvarWithStrongDefault在iOS10之后才有效碎罚,
    所以需要手動(dòng)調(diào)用retain磅废,并在父對(duì)象dealloc的時(shí)候調(diào)用release
 */
void JOSetter(id obj, SEL sel, id newValue) {
    NSString *selStr = [NSStringFromSelector(sel) substringFromIndex:3];
    NSString *head = [[selStr substringToIndex:1] lowercaseString];
    NSString *tail = [selStr substringFromIndex:1];
    tail = [tail substringToIndex:tail.length - 1];
    NSString *key = [NSString stringWithFormat:@"_%@%@", head, tail];

    Ivar ivar = class_getInstanceVariable([obj class], [key UTF8String]);
    if (ivar) {
        id value = object_getIvar(obj, ivar);
        JOTools.release(value);
        object_setIvar(obj, ivar, newValue);
        JOTools.retain(newValue);
    } else {
        objc_setAssociatedObject(obj, JOAssociatedKey(key), newValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}

JOGetter先通過(guò)class_getInstanceVariable在類(lèi)中查找實(shí)例描述Ivar,如果找到了則表示其為成員變量荆烈,調(diào)用對(duì)象的object_getIvar方法獲取成員變量值拯勉,否則就調(diào)用關(guān)聯(lián)對(duì)象方法獲取關(guān)聯(lián)對(duì)象竟趾。

JOSetter套路類(lèi)似,先獲取描述宫峦,如果有描述則先調(diào)用object_setIvar存儲(chǔ)數(shù)據(jù)岔帽,需要注意的是其不會(huì)retain對(duì)象,而會(huì)retain對(duì)象的方法object_setIvarWithStrongDefault在iOS10才可用导绷,所以這里需要手動(dòng)調(diào)用JOTools.retain()犀勒。如果是關(guān)聯(lián)對(duì)象就簡(jiǎn)單了,直接調(diào)用相關(guān)方法就好妥曲。需要注意的是這里的Key必須是唯一變量地址账蓉,這里將key放入全局字典,逾一。

JOTools.retain實(shí)現(xiàn)如下,就是匯編調(diào)用_objc_retain(當(dāng)然也可以用別的實(shí)現(xiàn)方案)肮雨,注意參數(shù)和堆棧遵堵,需要注意的是這里其實(shí)是有一個(gè)參數(shù)的存在x0中,但在該retain函數(shù)中我并不使用怨规,所以不用在棧上存儲(chǔ)陌宿,也就不用手動(dòng)修改棧大小(不過(guò)stp波丰, ldp命令會(huì)導(dǎo)致sp增減)壳坪,直接調(diào)用_objc_retain就可以了,不過(guò)retain這個(gè)樁函數(shù)會(huì)默認(rèn)插入?yún)R編指令ret掰烟,因此這里需要將x29, x30入棧爽蝴,如果不想入棧優(yōu)化性能,可以把retain定義為宏函數(shù)纫骑,直接調(diào)用_objc_retain蝎亚。release同理。

OS_ALWAYS_INLINE void retain() {
    asm volatile("stp    x29, x30, [sp, #-0x10]!");
    asm volatile("mov    x29, sp");
    asm volatile("bl _objc_retain");
    asm volatile("mov    sp, x29");
    asm volatile("ldp    x29, x30, [sp], #0x10");
}
OS_ALWAYS_INLINE void release() {
    asm volatile("stp    x29, x30, [sp, #-0x10]!");
    asm volatile("mov    x29, sp");
    asm volatile("bl _objc_release");
    asm volatile("mov    sp, x29");
    asm volatile("ldp    x29, x30, [sp], #0x10");
}

注冊(cè)方法

接下來(lái)解析JS方法先馆,根據(jù)方法列表創(chuàng)建對(duì)應(yīng)的Method发框。

- (void)parseMethods:(JSValue *)jsMethods isMeta:(BOOL)isMeta {
    NSDictionary *methods = [jsMethods toDictionary];
    for (NSString *method in methods) {
        JSValue *jsMethod = [jsMethods valueForProperty:method];
        SEL sel = [self getSelectorWithString:method];
        
        //這里使用class_copyMethodList,其只會(huì)獲取當(dāng)前類(lèi)方法煤墙,不會(huì)獲取父類(lèi)方法梅惯,而class_getInstanceMethod等會(huì)獲取父類(lèi)方法
        Method ocMethod = isMeta ? JOGetMethodWithSelector(object_getClass(self.class), sel)
                                 : JOGetMethodWithSelector(self.class, sel);
        if (ocMethod) {
            method_setImplementation(ocMethod, JOGlobalSwizzle);
            JOAddJsMethod(self.class, NSStringFromSelector(sel), jsMethod);
            continue;
        }
        Method ocSuperMethod = isMeta ? class_getClassMethod([self.class superclass], sel)
                                      : class_getInstanceMethod([self.class superclass], sel);
        if (ocSuperMethod) {
            const char *type = method_getTypeEncoding(ocSuperMethod);
            class_addMethod(self.class, sel, JOGlobalSwizzle, type);
            JOAddJsMethod(self.class, NSStringFromSelector(sel), jsMethod);
            continue;
        }
        
        char *type = NULL;
        for (Protocol *p in self.protocols) {
            type = protocol_getMethodDescription(p, sel, YES, !isMeta).types;
            if (!type) type = protocol_getMethodDescription(p, sel, NO, !isMeta).types;
            if (type) break;
        }
        
        //如果協(xié)議中也沒(méi)有此方法簽名,表明是由js新創(chuàng)建的方法仿野,則獲取js提供的簽名
        if (type) {
            class_addMethod(isMeta ? object_getClass(self.class) : self.class, sel, JOGlobalSwizzle, type);
        } else {
            NSArray *array = [jsMethod toObject];
            if ([array isKindOfClass:[NSArray class]]
                && array.count > 1
                && [array.firstObject isKindOfClass:[NSString class]]) {
                const char *type = [array.firstObject UTF8String];
                class_addMethod(isMeta ? object_getClass(self.class) : self.class, sel, JOGlobalSwizzle, type);
                jsMethod = jsMethod[1];
            } else {
                continue;
            }
        }
        JOAddJsMethod(self.class, NSStringFromSelector(sel), jsMethod);
    }
}

獲取JS中的SEL名字構(gòu)建SEL铣减,在當(dāng)前類(lèi)中獲取對(duì)應(yīng)的Method,注意使用class_copyMethodList脚作。如果有對(duì)應(yīng)的Method徙歼,則只需替換其實(shí)現(xiàn)為JOGlobalSwizzle,同時(shí)將對(duì)應(yīng)的JS實(shí)現(xiàn)以ClassName和SEL為Key存入全局字典。這里就和上篇博客關(guān)聯(lián)起來(lái)了魄梯,調(diào)用鏈為ClassA的MethodA->JOGlobalSwizzle->...->JOCallJsFunction->JS->(Native)桨螺。

如果當(dāng)前類(lèi)沒(méi)有該方法,則跳過(guò)該類(lèi)酿秸,去父類(lèi)中循環(huán)獲取灭翔,如果找到方法,則調(diào)用class_addMethod添加方法辣苏,相當(dāng)于當(dāng)前類(lèi)重寫(xiě)該方法肝箱,調(diào)用JOAddJsMethod添加全局字典,并跳到下一次循環(huán)稀蟋。

接下來(lái)去協(xié)議中查找方法簽名煌张,如果找到了則添加方法,如果沒(méi)有找到退客,則表明這是個(gè)JS新定義的方法骏融,獲取JS提供簽名后向類(lèi)添加方法,最后把JS方法添加到全局字典萌狂。

目前class方法等注冊(cè)和方法調(diào)用沒(méi)有加鎖档玻,所以在調(diào)用時(shí)重新注冊(cè)很可能會(huì)涼涼,也就是說(shuō)最好不要實(shí)時(shí)跟新代碼茫藏,注冊(cè)最好在app啟動(dòng)時(shí)完成误趴。

注冊(cè)dealloc

前面講到所有的成員變量會(huì)調(diào)用retain來(lái)持有變量,帶來(lái)的結(jié)果就是內(nèi)存泄露务傲,所有需要在注冊(cè)類(lèi)的dealloc中來(lái)release一次凉当。

//對(duì)JOSetter中retain的對(duì)象一次調(diào)用release
void JORelease(__unsafe_unretained id obj, SEL sel) {
    unsigned int count;
    Ivar *v = class_copyIvarList([obj class], &count);
    for (int i = 0; i < count; ++i) {
        const char *name = ivar_getName(v[i]);
        Ivar ivar = class_getInstanceVariable([obj class],name);
        
        __unsafe_unretained id value = object_getIvar(obj, ivar);
        object_setIvar(obj, ivar, nil);
        JOTools.release(value);
    }
    free(v);
//#pragma clang diagnostic push
//#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//#pragma clang diagnostic ignored "-Wundeclared-selector"
//    if ([obj respondsToSelector:@selector(JODealloc)]) {
//        [obj performSelector:@selector(JODealloc)];
//    }
//    
//#pragma clang diagnostic pop
    
    //調(diào)用父類(lèi)dealloc實(shí)現(xiàn),重寫(xiě)dealloc后售葡,編譯器會(huì)默認(rèn)插入父類(lèi)dealloc的調(diào)用纤怒,但這里修改其實(shí)現(xiàn)后,必須手動(dòng)調(diào)用
    IMP imp = class_getMethodImplementation([[obj class] superclass], sel);
    imp ? ((void(*)(id, SEL))imp)(obj, sel) : nil;
}

這里將class所有的Ivar枚舉出來(lái)天通,獲取所有的成員對(duì)象泊窘,依次調(diào)用release,注意free IvarList(這里我一直忘了free像寒,這么個(gè)小錯(cuò)誤導(dǎo)致的內(nèi)存泄露查了好久才查出來(lái)烘豹,??)。

本來(lái)我是打算讓JS通過(guò)重寫(xiě)JODealloc來(lái)實(shí)現(xiàn)自定義的dealloc過(guò)程诺祸,后來(lái)發(fā)現(xiàn)會(huì)導(dǎo)致crash携悯,原因在于調(diào)用JODealloc會(huì)讓js通過(guò)共享對(duì)象也持有當(dāng)前對(duì)象,這里調(diào)用完成后當(dāng)前對(duì)象已經(jīng)沒(méi)了筷笨,而js卻還持有共享對(duì)象憔鬼。目前我還沒(méi)有想到好的解決方案龟劲,只能暫時(shí)不提供該功能,自己能砍需求就是牛逼爸峄颉昌跌!??

我們平常重寫(xiě)dealloc方法,Xcode編譯器會(huì)幫我們插入調(diào)用父類(lèi)dealloc的代碼照雁,所以這里需要手動(dòng)調(diào)用一下父類(lèi)的dealloc蚕愤,另外需要注意的是在這個(gè)函數(shù)實(shí)現(xiàn)中最好不要retain當(dāng)前對(duì)象,weak也不能用饺蚊,因?yàn)樵谡{(diào)用了父類(lèi)的dealloc后萍诱,對(duì)象已經(jīng)釋放了,這時(shí)候編譯器幫你插入的release再調(diào)用就會(huì)crash污呼。weak對(duì)象的問(wèn)題在于其調(diào)用函數(shù)時(shí)會(huì)被retain裕坊,調(diào)用完會(huì)立即release,這也很可能crash燕酷。

JS訪(fǎng)問(wèn)所有的Native方法(JS->OC通用調(diào)用橋)

JSC提供共享對(duì)象籍凝,JS和Native可以相互傳遞參數(shù),同時(shí)有了類(lèi)似于JOGlobalSwizzle可以用于解析參數(shù)和轉(zhuǎn)發(fā)消息函數(shù)之后悟狱,JS動(dòng)態(tài)調(diào)用OC方法就有了可能,只需要將OC對(duì)象和SEL傳給Native堰氓,就可以動(dòng)態(tài)調(diào)用所有OC方法了挤渐,若不處理過(guò)多(種)的參數(shù),通過(guò)performSelector動(dòng)態(tài)調(diào)用双絮,甚至只需要少量的代碼就可以完成浴麻。如果在JSContext中注入一個(gè)方法來(lái)實(shí)現(xiàn),那么js語(yǔ)法將會(huì)很復(fù)雜囤攀,同時(shí)不能使用“.”運(yùn)算符软免,為了調(diào)用簡(jiǎn)單,我還是借鑒JSPatch類(lèi)似的語(yǔ)法(沒(méi)法子js不精通焚挠,玩不出新花樣)膏萧,只不過(guò)我這里實(shí)現(xiàn)就完全不一樣了。

JS根類(lèi)添加_oc_屬性

通過(guò)JSContext給js的根類(lèi)Object的prototype原型添加一個(gè)_oc_屬性蝌衔,其描述信息為@{JSPropertyDescriptorValueKey:^{...},JSPropertyDescriptorConfigurableKey:@(NO), JSPropertyDescriptorEnumerableKey:@(NO)}榛泛。這里使用JSPropertyDescriptorValueKey,并提供一個(gè)block噩斟,js就可以直接調(diào)用該屬性了曹锨。具體的玩法,注釋里面有描述剃允。

/*  給js的根類(lèi)添加一個(gè)_oc_方法(或者說(shuō)一個(gè)屬性吧)沛简,其返回了一個(gè)block齐鲤,block第一個(gè)參數(shù)是selector名稱(chēng),同時(shí)在上下文獲取currentThis椒楣,從中取出obj给郊,余下的參數(shù)則在后面,這里我只寫(xiě)了12個(gè)撒顿,添加也容易丑罪,補(bǔ)上去就行(當(dāng)然還有其他麻煩一點(diǎn)的辦法,比如根據(jù)selector動(dòng)態(tài)獲取凤壁,也可以通過(guò)預(yù)處理js將參數(shù)打包成數(shù)組吩屹,都可以支持任意長(zhǎng)度了)。
js調(diào)用會(huì)被預(yù)處理拧抖,例如tableView.setDelegate_(self)煤搜,會(huì)被轉(zhuǎn)成tableView._oc_('setDelegate_',self),當(dāng)然也可以使用其他的語(yǔ)法唧席。在調(diào)用原生方法的時(shí)候我偷了一下懶擦盾,使用了NSInvocation胯究。也可以像獲取任意參數(shù)一樣姨丈,強(qiáng)制寫(xiě)入?yún)?shù)到寄存器和棧來(lái)調(diào)用objc_msgSend(當(dāng)然也可以直接調(diào)用其實(shí)現(xiàn),但可能沒(méi)有objc_msgSend的緩存效果)银还,就是有點(diǎn)麻煩徒仓,后續(xù)有時(shí)間再改腐碱。
*/
[self.jsContext[@"Object"][@"prototype"] defineProperty:@"_oc_" descriptor:@{JSPropertyDescriptorValueKey:^id (JSValue *selName, JSValue *p0, JSValue *p1, JSValue *p2,  JSValue *p3, JSValue *p4, JSValue *p5, JSValue *p6, JSValue *p7, JSValue *p8, JSValue *p9, JSValue *p10, JSValue *p11, JSValue *p12) {
        
    NSString *tmp = [[selName toString] stringByReplacingOccurrencesOfString:@"__" withString:@"-"];
    tmp = [tmp stringByReplacingOccurrencesOfString:@"_" withString:@":"];
    SEL sel = NSSelectorFromString([tmp stringByReplacingOccurrencesOfString:@"-" withString:@"_"]);

    id obj = [[JSContext currentThis] toObject];
    NSAssert(obj, @"調(diào)用者容器不能為空");
    if (![obj obj]) return [NSNull null];
    obj = [obj obj];
    NSMethodSignature *sign = [obj methodSignatureForSelector:sel];
    NSAssert(sign, @"簽名不能為空,請(qǐng)先檢測(cè)JS調(diào)用函數(shù)是否書(shū)寫(xiě)正確掉弛,再檢查OC是否提供此函數(shù)");
    id params[] = {p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12};//創(chuàng)建c數(shù)組

    NSInvocation *invoke = [NSInvocation invocationWithMethodSignature:sign];
    [invoke setTarget:obj];
    [invoke setSelector:sel];
    NSUInteger num = [sign numberOfArguments];
    for (int i = 2; i < num; ++i) {
        const char* type = [sign getArgumentTypeAtIndex:i];
        while (*type == 'r' || // const
               *type == 'n' || // in
               *type == 'N' || // inout
               *type == 'o' || // out
               *type == 'O' || // bycopy
               *type == 'R' || // byref
               *type == 'V') { // oneway
            type++; // cut off useless prefix
        }
        switch (type[0]) {
            case 'B': {BOOL v = [params[i-2] toBool]; [invoke setArgument:&v atIndex:i]; break;}
            case 'c': {char v = [[params[i-2] toNumber] charValue]; [invoke setArgument:&v atIndex:i]; break;}
            case 'C': {Byte v = [[params[i-2] toNumber] unsignedCharValue]; [invoke setArgument:&v atIndex:i]; break;}
            case 's': {short v = [[params[i-2] toNumber] shortValue]; [invoke setArgument:&v atIndex:i]; break;}
            case 'S': {unsigned short v = [[params[i-2] toNumber] unsignedShortValue]; [invoke setArgument:&v atIndex:i]; break;}
            case 'i': {int v = [params[i-2] toInt32]; [invoke setArgument:&v atIndex:i]; break;}
            case 'I': {unsigned int v = [params[i-2] toUInt32]; [invoke setArgument:&v atIndex:i]; break;}
            case 'l': {long v = [[params[i-2] toNumber] longValue]; [invoke setArgument:&v atIndex:i]; break;}
            case 'L': {unsigned long v = [[params[i-2] toNumber] unsignedLongValue]; [invoke setArgument:&v atIndex:i]; break;}
            case 'q': {long long v = [[params[i-2] toNumber] longLongValue]; [invoke setArgument:&v atIndex:i]; break;}
            case 'Q': {unsigned long long v = [[params[i-2] toNumber] unsignedLongLongValue]; [invoke setArgument:&v atIndex:i]; break;}
            case 'f': {float v = [[params[i-2] toNumber] floatValue]; [invoke setArgument:&v atIndex:i]; break;}
            case 'd': {double v = [params[i-2] toDouble]; [invoke setArgument:&v atIndex:i]; break;}
            case '#':
            case '@': {
                __autoreleasing id v = [params[i-2] toObject];
                if ([v isKindOfClass:[NSArray class]]
                    && [v count] == 2
                    && [v[0] isKindOfClass:[NSString class]]
                    && [params[i-2][1] isInstanceOf:[JSContext currentContext][@"Function"] ]) {

                    //簽名字符串不能放在棧上症见,否則很可能被覆蓋掉
                    NSString *signString = v[0];
                    char *blockSgin = JOCopySgin(signString);
                    id jsBlocks = params[i-2][1];
/*  原生方法需要的參數(shù)是block,js傳過(guò)來(lái)的參數(shù)是function殃饿,這時(shí)需要構(gòu)造一個(gè)block(無(wú)參數(shù)無(wú)返回)谋作,強(qiáng)制修改這個(gè)block的簽名。然后在block回調(diào)的時(shí)候才能根據(jù)簽名解析出參數(shù)(Method或者說(shuō)selector的簽名在定義的時(shí)候乎芳,就由編譯器搞定了遵蚜,protocol也會(huì)生成簽名,但如果僅僅是在類(lèi)中聲明而不實(shí)現(xiàn)是沒(méi)有簽名的奈惑,而block的簽名則是在定義的時(shí)候才有谬晕,僅聲明也沒(méi)有,js沒(méi)有類(lèi)型信息携取,所以這里需要在js傳funtion的時(shí)候手動(dòng)簽名)攒钳。JOGlobalParamsResolver只能根據(jù)第一個(gè)參數(shù)也就是block本身,如果不拷貝雷滋,則每次都是相同的block不撑,無(wú)法獲取正確的簽名文兢,如果強(qiáng)制每次修改簽名,那么在異步執(zhí)行的情況下焕檬,簽名會(huì)沖突姆坚,所以需要拷貝整個(gè)block,并重新簽名实愚。
*/
                    v = ^() {
                        JOGlobalBlockGetParams();
                        //從x0中獲取已經(jīng)解析出的參數(shù)列表兼呵,是個(gè)NSArray,其中第一個(gè)參數(shù)是Block本身是隱藏參數(shù)
                        asm volatile("mov x1, x0");
                        uintptr_t *array = NULL;
                        void **arrayptr = (void **)&array;
                        asm volatile("ldr x0, %0": "=m"(arrayptr));
                        asm volatile("str x1, [x0]");
                        __autoreleasing NSArray *arr = (__bridge NSArray *)(void *)(*arrayptr);
                        __autoreleasing JSValue *ret = [jsBlocks callWithArguments:[arr subarrayWithRange:(NSRange){1, arr.count - 1}]];
                        char returnType = blockSgin[0];
                        //構(gòu)建返回值和方法的返回值構(gòu)建是一樣的注意使用__autoreleasing變量以防止x0被破壞
                        JOConstructReturnValue(ret, returnType);//此句最好在方法的最末
                    };
                    v = (__bridge id)JOCopyBlock(v, blockSgin);

                    //修改block簽名腊敲,JOGlobalParamsResolver解析參數(shù)時(shí)會(huì)獲取該簽名
//                        asm volatile("ldr    x0, %0" : "=m"(v));
//                        asm volatile("ldr    x8, [x0, 0x18]");
//                        asm volatile("add    x1, x8, 0x10");
//                        asm volatile("add    x2, x8, 0x20");
//                        asm volatile("ldr    w3, [x0, 0x8]");
//                        asm volatile("tst    w3, #0x2000000");
//                        asm volatile("csel   x2, x1, x2, eq");
//                        asm volatile("ldr    x0, %0": "=m"(blockSgin));
//                        asm volatile("str    x0, [x2]" );
                }
                if ([v isKindOfClass:JOSelObj.class]) {
                    SEL s = [v sel];
                    [invoke setArgument:&s atIndex:i]; break;
                } else if ([v isKindOfClass:JOObj.class]) {
                    v = [v obj];
                    [invoke setArgument:&v atIndex:i]; break;
                }  else if ([v isKindOfClass:JOPointerObj.class]) {
                    void *p = [v ptr];
                    [invoke setArgument:&p atIndex:i]; break;
                } else if ([v isKindOfClass:JOWeakObj.class]) {
                    v = [v obj];
                    [invoke setArgument:&v atIndex:i]; break;
                } else if ([v isKindOfClass:[NSNull class]]) {
                    v = nil;
                    [invoke setArgument:&v atIndex:i];
                } else {
                    [invoke setArgument:&v atIndex:i];
                }
                break;
            }
            case '{': {
                if (!strcmp(type, @encode(CGRect))) {
                    CGRect v = [params[i-2] toRect];
                    [invoke setArgument:&v atIndex:i];
                } else if (!strcmp(type, @encode(CGPoint))) {
                    CGPoint v = [params[i-2] toPoint];
                    [invoke setArgument:&v atIndex:i];
                } else if (!strcmp(type, @encode(CGSize))) {
                    CGSize v = [params[i-2] toSize];
                    [invoke setArgument:&v atIndex:i];
                } else if (!strcmp(type, @encode(NSRange))) {
                    NSRange v = [params[i-2] toRange];
                    [invoke setArgument:&v atIndex:i];
                }
                //MARK:解析結(jié)構(gòu)體
                break;
            }
            case '^':
            case '*': {
                __autoreleasing id v = [params[i-2] toObject];
                void *p = NULL;
                if ([v isKindOfClass:[JOPointerObj class]]) {
                    p = [v ptr];
                }
                [invoke setArgument:&p atIndex:i];
                break;
            }
            case ':': {
                __autoreleasing id v = [params[i-2] toObject];
                void *p = NULL;
                if ([v isKindOfClass:[JOSelObj class]]) {
                    p = [v sel];
                }
                [invoke setArgument:&p atIndex:i];
                break;
            }
        }
    }

    [invoke invoke];

    const char *type = [sign methodReturnType];
    while (*type == 'r' || // const
           *type == 'n' || // in
           *type == 'N' || // inout
           *type == 'o' || // out
           *type == 'O' || // bycopy
           *type == 'R' || // byref
           *type == 'V') { // oneway
        type++; // cut off useless prefix
    }

    switch (type[0]) {
        case 'v': return [NSNull null];
        case 'B': {BOOL value; [invoke getReturnValue:&value]; return @(value);}
        case 'c': {char value; [invoke getReturnValue:&value]; return @(value);}
        case 'C': {Byte value; [invoke getReturnValue:&value]; return @(value);}
        case 's': {short value; [invoke getReturnValue:&value]; return @(value);}
        case 'S': {unsigned short value; [invoke getReturnValue:&value]; return @(value);}
        case 'i': {int value; [invoke getReturnValue:&value]; return @(value);}
        case 'I': {unsigned int value; [invoke getReturnValue:&value]; return @(value);}
        case 'l': {long value; [invoke getReturnValue:&value]; return @(value);}
        case 'L': {unsigned long value; [invoke getReturnValue:&value]; return @(value);}
        case 'q': {long long value; [invoke getReturnValue:&value];
            return @(value);}
        case 'Q': {unsigned long long value; [invoke getReturnValue:&value];
            return @(value);}
        case 'f': {float value; [invoke getReturnValue:&value]; return @(value);}
        case 'd': {double value; [invoke getReturnValue:&value]; return @(value);}
        case '#': {
            id retrunValue = nil;
            [invoke getReturnValue:&retrunValue];
            return MakeObj(retrunValue) ?: [NSNull null] ;

        }
        case '@': {
            __unsafe_unretained id retrunValue = nil;
            [invoke getReturnValue:&retrunValue];
            JOObj *o = MakeObj(retrunValue);
            if (sel == @selector(alloc) || sel == @selector(new)
                || sel == @selector(copy) || sel == @selector(mutableCopy)
                || (!strncmp(sel_getName(sel), "init", 4) && invoke.target != retrunValue)) {
                JOTools.release(retrunValue);
            }

            return o ?: [NSNull null] ;
        }
        case '^':
        case '*': {
            void *retrunValue = NULL;
            [invoke getReturnValue:&retrunValue];
            return MakePointerObj(retrunValue) ?: [NSNull null] ;
        }
        case ':': {
            void *retrunValue = NULL;
            [invoke getReturnValue:&retrunValue];
            return MakeSelObj(retrunValue) ?: [NSNull null];
        }
        case '{': {
            if (!strcmp(type, @encode(CGRect))) {
                CGRect v; [invoke getReturnValue:&v];
                return [JSValue valueWithRect:v inContext:[JSContext currentContext]];
            } else if (!strcmp(type, @encode(CGPoint))) {
                CGPoint v; [invoke getReturnValue:&v];
                return [JSValue valueWithPoint:v inContext:[JSContext currentContext]];
            } else if (!strcmp(type, @encode(CGSize))) {
                CGSize v; [invoke getReturnValue:&v];
                return [JSValue valueWithSize:v inContext:[JSContext currentContext]];
            } else if (!strcmp(type, @encode(NSRange))) {
                NSRange v; [invoke getReturnValue:&v];
                return [JSValue valueWithRange:v inContext:[JSContext currentContext]];
            }
            //MARK:更完善的結(jié)構(gòu)體解析以后再想辦法
        }
        default : break;
    }

    return [NSNull null];
}, JSPropertyDescriptorConfigurableKey:@(NO), JSPropertyDescriptorEnumerableKey:@(NO)}];

在這里通過(guò)[JSContext currentThis]獲取this击喂,就是打包OC中的self,解包得到得到真正的self對(duì)象碰辅,然后將SEL解析出來(lái)懂昂,通過(guò)創(chuàng)建NSMethodSignature和NSInvocation來(lái)調(diào)用。這里有個(gè)優(yōu)化點(diǎn)没宾,_oc_調(diào)用很頻繁凌彬,可以緩存這兩個(gè)對(duì)象,因?yàn)閠ype字符串確定后這兩個(gè)對(duì)象也就確定了循衰,緩存可以有一些性能提升铲敛,之前給忘了。當(dāng)然也可以通過(guò)method_getArgumentType獲取參數(shù)簽名会钝。

使用NSInvocation轉(zhuǎn)發(fā)消息伐蒋,能夠比較好的處理參數(shù)和返回值。這兩個(gè)switch我一直想簡(jiǎn)化顽素,想來(lái)想去除了宏似乎也沒(méi)有別的法子咽弦,只是目前為了方便閱讀和調(diào)試徒蟆,暫不用宏簡(jiǎn)化代碼胁出。用了NSInvocation,常規(guī)參數(shù)處理段审,需要說(shuō)明一下的是基礎(chǔ)數(shù)據(jù)類(lèi)型會(huì)被封裝成NSNumber(不打包成JOObj對(duì)象)全蝶,其和js的Number對(duì)應(yīng),可以直接被js使用寺枉。其他也就沒(méi)有太多需要介紹的了抑淫,JSC有數(shù)據(jù)轉(zhuǎn)換方法,根據(jù)簽名轉(zhuǎn)就可以了姥闪。

block參數(shù)處理

重點(diǎn)說(shuō)明Block參數(shù)始苇,例如:當(dāng)JS調(diào)用Native的enumerateObjectsUsingBlock:方法,需要傳遞傳遞一個(gè)Function筐喳,而這個(gè)Function明顯不能被Native直接調(diào)用催式,一種樸素的想法就是給enumerateObjectsUsingBlock傳一個(gè)Block函喉,再在這個(gè)Block中調(diào)用JS Function。呵呵荣月,想法是沒(méi)錯(cuò)的管呵,但是實(shí)現(xiàn)起來(lái)確實(shí)很麻煩。

因?yàn)锽lock參數(shù)的多樣性哺窄,這里定義一種的通用Block似乎不夠用捐下?如果根據(jù)參數(shù)和返回值預(yù)先定義多個(gè)Block,可參數(shù)種類(lèi)有十幾種萌业,排列后就需要定義幾十上百個(gè)Block坷襟,??,就算整型類(lèi)型合并咽白,只保留id啤握,double,NSInteger晶框,指針還是要定義很多個(gè)排抬,想想還是算了吧。定義一種Block真的不夠么授段?有了JOGlobalSwizzle成功思路蹲蒲,似乎也不是不可行。Block本質(zhì)上也是OC對(duì)象侵贵,方法也是C語(yǔ)法届搁,符合ARM64 PCSAA調(diào)用協(xié)議,那么解析參數(shù)就和OC完全一致窍育,唯一不一樣的是簽名卡睦,不過(guò)只要有簽名就可以解析參數(shù)。但有一個(gè)問(wèn)題是Block對(duì)象在定義的時(shí)候編譯器才會(huì)生成漱抓,如果僅僅是聲明是沒(méi)有的表锻,沒(méi)有Block對(duì)象也就沒(méi)有簽名,而Method的也類(lèi)似乞娄,不過(guò)協(xié)議也會(huì)生成Method瞬逊,但沒(méi)有實(shí)現(xiàn)。因此在JS傳入Function的時(shí)候需要給出簽名仪或,OK确镊,思路有了,下面來(lái)看具體實(shí)現(xiàn)范删。

說(shuō)一下這里的原理蕾域,block的簽名實(shí)際是其包裝函數(shù)指針的簽名,捕獲的參數(shù)跟簽名沒(méi)有關(guān)系到旦,僅需要將簽名copy給Block就行(如果每次都是串行調(diào)用這個(gè)通用Block旨巷,實(shí)際上不需要copy整個(gè)Block廓块,只需要強(qiáng)制修改Block的簽名就行了,也就是說(shuō)block實(shí)際上就一個(gè)契沫,只是每次調(diào)用傳參時(shí)修改簽名带猴,回調(diào)是串行,所以簽名也就不存在覆蓋的情況懈万,這也是我最開(kāi)始的做法)拴清。但異步調(diào)用這種非串行的方式,就不能簡(jiǎn)單的修改簽名了会通,需要將對(duì)應(yīng)簽名Block與JS函數(shù)綁定在一起口予,因此需要將block拷貝一份,然后手動(dòng)簽名涕侈。

這里需要先定義一下對(duì)應(yīng)的Block的數(shù)據(jù)結(jié)構(gòu)沪停,這是一個(gè)具體的Block結(jié)構(gòu),帶兩個(gè)捕獲參數(shù)裳涛。

通用的block定義如下:

typedef struct _JOBlockDescriptor {
    void *reserved;
    unsigned long int size;
    void (*copy)(void *dst, const void *src);
    void *dispose;
    const char *signature;//目前使size木张,copy,signatrue字段端三,其他占位即可
    void *layout;
} _JOBlockDescriptor;

typedef struct _JOBlock {
    Class isa;
    short int retainCount;
    short int flag;
    int token;
    void *BlockFunctionPtr;
    _JOBlockDescriptor *descriptor;//目前僅用retainCount舷礼,descriptor,其他占位即可
    
    //捕獲的參數(shù)
    JSValue *jsFunction;
    char *sign;
} _JOBlock;

JOCopySgin會(huì)將簽名拷貝到堆上郊闯,同時(shí)緩存到字典妻献,同一個(gè)簽名就不用拷貝了。

/* 拷貝一個(gè)簽名到堆上并返回 */
char *JOCopySgin(NSString *sign) {
    [_JOBlockLock lock];
    JOPointerObj *obj = _JOBlockSigns[sign];
    [_JOBlockLock unlock];
    
    if (obj) return [obj ptr];
    
    const char *type = [sign UTF8String];
    void *blockSgin = malloc(strlen(type) + 1);
    memcpy(blockSgin, type, strlen(type) + 1);
    
    [_JOBlockLock lock];
    _JOBlockSigns[sign] = MakePointerObj(blockSgin);
    [_JOBlockLock unlock];
    
    return blockSgin;
}

新分配一個(gè)block和descriptor空間团赁,拷貝descriptor和signature育拨。

/*
 關(guān)注JOBridge中處理JS傳過(guò)來(lái)的block(js function)參數(shù),其都被一個(gè)無(wú)參數(shù)無(wú)返回值的block處理欢摄,這是一個(gè)原型block熬丧,其原始簽名是固定的,但該block會(huì)調(diào)用各種不同的js function剧浸,也就意味著要處理不同的參數(shù)和返回值锹引。如果串行調(diào)用矗钟,每次根據(jù)傳入js function的簽名修改即可唆香,但如果存在異步操作,就會(huì)出問(wèn)題吨艇,所以簽名必須和js function對(duì)應(yīng)躬它,而實(shí)際上我這里只處理參數(shù),所以只需要關(guān)注簽名是否一致东涡,不需要關(guān)注捕獲參數(shù)冯吓。
 為了確保在執(zhí)行過(guò)程中JOSwizzle的JOGlobalParamsResolver取到正確的簽名倘待,需要將block拷貝一份,調(diào)用copy或者_(dá)Block_copy都不行组贺,其檢查flags時(shí)跳過(guò)了拷貝凸舵。所以這里只能構(gòu)造相同的結(jié)構(gòu)體來(lái)手動(dòng)拷貝,_JOBlock與原型block對(duì)應(yīng)失尖,拷貝block啊奄,descriptor和sign。最后將修改descriptor和sign掀潮。
 */
id JOCopyBlock(id block, _JOBlock *blockCopy, _JOBlockDescriptor *blockDescriptor, char *blockSgin) {
    //_Block_copy;
    
    _JOBlock *blockPtr = (__bridge void *)block;
    _JOBlockDescriptor *blockDescriptor = malloc(sizeof(_JOBlockDescriptor));
    _JOBlock *blockCopy = malloc(sizeof(_JOBlock));
    memcpy((void*)blockCopy, (void*)blockPtr, sizeof(_JOBlock));
    memcpy(blockDescriptor, blockPtr->descriptor, sizeof(_JOBlockDescriptor));
    
//    blockDescriptor->copy = (void *)_JOCopyHelper;
    blockDescriptor->copy(blockCopy, blockPtr);
    blockDescriptor->dispose = (void *)_JODisposeHelper;
    blockCopy->retainCount = 0x0;
    blockCopy->descriptor = blockDescriptor;
    blockCopy->descriptor->signature = blockSgin;

    return blockCopy;
}

手動(dòng)調(diào)用block的copy函數(shù)菇夸,拷貝一下捕獲的參數(shù),說(shuō)是copy其實(shí)是retain一下jsFunction仪吧。這里的copy實(shí)現(xiàn)可以不用替換庄新。

替換dispose的實(shí)現(xiàn)代碼,主要是為了釋放descriptor薯鼠,同時(shí)手動(dòng)release一下jsFunction择诈。

void _JODisposeHelper(_JOBlock *src) {
    JOTools.release(src->jsFunction);
    free(src->descriptor);
}

將retainCount設(shè)置為0,v = (__bridge id)JOCopyBlock(v, blockSgin);賦值時(shí)候出皇,會(huì)自動(dòng)將其+1吭从,就懶得前前后后的調(diào)用retain和release。ARM64位isa定義中恶迈,后19bit為引用計(jì)數(shù)器涩金,但block似乎不太一樣,引用計(jì)數(shù)器不在isa中暇仲,而在下一個(gè)4Byte步做,可能占用了32bit,但也可能不是奈附,所有我這里定義retainCount為short int全度,16bit,一般也足夠用了斥滤。

目前block是手動(dòng)完成copy的将鸵,其實(shí)挺麻煩的,研究_Block_copy匯編碼時(shí)佑颇,發(fā)現(xiàn)其拷貝前檢查了一個(gè)flag顶掉,然后跳過(guò)了拷貝,目前這個(gè)flag情況不明所以我沒(méi)有通過(guò)強(qiáng)行修改flag的方式去讓_Block_copy生效挑胸,如果研究清楚了這個(gè)flag或許可以調(diào)用該方法copy了痒筒。

返回值內(nèi)存管理

其他的情況沒(méi)有什么特別的(更完善的結(jié)構(gòu)體解析暫不實(shí)現(xiàn)),這里需要說(shuō)明一下這種對(duì)象返回值的情況。

case '@': {
    __unsafe_unretained id retrunValue = nil;
    [invoke getReturnValue:&retrunValue];
    JOObj *o = MakeObj(retrunValue);
    if (sel == @selector(alloc) || sel == @selector(new)
        || sel == @selector(copy) || sel == @selector(mutableCopy)
        || (!strncmp(sel_getName(sel), "init", 4) && invoke.target != retrunValue)) {
        JOTools.release(retrunValue);
    }

    return o ?: [NSNull null] ;
}

通過(guò)getReturnValue獲取到返回值retrunValue簿透,然后將其封裝到JOObj中移袍。

對(duì)于alloc,new老充,copy葡盗,mutableCopy需要調(diào)用一次release。為什么呢啡浊?因?yàn)檫@幾種調(diào)用返回的是非autoreleasing對(duì)象戳粒,其retainCount默認(rèn)為1,而且與外部聲明的獲取指針__unsafe_unretained id retrunValue無(wú)關(guān)虫啥,但是返回JOObj給JS持有也會(huì)導(dǎo)致其retainCount+1蔚约,也就是說(shuō)調(diào)用一次alloc導(dǎo)致retainCount+2了,那就需要release一次涂籽。使用MakeObj封裝一次后再調(diào)用release可以將retrunValue轉(zhuǎn)換成autoreleasing對(duì)象苹祟,同時(shí)返回給JS后交給js管理。

(!strncmp(sel_getName(sel), "init", 4) && invoke.target != retrunValue)又是另外一種情況评雌,當(dāng)通過(guò)alloc和init創(chuàng)建類(lèi)簇對(duì)象的時(shí)候树枫,比如[[NSMutableArray alloc] init],alloc和init調(diào)用完后返回的不是同一個(gè)對(duì)象景东,alloc返回的是一個(gè)__NSPlaceholderArray占位對(duì)象砂轻,調(diào)用init時(shí),這個(gè)對(duì)象會(huì)被銷(xiāo)毀再新建一個(gè)對(duì)應(yīng)的對(duì)象返回斤吐。所以判斷條件為sel為init打頭時(shí)搔涝,并且self不等于返回值也release一下。

iOS的內(nèi)存管理說(shuō)簡(jiǎn)單也簡(jiǎn)單和措,說(shuō)復(fù)雜還是很復(fù)雜的庄呈,比如在這里JS,OC派阱,匯編诬留,手動(dòng)的MRC,編譯器的ARC贫母,Block拷貝的內(nèi)存管理(retain和release捕獲變量文兑,block自身的內(nèi)存管理),這些問(wèn)題糅合在一起腺劣,就會(huì)讓內(nèi)存管理復(fù)雜很多绿贞,需要一點(diǎn)一點(diǎn)梳理才能解決,如果對(duì)內(nèi)存管理理解不深刻的話(huà)誓酒,很容易出現(xiàn)各種問(wèn)題樟蠕,而且不知道問(wèn)題出在什么地方,怎么解決靠柑,有興趣的話(huà)可以去看我之前的ARC和block的相關(guān)博客寨辩,對(duì)很多問(wèn)題都有詳細(xì)描述和分析。

NULL處理

JSC頭文件有說(shuō)明歼冰,js的null和OC的[NSNull null]是可以等價(jià)轉(zhuǎn)換的靡狞,所以遇到nil可以轉(zhuǎn)成[NSNull null]返給JS。眾所周知OC中對(duì)nil調(diào)用方法是合法的隔嫡,因?yàn)檎{(diào)用都會(huì)被轉(zhuǎn)成objc_msgSend的形式甸怕,但是js的對(duì)null調(diào)用方法就會(huì)報(bào)錯(cuò)。所以對(duì)于調(diào)用鏈JOC.class('ZWConfigure').sharedInstance().loginUser().userID(),如果中間的sharedInstance或者loginUser返回了null腮恩,js就會(huì)報(bào)錯(cuò)梢杭,但如果每次都需要判斷是否為null再調(diào)用會(huì)有些麻煩。

一種解決方案是返回一個(gè)空的JOObj(單獨(dú)創(chuàng)建一個(gè)單例也行)秸滴,遇到nil要傳給js時(shí)就使用這個(gè)空的JOObj武契,但這樣的話(huà)js就不能只檢查null還得檢查是否為空的JOObj,但這需要單獨(dú)的語(yǔ)法支持荡含,比如if(JOC.class('ZWConfigure').sharedInstance().loginUser().userID() != JOObjNil) 或者if(JOC.class('ZWConfigure').sharedInstance().loginUser().userID().isNil())咒唆。

但是如果是浮點(diǎn)判斷怎么處理,比如if(JOC.class('ZWConfigure').sharedInstance().loginUser().userAccountMoney() > 0)释液。

目前我還沒(méi)有比較好的辦法來(lái)解決這個(gè)問(wèn)題全释,所以暫時(shí)我還是使用[NSNull null]來(lái)作為nil返回值,JS就麻煩點(diǎn)误债,調(diào)用前先判斷是否為null再調(diào)用浸船。

最后

雖然實(shí)現(xiàn)了功能,也作了一些優(yōu)化寝蹈,但依舊不少可以改進(jìn)和優(yōu)化的地方糟袁,大概列了幾個(gè):

1、NSMethodSignature和NSInvocation的復(fù)用躺盛。

2项戴、傳遞給JS封裝對(duì)象的復(fù)用,特別是后兩者槽惫。JS代碼只是作為一個(gè)橋周叮, 實(shí)現(xiàn)功能全靠JS調(diào)用的Native,因此需要頻繁調(diào)用Native方法界斜,每次調(diào)用會(huì)創(chuàng)建好幾個(gè)臨時(shí)對(duì)象仿耽,而緩存復(fù)用就成了提高性能的最有效的方式。

3各薇、Block不使用拷貝的方式處理簽名项贺,目前js傳遞一個(gè)function給原生調(diào)用時(shí)君躺,會(huì)被參數(shù)解析函數(shù)封裝成一個(gè)特殊的block,在block中再調(diào)用該function开缎,但是給block解析參數(shù)需要簽名棕叫。目前的做法是寫(xiě)一個(gè)手動(dòng)拷貝block的函數(shù)將block拷貝一份,同時(shí)強(qiáng)制替換其簽名奕删,這樣做的好處是該特殊block可以像普通的block一樣調(diào)用俺泣,但實(shí)際上可以在block中捕獲簽名作為成員變量,這樣可以避免拷貝block完残,但是調(diào)用的時(shí)候需要和普通的block區(qū)別對(duì)待伏钠。

目來(lái)還有不少語(yǔ)法功能未支持,目前以下這些語(yǔ)法都在醞釀中(其實(shí)基本已經(jīng)實(shí)現(xiàn)谨设,只是還需要調(diào)試)熟掂,當(dāng)然還有其他一些細(xì)節(jié)功能

1、調(diào)用父類(lèi)方法扎拣,比如重寫(xiě)init方法打掘,必然要調(diào)用父類(lèi)init。

2鹏秋、再比如最開(kāi)始說(shuō)的變長(zhǎng)參數(shù)中的匿名參數(shù)尊蚁,最常用的就是stringWithFormat。

3侣夷、不通過(guò)setter和getter横朋,獲取和修改對(duì)象的成員變量,比如懶加載重寫(xiě)getter的時(shí)候百拓。

4琴锭、再比如Masonry的鏈?zhǔn)秸Z(yǔ)法的支持(我自己也有一個(gè)UI創(chuàng)建的鏈?zhǔn)秸Z(yǔ)法庫(kù),主要是為了支持它衙传,??????)决帖。

最后給一個(gè)ARM64程序調(diào)用標(biāo)準(zhǔn)的鏈接:Procedure Call Standard for the ARM 64-bit Architecture

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蓖捶,隨后出現(xiàn)的幾起案子地回,更是在濱河造成了極大的恐慌,老刑警劉巖俊鱼,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刻像,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡并闲,警方通過(guò)查閱死者的電腦和手機(jī)细睡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)帝火,“玉大人溜徙,你說(shuō)我怎么就攤上這事湃缎。” “怎么了蠢壹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵嗓违,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我知残,道長(zhǎng)靠瞎,這世上最難降的妖魔是什么比庄? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任求妹,我火速辦了婚禮,結(jié)果婚禮上佳窑,老公的妹妹穿的比我還像新娘制恍。我一直安慰自己,他們只是感情好神凑,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布净神。 她就那樣靜靜地躺著,像睡著了一般溉委。 火紅的嫁衣襯著肌膚如雪鹃唯。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天瓣喊,我揣著相機(jī)與錄音坡慌,去河邊找鬼。 笑死藻三,一個(gè)胖子當(dāng)著我的面吹牛洪橘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棵帽,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼熄求,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了逗概?” 一聲冷哼從身側(cè)響起弟晚,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎逾苫,沒(méi)想到半個(gè)月后指巡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡隶垮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年藻雪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狸吞。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡勉耀,死狀恐怖指煎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情便斥,我是刑警寧澤至壤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站枢纠,受9級(jí)特大地震影響像街,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晋渺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一镰绎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧木西,春花似錦畴栖、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至恋捆,卻和暖如春照皆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沸停。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工膜毁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人星立。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓爽茴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親绰垂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子室奏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類(lèi)型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,089評(píng)論 1 32
  • 1.設(shè)計(jì)模式是什么胧沫? 你知道哪些設(shè)計(jì)模式,并簡(jiǎn)要敘述占业?設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn)绒怨,就是用比較成熟的邏輯去處理某一種類(lèi)型...
    龍飝閱讀 2,138評(píng)論 0 12
  • 1.項(xiàng)目經(jīng)驗(yàn) 2.基礎(chǔ)問(wèn)題 3.指南認(rèn)識(shí) 4.解決思路 ios開(kāi)發(fā)三大塊: 1.Oc基礎(chǔ) 2.CocoaTouch...
    陽(yáng)光的大男孩兒閱讀 4,969評(píng)論 0 13
  • 1.OC里用到集合類(lèi)是什么? 基本類(lèi)型為:NSArray谦疾,NSSet以及NSDictionary 可變類(lèi)型為:NS...
    輕皺眉頭淺憂(yōu)思閱讀 1,363評(píng)論 0 3
  • 身在千山頂上頭南蹂,突巖深縫妙香稠。 非無(wú)腳下浮云鬧念恍,來(lái)不相知去不留六剥。 墨蘭幽香濃晚顷,惹蝶尋芳蹤。...
    周墨_de19閱讀 323評(píng)論 0 0