簡(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