日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

JSPatch实现原理一览

發布時間:2023/12/29 javascript 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JSPatch实现原理一览 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

JSPatch是一個非常棒的熱修復框架,10000+star!!!!!雖說2017年被蘋果封殺了,但是據我獲取到的有限的信息,大家還是偷偷摸摸混淆一下、改改類名繼續在使用。畢竟bug還是不可避免的,有了JSPatch萬一出了問題我們還是能夠搶救一下的。一些實現細節bang哥其實已經做了一些介紹了,但是看了之后還是不能對完整的流程有個大致的印象,并且其中肯定還是有很多東西值得我們去深挖學習的。

JSPatch簡介

JSPatch 是一個 iOS 動態更新框架,只需在項目中引入極小的引擎,就可以使用 JavaScript 調用任何 Objective-C 原生接口,獲得腳本語言的優勢:為項目動態添加模塊,或替換項目原生代碼動態修復 bug。

大致需要了解的

  • JavaScript - 還是要稍微會一點點js的,我就是那種菜的摳腳的水平,所以js部分看的有點吃力。不懂的地方都是靠調試js代碼打斷點,根據結果反向推來搞懂的。
  • JSPatch里JavaScript的寫法。還是那句話,用過之后會有一些問題,帶著問題去讀源碼更有針對性,效果更好。
  • JavaScriptCore - js和oc內部交互用的是JavaScriptCore,推薦閱讀JavaScriptCore 整體介紹。
  • Runtime - JSPatch內部是通過Runtime來動態修改添加方法的,涉及到了很多很多的runtime的知識。所以你還是需要了解一些runtime的知識。推薦閱讀Objective-C Runtime。

帶著問題看源碼

國際慣例,拋磚引玉提倆問題。

1. 添加方法的時候,方法的編碼是怎么做處理的

class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) 復制代碼

我知道runtime里添加一個方法里會需要我們傳遞一個方法的編碼。蘋果的運行時庫內部利用類型編碼幫助加快消息分發。方法的編碼主要描述了方法的返回值和參數。比如這樣一個方法-(void)test:(id)arg;最后拿到的編碼是v@:@,具體各個字符對應關系Type Encodings。但是你可能會有個問題這里算上返回值一共就倆個參數,但是這里一共有四個字符。原因是[receiver test:arg]會被編譯器轉化成objc_msgSend(receiver, selector, arg1) 我們拿到的簽名里的參數部分被加上了一個self和一個SEL。

JS里的方法顯然是沒有這樣的編碼的,那么從JS到OC方法的編碼是如何處理的呢。

2. JS function內是怎么調用OC方法的

如果某個JS對象不存在某個方法那么調用是要出錯的。例如UIView.alloc()如果UIView對象沒有alloc這個方法調用是會出問題的。

粗略的看一下流程

簡化了很多細節,demo里的代碼的大致的流程如下。用兩種顏色區分了js端和native端。

從這個流程圖當中我們大致可以了解到,JSPatch最后的方法調用和Aspects庫類似都是走的消息轉發。而且native方法不存在的時候JSPatch并不會去動態生成一個方法。所以當Aspects和JSPatch混用的時候需要特別小心。

回頭看疑問

問題1

關于第一個方法編碼的問題,JSPatch分了幾種情況去處理

  • 協議里的方法
  • native已經實現了的方法
  • 默認的情況

對應的大致的代碼邏輯,可以看到如果是添加了一個之前完全不存在的方法,是需要知道有多少個參數來自己創建一個方法編碼的。所以之前的js里有一步是要生成一個[方法參數個數,方法實現]這樣的數據結構。

// 如果已經實現了這個方法 if (class_respondsToSelector(currCls, NSSelectorFromString(selectorName))) {// overrideMethod方法內部通過下面的邏輯來獲取編碼// Method method = class_getInstanceMethod(cls, selector);// typeDescription = (char *)method_getTypeEncoding(method);overrideMethod(currCls, selectorName, jsMethod, !isInstance, NULL);} else {// 協議方法BOOL overrided = NO;for (NSString *protocolName in protocols) {char *types = methodTypesInProtocol(protocolName, selectorName, isInstance, YES);if (!types) types = methodTypesInProtocol(protocolName, selectorName, isInstance, NO);if (types) {overrideMethod(currCls, selectorName, jsMethod, !isInstance, types);free(types);overrided = YES;break;}}// 默認的情況if (!overrided) {if (![[jsMethodName substringToIndex:1] isEqualToString:@"_"]) {NSMutableString *typeDescStr = [@"@@:" mutableCopy];for (int i = 0; i < numberOfArg; i ++) {[typeDescStr appendString:@"@"];}overrideMethod(currCls, selectorName, jsMethod, !isInstance, [typeDescStr cStringUsingEncoding:NSUTF8StringEncoding]);}}} 復制代碼

問題2

關于這個問題bang哥已經有一些介紹,通過正則,類似UIView.alloc().init()的調用被替換成UIView.__c('alloc')().__c('init')()形式。走統一的__c()元函數,傳遞進方法名拿到一個方法,再傳遞進參數調用方法。

實現細節

JSPatch細節有點多。我這邊只挑一些主要的看,默認你已經大致看過源碼。

從demo的執行入口開始

[JPEngine startEngine];NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];[JPEngine evaluateScript:script]; 復制代碼

startEngine相當于初始化運行環境,它的內部通過JavaScriptCore定義了很多JS的方法,給外部的js來和native交互。

+ (void)startEngine { . . .context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {return defineClass(classDeclaration, instanceMethods, classMethods);};context[@"_OC_defineProtocol"] = ^(NSString *protocolDeclaration, JSValue *instProtocol, JSValue *clsProtocol) {return defineProtocol(protocolDeclaration, instProtocol,clsProtocol);};context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) {return callSelector(nil, selectorName, arguments, obj, isSuper);};context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) {return callSelector(className, selectorName, arguments, nil, NO);};context[@"_OC_formatJSToOC"] = ^id(JSValue *obj) {return formatJSToOC(obj);};context[@"_OC_formatOCToJS"] = ^id(JSValue *obj) {return formatOCToJS([obj toObject]);};... 復制代碼

js可以直接調用這些方法并且傳遞參數,傳遞的參數在這里會自動轉換。具體的轉換對應關系。

Objective-C type | JavaScript type--------------------+---------------------nil | undefinedNSNull | nullNSString | stringNSNumber | number, booleanNSDictionary | Object objectNSArray | Array objectNSDate | Date objectNSBlock (1) | Function object (1)id (2) | Wrapper object (2)Class (3) | Constructor object (3) 復制代碼

上面是環境的初始化,初始化完就是js腳本的調用。[JPEngine evaluateScript:script]; evaluateScript方法的主要作用就是把原始的js方法里方法調用正則修改成__c函數調用。也就是上面問題2的描述。

+ (JSValue *)_evaluateScript:(NSString *)script withSourceURL:(NSURL *)resourceURL {if (!script || ![JSContext class]) {_exceptionBlock(@"script is nil");return nil;}[self startEngine];if (!_regex) {_regex = [NSRegularExpression regularExpressionWithPattern:_regexStr options:0 error:nil];}NSString *formatedScript = [NSString stringWithFormat:@";(function(){try{\n%@\n}catch(e){_OC_catch(e.message, e.stack)}})();", [_regex stringByReplacingMatchesInString:script options:0 range:NSMakeRange(0, script.length) withTemplate:_replaceStr]];@try {if ([_context respondsToSelector:@selector(evaluateScript:withSourceURL:)]) {return [_context evaluateScript:formatedScript withSourceURL:resourceURL];} else {return [_context evaluateScript:formatedScript];}}@catch (NSException *exception) {_exceptionBlock([NSString stringWithFormat:@"%@", exception]);}return nil; } 復制代碼

接下來就是JavaScriptCore執行js腳本了。

defineClass('JPViewController', {viewDidAppear:function(animate) {console.log('lol');},handleBtn: function(sender) {var tableViewCtrl = JPTableViewController.alloc().init()self.navigationController().pushViewController_animated(tableViewCtrl, YES)} }) 復制代碼global.defineClass = function(declaration, properties, instMethods, clsMethods) {var newInstMethods = {}, newClsMethods = {}if (!(properties instanceof Array)) {clsMethods = instMethodsinstMethods = propertiesproperties = null}// 如果有屬性的情況下,需要去判斷有沒有實現set get方法,如果實現了就要加到實例方法列表里面去if (properties) {properties.forEach(function(name){if (!instMethods[name]) {instMethods[name] = _propertiesGetFun(name);}var nameOfSet = "set"+ name.substr(0,1).toUpperCase() + name.substr(1);if (!instMethods[nameOfSet]) {instMethods[nameOfSet] = _propertiesSetFun(name);}});}// 解析出類名var realClsName = declaration.split(':')[0].trim()_formatDefineMethods(instMethods, newInstMethods, realClsName)_formatDefineMethods(clsMethods, newClsMethods, realClsName)var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods)var className = ret['cls']var superCls = ret['superCls']_ocCls[className] = {instMethods: {},clsMethods: {},}if (superCls.length && _ocCls[superCls]) {for (var funcName in _ocCls[superCls]['instMethods']) {_ocCls[className]['instMethods'][funcName] = _ocCls[superCls]['instMethods'][funcName]}for (var funcName in _ocCls[superCls]['clsMethods']) {_ocCls[className]['clsMethods'][funcName] = _ocCls[superCls]['clsMethods'][funcName]}}_setupJSMethod(className, instMethods, 1, realClsName)_setupJSMethod(className, clsMethods, 0, realClsName)return require(className)} 復制代碼

我們看到defineClass方法內部做了一些解析處理,并且調用了我們之前注冊的_OC_defineClass方法,傳遞進去了類名、實例方法、類方法。OC部分的defineClass方法如下

static NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {NSScanner *scanner = [NSScanner scannerWithString:classDeclaration];NSString *className;NSString *superClassName;NSString *protocolNames;// xxObject : xxSuperObject <xxProtocol>// 解析類[scanner scanUpToString:@":" intoString:&className];if (!scanner.isAtEnd) {// 解析父類scanner.scanLocation = scanner.scanLocation + 1;[scanner scanUpToString:@"<" intoString:&superClassName];// 解析協議if (!scanner.isAtEnd) {scanner.scanLocation = scanner.scanLocation + 1;[scanner scanUpToString:@">" intoString:&protocolNames];}}// 沒有父類信息的情況就繼承自NSObjectif (!superClassName) superClassName = @"NSObject";// 刪除前后空格className = trim(className);superClassName = trim(superClassName);// 解析出協議NSArray *protocols = [protocolNames length] ? [protocolNames componentsSeparatedByString:@","] : nil;Class cls = NSClassFromString(className);if (!cls) {// 如果當前的類不存在Class superCls = NSClassFromString(superClassName);if (!superCls) {// 如果父類不存在直接奔潰_exceptionBlock([NSString stringWithFormat:@"can't find the super class %@", superClassName]);return @{@"cls": className};}// 創建這個類cls = objc_allocateClassPair(superCls, className.UTF8String, 0);objc_registerClassPair(cls);}// 給這個類添加協議if (protocols.count > 0) {for (NSString* protocolName in protocols) {Protocol *protocol = objc_getProtocol([trim(protocolName) cStringUsingEncoding:NSUTF8StringEncoding]);class_addProtocol (cls, protocol);}}// 實例方法和類方法處理 1是實例方法 2是類方法for (int i = 0; i < 2; i ++) {BOOL isInstance = i == 0;// 傳遞過來的方法都是 `方法名` : `[方法參數個數,方法實現]` 這樣的形式JSValue *jsMethods = isInstance ? instanceMethods: classMethods;Class currCls = isInstance ? cls: objc_getMetaClass(className.UTF8String);NSDictionary *methodDict = [jsMethods toDictionary];for (NSString *jsMethodName in methodDict.allKeys) {JSValue *jsMethodArr = [jsMethods valueForProperty:jsMethodName];// 參數的個數int numberOfArg = [jsMethodArr[0] toInt32];// JS的方法名字轉換成OC的方法名字NSString *selectorName = convertJPSelectorString(jsMethodName);// 為了js端的寫法好看點,末尾的參數可以不用寫 `_`if ([selectorName componentsSeparatedByString:@":"].count - 1 < numberOfArg) {selectorName = [selectorName stringByAppendingString:@":"];}JSValue *jsMethod = jsMethodArr[1];// 如果已經實現了這個方法if (class_respondsToSelector(currCls, NSSelectorFromString(selectorName))) {// overrideMethod方法內部通過下面的邏輯來獲取編碼// Method method = class_getInstanceMethod(cls, selector);// typeDescription = (char *)method_getTypeEncoding(method);overrideMethod(currCls, selectorName, jsMethod, !isInstance, NULL);} else {// 協議方法BOOL overrided = NO;for (NSString *protocolName in protocols) {char *types = methodTypesInProtocol(protocolName, selectorName, isInstance, YES);if (!types) types = methodTypesInProtocol(protocolName, selectorName, isInstance, NO);if (types) {overrideMethod(currCls, selectorName, jsMethod, !isInstance, types);free(types);overrided = YES;break;}}// 默認的情況if (!overrided) {if (![[jsMethodName substringToIndex:1] isEqualToString:@"_"]) {NSMutableString *typeDescStr = [@"@@:" mutableCopy];for (int i = 0; i < numberOfArg; i ++) {[typeDescStr appendString:@"@"];}overrideMethod(currCls, selectorName, jsMethod, !isInstance, [typeDescStr cStringUsingEncoding:NSUTF8StringEncoding]);}}}}}class_addMethod(cls, @selector(getProp:), (IMP)getPropIMP, "@@:@");class_addMethod(cls, @selector(setProp:forKey:), (IMP)setPropIMP, "v@:@@");return @{@"cls": className, @"superCls": superClassName}; } 復制代碼

我們主要來看一下針對方法部分的操作,可以看到區分不同的情況拿到方法的編碼之后走的都是overrideMethod方法。再來看看這個方法的主要邏輯。

static void overrideMethod(Class cls, NSString *selectorName, JSValue *function, BOOL isClassMethod, const char *typeDescription) {SEL selector = NSSelectorFromString(selectorName);// 拿到方法簽名if (!typeDescription) {Method method = class_getInstanceMethod(cls, selector);typeDescription = (char *)method_getTypeEncoding(method);}// 拿到原始方法的IMP如果方法實現了的話IMP originalImp = class_respondsToSelector(cls, selector) ? class_getMethodImplementation(cls, selector) : NULL;// 拿到消息轉發指針IMP msgForwardIMP = _objc_msgForward;#if !defined(__arm64__)if (typeDescription[0] == '{') {//In some cases that returns struct, we should use the '_stret' API://http://sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html//NSMethodSignature knows the detail but has no API to return, we can only get the info from debugDescription.NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:typeDescription];if ([methodSignature.debugDescription rangeOfString:@"is special struct return? YES"].location != NSNotFound) {msgForwardIMP = (IMP)_objc_msgForward_stret;}}#endif// 替換消息轉發 `forwardInvocation:`的實現if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@");if (originalForwardImp) {class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@");// 存一下原始的IMP}}// invocation return 0 when the return type is double/float.[cls jp_fixMethodSignature];// 如果實現了這個方法,做一下區分生成一個新的 ORIGSelector -> 原始方法的IMP。 為了JS能調用到之前的方法if (class_respondsToSelector(cls, selector)) {NSString *originalSelectorName = [NSString stringWithFormat:@"ORIG%@", selectorName];SEL originalSelector = NSSelectorFromString(originalSelectorName);if(!class_respondsToSelector(cls, originalSelector)) {class_addMethod(cls, originalSelector, originalImp, typeDescription);}}NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];_initJPOverideMethods(cls);_JSOverideMethods[cls][JPSelectorName] = function;// Replace the original selector at last, preventing threading issus when// the selector get called during the execution of `overrideMethod`// 調用方法的時候直接走消息轉發class_replaceMethod(cls, selector, msgForwardIMP, typeDescription); } 復制代碼

這個方法和Aspects里的核心方法很像。主要的操作就是替換forwardInvocation的實現,替換要調用的方法的SEL指向_objc_msgForward。也就是方法調用的時候直接走消息轉發,執行JPForwardInvocation方法。并且也把傳遞過來的JS方法存了一下,以便在JPForwardInvocation方法里調用。JPForwardInvocation的方法實在是有點長,它主要的流程如下圖

代碼如下,有刪除一些內容。

static void JPForwardInvocation(__unsafe_unretained id assignSlf, SEL selector, NSInvocation *invocation) {// 調用棧 #ifdef DEBUG_JSLastCallStack = [NSThread callStackSymbols]; #endifBOOL deallocFlag = NO;id slf = assignSlf;// 方法簽名NSMethodSignature *methodSignature = [invocation methodSignature];// 方法參數NSInteger numberOfArguments = [methodSignature numberOfArguments];// 方法名字NSString *selectorName = NSStringFromSelector(invocation.selector);// JS方法名字NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];// 拿到js的functionJSValue *jsFunc = getJSFunctionInObjectHierachy(slf, JPSelectorName);if (!jsFunc) {// 如果沒有實現JS的方法就調用原始的消息轉發JPExecuteORIGForwardInvocation(slf, selector, invocation);return;}// JPBoxing的作用是防止強制轉換NSMutableArray *argList = [[NSMutableArray alloc] init];if ([slf class] == slf) {// 類方法[argList addObject:[JSValue valueWithObject:@{@"__clsName": NSStringFromClass([slf class])} inContext:_context]];} else if ([selectorName isEqualToString:@"dealloc"]) {// dealloc方法添加一個assign的self對象[argList addObject:[JPBoxing boxAssignObj:slf]];deallocFlag = YES;} else {// 默認的情況添加一個weak的self對象[argList addObject:[JPBoxing boxWeakObj:slf]];}// 方法簽名的 0是self 1是_cmd// 遍歷獲取方法的參數,for (NSUInteger i = 2; i < numberOfArguments; i++) {const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {// 基礎數據類型#define JP_FWD_ARG_CASE(_typeChar, _type) \case _typeChar: { \_type arg; \[invocation getArgument:&arg atIndex:i]; \[argList addObject:@(arg)]; \break; \}JP_FWD_ARG_CASE('c', char)JP_FWD_ARG_CASE('C', unsigned char)JP_FWD_ARG_CASE('s', short)JP_FWD_ARG_CASE('S', unsigned short)JP_FWD_ARG_CASE('i', int)JP_FWD_ARG_CASE('I', unsigned int)JP_FWD_ARG_CASE('l', long)JP_FWD_ARG_CASE('L', unsigned long)JP_FWD_ARG_CASE('q', long long)JP_FWD_ARG_CASE('Q', unsigned long long)JP_FWD_ARG_CASE('f', float)JP_FWD_ARG_CASE('d', double)JP_FWD_ARG_CASE('B', BOOL)// 對象case '@': {__unsafe_unretained id arg;[invocation getArgument:&arg atIndex:i];if ([arg isKindOfClass:NSClassFromString(@"NSBlock")]) {[argList addObject:(arg ? [arg copy]: _nilObj)];} else {[argList addObject:(arg ? arg: _nilObj)];}break;}// structcase '{': {NSString *typeString = extractStructName([NSString stringWithUTF8String:argumentType]);#define JP_FWD_ARG_STRUCT(_type, _transFunc) \if ([typeString rangeOfString:@#_type].location != NSNotFound) { \_type arg; \[invocation getArgument:&arg atIndex:i]; \[argList addObject:[JSValue _transFunc:arg inContext:_context]]; \break; \}JP_FWD_ARG_STRUCT(CGRect, valueWithRect)JP_FWD_ARG_STRUCT(CGPoint, valueWithPoint)JP_FWD_ARG_STRUCT(CGSize, valueWithSize)JP_FWD_ARG_STRUCT(NSRange, valueWithRange)@synchronized (_context) {NSDictionary *structDefine = _registeredStruct[typeString];if (structDefine) {size_t size = sizeOfStructTypes(structDefine[@"types"]);if (size) {void *ret = malloc(size);[invocation getArgument:ret atIndex:i];NSDictionary *dict = getDictOfStruct(ret, structDefine);[argList addObject:[JSValue valueWithObject:dict inContext:_context]];free(ret);break;}}}break;}case ':': {SEL selector;[invocation getArgument:&selector atIndex:i];NSString *selectorName = NSStringFromSelector(selector);[argList addObject:(selectorName ? selectorName: _nilObj)];break;}// 指針case '^':case '*': {void *arg;[invocation getArgument:&arg atIndex:i];[argList addObject:[JPBoxing boxPointer:arg]];break;}// 類case '#': {Class arg;[invocation getArgument:&arg atIndex:i];[argList addObject:[JPBoxing boxClass:arg]];break;}default: {NSLog(@"error type %s", argumentType);break;}}}// 調用父類方法if (_currInvokeSuperClsName[selectorName]) {Class cls = NSClassFromString(_currInvokeSuperClsName[selectorName]);NSString *tmpSelectorName = [[selectorName stringByReplacingOccurrencesOfString:@"_JPSUPER_" withString:@"_JP"] stringByReplacingOccurrencesOfString:@"SUPER_" withString:@"_JP"];if (!_JSOverideMethods[cls][tmpSelectorName]) {NSString *ORIGSelectorName = [selectorName stringByReplacingOccurrencesOfString:@"SUPER_" withString:@"ORIG"];[argList removeObjectAtIndex:0];id retObj = callSelector(_currInvokeSuperClsName[selectorName], ORIGSelectorName, [JSValue valueWithObject:argList inContext:_context], [JSValue valueWithObject:@{@"__obj": slf, @"__realClsName": @""} inContext:_context], NO);id __autoreleasing ret = formatJSToOC([JSValue valueWithObject:retObj inContext:_context]);[invocation setReturnValue:&ret];return;}}// 把oc的參數轉成js的參數NSArray *params = _formatOCToJSList(argList);// 返回數據的encodechar returnType[255];strcpy(returnType, [methodSignature methodReturnType]);// 處理7.1系統下返回double float 0的問題// Restore the return typeif (strcmp(returnType, @encode(JPDouble)) == 0) {strcpy(returnType, @encode(double));}if (strcmp(returnType, @encode(JPFloat)) == 0) {strcpy(returnType, @encode(float));}// 拿到jsFunc 直接調用 `[jsFunc callWithArguments:params]` 然后根據returnType轉成對應的OC數據switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {#define JP_FWD_RET_CALL_JS \JSValue *jsval; \[_JSMethodForwardCallLock lock]; \jsval = [jsFunc callWithArguments:params]; \[_JSMethodForwardCallLock unlock]; \while (![jsval isNull] && ![jsval isUndefined] && [jsval hasProperty:@"__isPerformInOC"]) { \NSArray *args = nil; \JSValue *cb = jsval[@"cb"]; \if ([jsval hasProperty:@"sel"]) { \id callRet = callSelector(![jsval[@"clsName"] isUndefined] ? [jsval[@"clsName"] toString] : nil, [jsval[@"sel"] toString], jsval[@"args"], ![jsval[@"obj"] isUndefined] ? jsval[@"obj"] : nil, NO); \args = @[[_context[@"_formatOCToJS"] callWithArguments:callRet ? @[callRet] : _formatOCToJSList(@[_nilObj])]]; \} \[_JSMethodForwardCallLock lock]; \jsval = [cb callWithArguments:args]; \[_JSMethodForwardCallLock unlock]; \}#define JP_FWD_RET_CASE_RET(_typeChar, _type, _retCode) \case _typeChar : { \JP_FWD_RET_CALL_JS \_retCode \[invocation setReturnValue:&ret];\break; \}#define JP_FWD_RET_CASE(_typeChar, _type, _typeSelector) \JP_FWD_RET_CASE_RET(_typeChar, _type, _type ret = [[jsval toObject] _typeSelector];) \#define JP_FWD_RET_CODE_ID \id __autoreleasing ret = formatJSToOC(jsval); \if (ret == _nilObj || \([ret isKindOfClass:[NSNumber class]] && strcmp([ret objCType], "c") == 0 && ![ret boolValue])) ret = nil; \#define JP_FWD_RET_CODE_POINTER \void *ret; \id obj = formatJSToOC(jsval); \if ([obj isKindOfClass:[JPBoxing class]]) { \ret = [((JPBoxing *)obj) unboxPointer]; \}#define JP_FWD_RET_CODE_CLASS \Class ret; \ret = formatJSToOC(jsval);#define JP_FWD_RET_CODE_SEL \SEL ret; \id obj = formatJSToOC(jsval); \if ([obj isKindOfClass:[NSString class]]) { \ret = NSSelectorFromString(obj); \}JP_FWD_RET_CASE_RET('@', id, JP_FWD_RET_CODE_ID)JP_FWD_RET_CASE_RET('^', void*, JP_FWD_RET_CODE_POINTER)JP_FWD_RET_CASE_RET('*', void*, JP_FWD_RET_CODE_POINTER)JP_FWD_RET_CASE_RET('#', Class, JP_FWD_RET_CODE_CLASS)JP_FWD_RET_CASE_RET(':', SEL, JP_FWD_RET_CODE_SEL)JP_FWD_RET_CASE('c', char, charValue)JP_FWD_RET_CASE('C', unsigned char, unsignedCharValue)JP_FWD_RET_CASE('s', short, shortValue)JP_FWD_RET_CASE('S', unsigned short, unsignedShortValue)JP_FWD_RET_CASE('i', int, intValue)JP_FWD_RET_CASE('I', unsigned int, unsignedIntValue)JP_FWD_RET_CASE('l', long, longValue)JP_FWD_RET_CASE('L', unsigned long, unsignedLongValue)JP_FWD_RET_CASE('q', long long, longLongValue)JP_FWD_RET_CASE('Q', unsigned long long, unsignedLongLongValue)JP_FWD_RET_CASE('f', float, floatValue)JP_FWD_RET_CASE('d', double, doubleValue)JP_FWD_RET_CASE('B', BOOL, boolValue)case 'v': {JP_FWD_RET_CALL_JSbreak;}case '{': {NSString *typeString = extractStructName([NSString stringWithUTF8String:returnType]);#define JP_FWD_RET_STRUCT(_type, _funcSuffix) \if ([typeString rangeOfString:@#_type].location != NSNotFound) { \JP_FWD_RET_CALL_JS \_type ret = [jsval _funcSuffix]; \[invocation setReturnValue:&ret];\break; \}JP_FWD_RET_STRUCT(CGRect, toRect)JP_FWD_RET_STRUCT(CGPoint, toPoint)JP_FWD_RET_STRUCT(CGSize, toSize)JP_FWD_RET_STRUCT(NSRange, toRange)@synchronized (_context) {NSDictionary *structDefine = _registeredStruct[typeString];if (structDefine) {size_t size = sizeOfStructTypes(structDefine[@"types"]);JP_FWD_RET_CALL_JSvoid *ret = malloc(size);NSDictionary *dict = formatJSToOC(jsval);getStructDataWithDict(ret, dict, structDefine);[invocation setReturnValue:ret];free(ret);}}break;}default: {break;}}... } 復制代碼

上面的流程主要是定義一個js的方法然后進行的一些操作。

下面再來看一下js方法內部是如何調用原生的方法的。JS部分的代碼就不貼全了,主要的操作邏輯是給JS 對象基類對象加加上__c成員,這樣所有對象都可以調用到__c, __c內部還有一些判斷是否是對象、是否調用的是父類的方法等等的操作,最后調用的是_methodFunc方法。

var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {var selectorName = methodNameif (!isPerformSelector) {methodName = methodName.replace(/__/g, "-")selectorName = methodName.replace(/_/g, ":").replace(/-/g, "_")var marchArr = selectorName.match(/:/g)var numOfArgs = marchArr ? marchArr.length : 0if (args.length > numOfArgs) {selectorName += ":"}}var ret = instance ? _OC_callI(instance, selectorName, args, isSuper):_OC_callC(clsName, selectorName, args)return _formatOCToJS(ret)} 復制代碼

然后我們看到,方法內部做了一些判斷和解析。最后對象方法調用的是在native里注冊了的_OC_callI類方法調用的是在native里注冊了的_OC_callC方法。這兩個方法都走的是callSelector方法。這個方法也是老長。它的主要流程圖如下。

代碼如下

static id callSelector(NSString *className, NSString *selectorName, JSValue *arguments, JSValue *instance, BOOL isSuper) {NSString *realClsName = [[instance valueForProperty:@"__realClsName"] toString];if (instance) {// JSValue -> OC對象instance = formatJSToOC(instance);if (class_isMetaClass(object_getClass(instance))) {// 是否是類對象className = NSStringFromClass((Class)instance);instance = nil;} else if (!instance || instance == _nilObj || [instance isKindOfClass:[JPBoxing class]]) { // 空對象return @{@"__isNil": @(YES)};}}// 參數id argumentsObj = formatJSToOC(arguments);if (instance && [selectorName isEqualToString:@"toJS"]) {if ([instance isKindOfClass:[NSString class]] || [instance isKindOfClass:[NSDictionary class]] || [instance isKindOfClass:[NSArray class]] || [instance isKindOfClass:[NSDate class]]) {return _unboxOCObjectToJS(instance);}}// 類Class cls = instance ? [instance class] : NSClassFromString(className);SEL selector = NSSelectorFromString(selectorName);// 父類NSString *superClassName = nil;if (isSuper) {NSString *superSelectorName = [NSString stringWithFormat:@"SUPER_%@", selectorName];SEL superSelector = NSSelectorFromString(superSelectorName);Class superCls;if (realClsName.length) {Class defineClass = NSClassFromString(realClsName);superCls = defineClass ? [defineClass superclass] : [cls superclass];} else {superCls = [cls superclass];}Method superMethod = class_getInstanceMethod(superCls, selector);IMP superIMP = method_getImplementation(superMethod);class_addMethod(cls, superSelector, superIMP, method_getTypeEncoding(superMethod));NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];JSValue *overideFunction = _JSOverideMethods[superCls][JPSelectorName];if (overideFunction) {overrideMethod(cls, superSelectorName, overideFunction, NO, NULL);}selector = superSelector;superClassName = NSStringFromClass(superCls);}NSMutableArray *_markArray;// 方法編碼NSInvocation *invocation;NSMethodSignature *methodSignature;if (!_JSMethodSignatureCache) {_JSMethodSignatureCache = [[NSMutableDictionary alloc]init];}if (instance) {[_JSMethodSignatureLock lock];if (!_JSMethodSignatureCache[cls]) {_JSMethodSignatureCache[(id<NSCopying>)cls] = [[NSMutableDictionary alloc]init];}methodSignature = _JSMethodSignatureCache[cls][selectorName];if (!methodSignature) {methodSignature = [cls instanceMethodSignatureForSelector:selector];methodSignature = fixSignature(methodSignature);_JSMethodSignatureCache[cls][selectorName] = methodSignature;}[_JSMethodSignatureLock unlock];if (!methodSignature) {_exceptionBlock([NSString stringWithFormat:@"unrecognized selector %@ for instance %@", selectorName, instance]);return nil;}invocation = [NSInvocation invocationWithMethodSignature:methodSignature];[invocation setTarget:instance];} else {methodSignature = [cls methodSignatureForSelector:selector];methodSignature = fixSignature(methodSignature);if (!methodSignature) {_exceptionBlock([NSString stringWithFormat:@"unrecognized selector %@ for class %@", selectorName, className]);return nil;}invocation= [NSInvocation invocationWithMethodSignature:methodSignature];[invocation setTarget:cls];}[invocation setSelector:selector];NSUInteger numberOfArguments = methodSignature.numberOfArguments;NSInteger inputArguments = [(NSArray *)argumentsObj count];if (inputArguments > numberOfArguments - 2) {// calling variable argument method, only support parameter type `id` and return type `id`id sender = instance != nil ? instance : cls;id result = invokeVariableParameterMethod(argumentsObj, methodSignature, sender, selector);return formatOCToJS(result);}// 設置方法參數for (NSUInteger i = 2; i < numberOfArguments; i++) {const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];id valObj = argumentsObj[i-2];switch (argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {#define JP_CALL_ARG_CASE(_typeString, _type, _selector) \case _typeString: { \_type value = [valObj _selector]; \[invocation setArgument:&value atIndex:i];\break; \}JP_CALL_ARG_CASE('c', char, charValue)JP_CALL_ARG_CASE('C', unsigned char, unsignedCharValue)JP_CALL_ARG_CASE('s', short, shortValue)JP_CALL_ARG_CASE('S', unsigned short, unsignedShortValue)JP_CALL_ARG_CASE('i', int, intValue)JP_CALL_ARG_CASE('I', unsigned int, unsignedIntValue)JP_CALL_ARG_CASE('l', long, longValue)JP_CALL_ARG_CASE('L', unsigned long, unsignedLongValue)JP_CALL_ARG_CASE('q', long long, longLongValue)JP_CALL_ARG_CASE('Q', unsigned long long, unsignedLongLongValue)JP_CALL_ARG_CASE('f', float, floatValue)JP_CALL_ARG_CASE('d', double, doubleValue)JP_CALL_ARG_CASE('B', BOOL, boolValue)case ':': {SEL value = nil;if (valObj != _nilObj) {value = NSSelectorFromString(valObj);}[invocation setArgument:&value atIndex:i];break;}case '{': {NSString *typeString = extractStructName([NSString stringWithUTF8String:argumentType]);JSValue *val = arguments[i-2];#define JP_CALL_ARG_STRUCT(_type, _methodName) \if ([typeString rangeOfString:@#_type].location != NSNotFound) { \_type value = [val _methodName]; \[invocation setArgument:&value atIndex:i]; \break; \}JP_CALL_ARG_STRUCT(CGRect, toRect)JP_CALL_ARG_STRUCT(CGPoint, toPoint)JP_CALL_ARG_STRUCT(CGSize, toSize)JP_CALL_ARG_STRUCT(NSRange, toRange)@synchronized (_context) {NSDictionary *structDefine = _registeredStruct[typeString];if (structDefine) {size_t size = sizeOfStructTypes(structDefine[@"types"]);void *ret = malloc(size);getStructDataWithDict(ret, valObj, structDefine);[invocation setArgument:ret atIndex:i];free(ret);break;}}break;}case '*':case '^': {if ([valObj isKindOfClass:[JPBoxing class]]) {void *value = [((JPBoxing *)valObj) unboxPointer];if (argumentType[1] == '@') {if (!_TMPMemoryPool) {_TMPMemoryPool = [[NSMutableDictionary alloc] init];}if (!_markArray) {_markArray = [[NSMutableArray alloc] init];}memset(value, 0, sizeof(id));[_markArray addObject:valObj];}[invocation setArgument:&value atIndex:i];break;}}case '#': {if ([valObj isKindOfClass:[JPBoxing class]]) {Class value = [((JPBoxing *)valObj) unboxClass];[invocation setArgument:&value atIndex:i];break;}}default: {if (valObj == _nullObj) {valObj = [NSNull null];[invocation setArgument:&valObj atIndex:i];break;}if (valObj == _nilObj ||([valObj isKindOfClass:[NSNumber class]] && strcmp([valObj objCType], "c") == 0 && ![valObj boolValue])) {valObj = nil;[invocation setArgument:&valObj atIndex:i];break;}if ([(JSValue *)arguments[i-2] hasProperty:@"__isBlock"]) {JSValue *blkJSVal = arguments[i-2];Class JPBlockClass = NSClassFromString(@"JPBlock");if (JPBlockClass && ![blkJSVal[@"blockObj"] isUndefined]) {__autoreleasing id cb = [JPBlockClass performSelector:@selector(blockWithBlockObj:) withObject:[blkJSVal[@"blockObj"] toObject]];[invocation setArgument:&cb atIndex:i];Block_release((__bridge void *)cb);} else {__autoreleasing id cb = genCallbackBlock(arguments[i-2]);[invocation setArgument:&cb atIndex:i];}} else {[invocation setArgument:&valObj atIndex:i];}}}}if (superClassName) _currInvokeSuperClsName[selectorName] = superClassName;[invocation invoke];if (superClassName) [_currInvokeSuperClsName removeObjectForKey:selectorName];if ([_markArray count] > 0) {for (JPBoxing *box in _markArray) {void *pointer = [box unboxPointer];id obj = *((__unsafe_unretained id *)pointer);if (obj) {@synchronized(_TMPMemoryPool) {[_TMPMemoryPool setObject:obj forKey:[NSNumber numberWithInteger:[(NSObject*)obj hash]]];}}}}char returnType[255];strcpy(returnType, [methodSignature methodReturnType]);// Restore the return typeif (strcmp(returnType, @encode(JPDouble)) == 0) {strcpy(returnType, @encode(double));}if (strcmp(returnType, @encode(JPFloat)) == 0) {strcpy(returnType, @encode(float));}// 方法返回值處理id returnValue;if (strncmp(returnType, "v", 1) != 0) {if (strncmp(returnType, "@", 1) == 0) {void *result;[invocation getReturnValue:&result];//For performance, ignore the other methods prefix with alloc/new/copy/mutableCopyif ([selectorName isEqualToString:@"alloc"] || [selectorName isEqualToString:@"new"] ||[selectorName isEqualToString:@"copy"] || [selectorName isEqualToString:@"mutableCopy"]) {returnValue = (__bridge_transfer id)result;} else {returnValue = (__bridge id)result;}return formatOCToJS(returnValue);} else {switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {#define JP_CALL_RET_CASE(_typeString, _type) \case _typeString: { \_type tempResultSet; \[invocation getReturnValue:&tempResultSet];\returnValue = @(tempResultSet); \break; \}JP_CALL_RET_CASE('c', char)JP_CALL_RET_CASE('C', unsigned char)JP_CALL_RET_CASE('s', short)JP_CALL_RET_CASE('S', unsigned short)JP_CALL_RET_CASE('i', int)JP_CALL_RET_CASE('I', unsigned int)JP_CALL_RET_CASE('l', long)JP_CALL_RET_CASE('L', unsigned long)JP_CALL_RET_CASE('q', long long)JP_CALL_RET_CASE('Q', unsigned long long)JP_CALL_RET_CASE('f', float)JP_CALL_RET_CASE('d', double)JP_CALL_RET_CASE('B', BOOL)case '{': {NSString *typeString = extractStructName([NSString stringWithUTF8String:returnType]);#define JP_CALL_RET_STRUCT(_type, _methodName) \if ([typeString rangeOfString:@#_type].location != NSNotFound) { \_type result; \[invocation getReturnValue:&result]; \return [JSValue _methodName:result inContext:_context]; \}JP_CALL_RET_STRUCT(CGRect, valueWithRect)JP_CALL_RET_STRUCT(CGPoint, valueWithPoint)JP_CALL_RET_STRUCT(CGSize, valueWithSize)JP_CALL_RET_STRUCT(NSRange, valueWithRange)@synchronized (_context) {NSDictionary *structDefine = _registeredStruct[typeString];if (structDefine) {size_t size = sizeOfStructTypes(structDefine[@"types"]);void *ret = malloc(size);[invocation getReturnValue:ret];NSDictionary *dict = getDictOfStruct(ret, structDefine);free(ret);return dict;}}break;}case '*':case '^': {void *result;[invocation getReturnValue:&result];returnValue = formatOCToJS([JPBoxing boxPointer:result]);if (strncmp(returnType, "^{CG", 4) == 0) {if (!_pointersToRelease) {_pointersToRelease = [[NSMutableArray alloc] init];}[_pointersToRelease addObject:[NSValue valueWithPointer:result]];CFRetain(result);}break;}case '#': {Class result;[invocation getReturnValue:&result];returnValue = formatOCToJS([JPBoxing boxClass:result]);break;}}return returnValue;}}return nil; } 復制代碼

ok,到這里一些基本的流程就已經走完了。你可以大致感受到,其中有很大的一部分代碼是在做類型轉換的工作。這轉換的過程中還有很多的細節,比如可變對象的處理等等這里沒有具體展開來講。有興趣可以研究研究。

總結

  • js和native的交互用的是JavaScriptCore。
  • 替換添加方法都是直接走的方法交換。不存在的方法不會添加。
  • 方法的調用用的都是NSInvocation。
  • 參數、返回值的格式都是通過編碼來處理。

最后

看完JSPatch的源碼,加上最近一直在玩JSBox。有些想要自己實現一個簡單的JSBox的沖動,定個小目標,準備最近好好看看JS,然后嘗試嘗試。

水文一篇,希望能幫到大家一點點。

總結

以上是生活随笔為你收集整理的JSPatch实现原理一览的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

精品久久久久久亚洲综合网站 | 91成人国产 | 99在线精品视频 | 色婷婷视频在线观看 | 91精品资源 | 91成人精品一区在线播放 | 亚洲高清在线精品 | 国产色视频一区二区三区qq号 | 在线观看国产一区 | 亚洲欧美日本A∨在线观看 青青河边草观看完整版高清 | 亚洲在线国产 | 日韩精品综合在线 | 天天插天天狠天天透 | 欧美激情视频在线观看免费 | 亚洲国产视频在线 | 日韩久久精品 | 97成人在线免费视频 | 日韩一级成人av | 欧美日韩视频免费 | 久久午夜免费视频 | 国产99中文字幕 | 黄色三级免费网址 | 97视频在线免费观看 | 五月天亚洲综合 | 亚洲成年人免费网站 | 中文字幕国产在线 | 国产精品久久久久一区二区三区 | 亚洲在线视频观看 | 国产精品美女久久久 | 最近高清中文字幕 | 亚洲欧美国产视频 | 欧美成人区 | 中文字幕婷婷 | 成人黄色电影在线 | 美女视频久久黄 | 在线黄色免费 | 久99久在线视频 | 波多野结衣精品视频 | 美女网站一区 | 婷婷亚洲最大 | 久久福利 | 91成人国产 | 91麻豆精品国产91久久久无限制版 | 免费国产视频 | 成人va在线观看 | 国产精品18久久久久久不卡孕妇 | 青春草视频在线播放 | 久草香蕉在线视频 | 国产在线观看网站 | 亚洲精品综合欧美二区变态 | 爱干视频 | 成人黄大片视频在线观看 | 中文字幕在线观看完整版 | 亚洲天堂精品视频在线观看 | 日韩成人欧美 | 一区二区成人国产精品 | 日韩免费观看av | 美女在线黄 | 国产成人精品一区二 | 国产精品久久二区 | 成人av片免费观看app下载 | 一区二区伦理 | 久久夜色精品国产欧美乱 | 国产第一页在线观看 | 免费看黄在线观看 | 天天干,天天操 | 亚洲欧美国内爽妇网 | 久久久久久久久艹 | 久久久久久国产一区二区三区 | 999免费视频 | 成年人视频在线观看免费 | av导航福利 | 久久免费精彩视频 | 91精品国产综合久久福利 | 美女视频黄,久久 | 国产一级黄 | 免费一级日韩欧美性大片 | 欧美色图30p | 激情开心色 | 精品久久久久久一区二区里番 | 午夜视频在线观看欧美 | 97色se| 国产免费一区二区三区最新 | 久久999精品| 欧美日韩一区二区三区在线免费观看 | 美女福利视频在线 | 999久久 | 久久精品久久久久久久 | 中文av字幕在线观看 | 中文字幕在线观看免费观看 | 免费看三片 | 中文国产字幕 | 免费观看一级一片 | 少妇bbw撒尿 | 欧美色综合天天久久综合精品 | 蜜臀av性久久久久蜜臀aⅴ流畅 | 国产精品成人一区二区 | 四虎成人精品永久免费av | 97在线影院 | 国产精品成人久久久久 | 国产精品video爽爽爽爽 | 成年免费在线视频 | 欧美午夜精品久久久久久浪潮 | 亚洲精品视频在线免费播放 | 久久久久久激情 | 欧美另类69 | 亚洲精品tv | 天天综合人人 | 精品国产aⅴ一区二区三区 在线直播av | 亚洲三区在线 | 99精品国产免费久久 | 中文字幕资源网 国产 | av性网站 | 欧美一区二区在线刺激视频 | 狠狠操操 | 欧美巨大荫蒂茸毛毛人妖 | 精品久久久久久久久久久久久久久久 | 久久午夜剧场 | 久久婷婷国产色一区二区三区 | 婷婷色中文字幕 | 成人h动漫在线看 | 中文字幕日韩免费视频 | 中文字幕 国产精品 | 亚洲国产久 | 国产色视频网站2 | 在线观看视频在线 | 婷婷色 亚洲 | 中文在线免费视频 | 六月丁香激情综合色啪小说 | 久久九九影视网 | 国产精品日韩在线播放 | 超碰在线观看99 | 欧美色图狠狠干 | 成人在线视频免费看 | 高清在线一区二区 | 亚洲天堂网在线视频 | 麻豆av电影| 久久婷亚洲五月一区天天躁 | 中文字幕视频网站 | 五月婷婷精品 | 国产原厂视频在线观看 | 久久久久中文字幕 | 综合在线亚洲 | 999久久久欧美日韩黑人 | 毛片视频网址 | 日韩激情久久 | 久久伊人五月天 | 日韩最新中文字幕 | 久久有精品| www.成人久久 | 日韩免费网址 | 免费高清在线观看成人 | 国产精品人成电影在线观看 | 久久电影中文字幕视频 | 久久99深爱久久99精品 | 天天插天天爱 | 国产小视频在线免费观看 | 国产精品爽爽爽 | 最新午夜电影 | 91在线免费观看国产 | 成人免费看电影 | 久久欧美视频 | 蜜臀av夜夜澡人人爽人人 | 久久久久国产成人免费精品免费 | 一区二区三区在线播放 | 黄色日本免费 | 99热99热 | 91成人在线免费观看 | 日狠狠| 黄色大片av| 国产日本亚洲 | 久久久国产一区二区三区 | 久久精品视频在线免费观看 | 88av色| 精品国产乱码久久久久久浪潮 | av电影在线免费 | 久久精品视频在线播放 | 色视频网页 | 精品国产精品久久 | 亚洲天天在线日亚洲洲精 | 国产a视频免费观看 | 国产 日韩 中文字幕 | 亚洲伊人成综合网 | 怡春院av| 国产精品麻豆一区二区三区 | 久久免费的精品国产v∧ | 国产91精品在线播放 | 日本激情视频中文字幕 | 久久露脸国产精品 | 亚洲 欧美 变态 国产 另类 | 久久黄色网址 | av在线电影免费观看 | 国产精品一区二区av | 人人干人人草 | 五月婷婷深开心 | 日躁夜躁狠狠躁2001 | 久久久久久免费毛片精品 | 91视频在线免费看 | 福利片视频区 | 成人一区二区三区中文字幕 | 国产精品免费看久久久8精臀av | 欧美一区二区三区四区夜夜大片 | 欧美人操人 | 国产91九色蝌蚪 | 免费日韩视频 | 久久国产精品久久精品 | 色婷婷国产 | 国产97碰免费视频 | 久久久久日本精品一区二区三区 | 天堂va在线观看 | 手机在线永久免费观看av片 | 人人插人人舔 | 涩涩资源网 | 天天射天天操天天干 | 精品在线视频一区二区三区 | 久久国产91 | 精品国产理论 | 色婷婷久久一区二区 | 91亚洲国产成人 | 中文字幕二区在线观看 | www.在线观看视频 | 亚洲一二三在线 | 一区二区视频免费在线观看 | 国产91精品久久久久久 | 久久男女视频 | 精品毛片久久久久久 | 麻豆视频免费在线观看 | 欧美日韩国产综合网 | 色婷婷丁香| 人人超在线公开视频 | 一区二区三区在线观看免费 | 久久伊人色综合 | 综合网伊人 | 永久免费av在线播放 | 亚洲在线精品 | 中文字幕精品一区二区三区电影 | 欧美国产精品久久久久久免费 | 91在线视频免费观看 | 91视频在线免费看 | 亚洲精品麻豆 | www.色综合.com | 99热超碰在线 | 久久这里只有精品久久 | 三级av在线播放 | av片一区二区 | 亚洲激情六月 | 麻豆精品视频在线观看免费 | 97在线影院| 国产一区二区在线观看免费 | 91传媒91久久久 | 日韩精品最新在线观看 | 国产一级在线 | 午夜久久久影院 | 国产传媒一区在线 | 欧美亚洲国产一卡 | 国产精品久久久久国产a级 激情综合中文娱乐网 | 中文字幕免费高清av | 国产 欧美 在线 | 免费三级黄色片 | av在线免费在线 | 亚洲女裸体 | a视频在线| 久久精品一区二区三区视频 | 久一网站 | 99热免费在线 | 欧美极品少妇xbxb性爽爽视频 | 九九热免费在线观看 | 超碰在线中文字幕 | 成人国产精品久久久 | 五月天丁香综合 | 久草在线视频在线 | 91高清视频在线 | 超碰在线94| 国产综合婷婷 | 精品久久久久久国产 | 国产欧美最新羞羞视频在线观看 | 欧美日韩国产一区二区在线观看 | 精品欧美在线视频 | 99精品久久久久久久 | 亚洲精品视频网址 | 色在线观看网站 | 亚洲精品1234区 | 国产伦理一区二区三区 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 天天舔天天射天天操 | 亚洲一区二区三区91 | 国产精选在线 | 免费黄色在线播放 | 伊人夜夜 | 91在线在线观看 | 欧美黑人xxxx猛性大交 | 久色 网| 久久精品一区二区国产 | 欧美一级久久久久 | 成人黄色视| 91.麻豆视频| 欧美一区二区三区在线看 | 麻豆视频网址 | 天天色天天射天天干 | 久久久视屏 | 天天操网站 | 五月黄色 | 五月激情天 | 青青河边草手机免费 | av电影亚洲 | 国产精品久久久久久久久久久不卡 | av中文字幕网站 | 欧美一区二区三区在线 | 免费在线观看不卡av | 国产一区影院 | 亚洲乱码久久 | 欧美激情综合五月 | 久久九九九九 | 丝袜精品视频 | 久久久久亚洲精品男人的天堂 | 激情综合网天天干 | 狠狠色噜噜狠狠狠合久 | 国产人成在线视频 | 国产高清久久久 | 色资源二区在线视频 | 久久久五月婷婷 | 亚洲视频第一页 | 中文字幕日韩在线播放 | 亚洲精品网站在线 | 国产一性一爱一乱一交 | 99九九免费视频 | 91在线porny国产在线看 | 久久婷婷久久 | 久久久久 免费视频 | 亚洲午夜久久久综合37日本 | 一区二区影视 | 色婷婷九月 | 国产在线999 | 在线免费观看国产黄色 | 成人av片免费观看app下载 | 在线免费观看视频一区二区三区 | 99热这里只有精品免费 | 不卡的av电影在线观看 | 国产精品久久久久久久av电影 | 亚洲精品视频二区 | 人人涩 | 在线国产99 | 成人黄色小说视频 | 不卡日韩av | 婷婷五天天在线视频 | 中文字幕在线免费 | 热久久在线视频 | 亚洲视频在线观看网站 | 久久精品79国产精品 | 精品久久久久久久久亚洲 | 国产精品一区二区在线观看免费 | 国产高清中文字幕 | 18国产精品福利片久久婷 | 不卡电影免费在线播放一区 | 久久久久久久久久久网站 | 日韩丝袜在线观看 | 婷婷中文字幕在线观看 | 欧美久久久 | 一级做a爱片性色毛片www | 国产在线播放一区 | 99精品影视 | 黄色日本免费 | 91免费看片黄 | 久久久久亚洲国产精品 | 九九热在线精品视频 | 日本中文字幕电影在线免费观看 | 亚洲精品国偷自产在线91正片 | 日韩成人不卡 | 国产白浆在线观看 | 天天操操操操操操 | 高清一区二区 | 国产精品一区二区精品视频免费看 | 成人av一区二区三区 | 久久激情小视频 | 久久久伊人网 | 色婷丁香 | 综合网av| 亚洲欧洲精品视频 | 日韩欧美一区二区三区视频 | 国产黄色大片 | 久精品在线 | 国产淫片 | 国产精品爽爽久久久久久蜜臀 | 中文字幕在线不卡国产视频 | 99精品久久只有精品 | 国产精品久久av | 五月天久久综合 | 国产免费成人av | 99热精品久久 | 又爽又黄又无遮挡网站动态图 | 99视频在线 | 日本资源中文字幕在线 | 日韩在线视频在线观看 | 久久99视频免费观看 | 久久国产色 | 日产乱码一二三区别免费 | 九九热视频在线播放 | 日韩欧美一区二区三区视频 | 在线亚洲欧美视频 | 中文字幕三区 | 免费网站污 | av千婊在线免费观看 | 91在线视频免费91 | 99精品久久只有精品 | 在线观看免费黄色 | 成人欧美一区二区三区在线观看 | 中文字幕在线免费播放 | 久久久91精品国产一区二区三区 | 99视频免费在线观看 | 久久精品高清 | 国产视频在线看 | 国产精品久久99综合免费观看尤物 | japanesexxx乱女另类 | 国产高清在线观看 | 久草| 黄色的网站免费看 | 色婷婷综合久久久久中文字幕1 | 精品在线视频播放 | 成人黄色大片在线免费观看 | 成人va天堂 | 992tv人人网tv亚洲精品 | www好男人 | 国产精品久久99精品毛片三a | 综合久久久久久久 | 久久激情五月婷婷 | 毛片网站免费在线观看 | 国产精久久久久久妇女av | 一区三区视频 | 99久久精品国产一区二区成人 | 亚洲爱av | 999久久久精品视频 日韩高清www | 国产日产欧美在线观看 | 久久久久久免费视频 | 亚洲狠狠丁香婷婷综合久久久 | 亚洲精品88欧美一区二区 | 一区中文字幕在线观看 | 在线免费观看黄色小说 | 四虎影视欧美 | 午夜视频在线观看一区二区 | 久久99国产一区二区三区 | 国产一区二区三区视频在线 | 十八岁以下禁止观看的1000个网站 | 欧美激情精品久久久久久 | 久久久国产精品人人片99精片欧美一 | 开心激情久久 | 亚洲视频网站在线观看 | 免费精品在线观看 | 欧美一区二区三区在线播放 | 91人人澡 | 91精品福利在线 | 国产亚洲va综合人人澡精品 | 精品免费久久久久久 | 国产又粗又猛又黄视频 | 国产精品美女久久久久久久久久久 | av高清网站在线观看 | 久久五月婷婷丁香社区 | 五月综合激情婷婷 | 国产四虎影院 | 国产日韩精品一区二区三区在线 | 日韩欧美高清免费 | 五月婷婷综合在线视频 | 91精品视频免费看 | 日韩三级视频在线观看 | 天天干天天干天天色 | 去看片| 国产精品一区二区av | 国产无区一区二区三麻豆 | 日韩美一区二区三区 | 9999在线观看 | 91福利区一区二区三区 | 国产精品久久久视频 | 首页av在线 | 97超碰人人网 | 久久国产一区 | 中文字幕第 | 天天操天天操天天操天天操天天操天天操 | 婷婷在线播放 | 久久观看最新视频 | www.久久久精品 | 911精品视频 | 国产黄免费看 | av在线中文 | 精品国模一区二区三区 | 日韩在线视频一区 | 欧美91精品久久久久国产性生爱 | 国产精品18久久久久vr手机版特色 | 黄色a大片 | 欧美成人日韩 | 天天干天天碰 | 99久在线精品99re8热视频 | 日韩免费中文字幕 | 很污的网站 | 欧美在线观看小视频 | 国产成人av免费在线观看 | 国产在线 一区二区三区 | 欧美最猛性xxxxx(亚洲精品) | 在线日韩亚洲 | 中文字幕专区高清在线观看 | 午夜私人影院 | 国产精品久久一区二区无卡 | 91精品婷婷国产综合久久蝌蚪 | 亚洲一区二区三区精品在线观看 | 久久只精品99品免费久23小说 | 天天操夜| 国精产品满18岁在线 | 久久久久国产精品一区 | 色网站视频 | 在线国产视频观看 | 成人av在线一区二区 | 九九热在线观看 | 日操操 | 成人久久18免费网站麻豆 | 不卡的av在线 | 午夜视频播放 | 黄色免费在线视频 | 97热在线观看 | 中文字幕av在线免费 | 精品视频久久 | 久久综合操 | 蜜臀久久99静品久久久久久 | 日日夜夜精品视频 | 亚洲最快最全在线视频 | 9999免费视频 | 在线视频日韩欧美 | 欧美日韩在线看 | 久久五月婷婷丁香社区 | 麻豆精品传媒视频 | 91看片一区二区三区 | 99高清视频有精品视频 | 91在线观看视频 | 久久久精品国产免费观看同学 | 国产精品久久久久久av | 成人精品影视 | 2019免费中文字幕 | 国产高清区 | 国产在线精品视频 | 久草99| 久久蜜臀一区二区三区av | 天堂中文在线视频 | 国产97在线观看 | 亚洲一区二区精品视频 | 色综合中文综合网 | 亚洲成aⅴ人片久久青草影院 | 免费看黄色小说的网站 | 日韩在线电影一区 | 激情五月视频 | 亚洲电影图片小说 | 日韩 在线 | 男女免费视频观看 | 欧美狠狠操 | 成人精品亚洲 | 色婷婷综合在线 | 国产乱视频 | 久久精品成人热国产成 | 韩国视频一区二区三区 | 日韩在线播放av | 色就色,综合激情 | 国产精品九色 | 日韩中文字幕免费视频 | 国产精品久久久久久久久久久杏吧 | 久久久久9999亚洲精品 | 国产精品一区二区中文字幕 | se视频网址| 国产精品久久久久永久免费 | 久久精品女人毛片国产 | 国产视频丨精品|在线观看 国产精品久久久久久久久久久久午夜 | 成人高清在线观看 | 深爱激情五月综合 | 美女免费视频观看网站 | 在线黄av | 天天操天天玩 | 欧美一级片免费观看 | 精品三级av | 国产成人精品一区二区三区福利 | 国产一级黄色免费看 | 91精品国产一区 | www.国产在线观看 | 亚洲va综合va国产va中文 | 国产系列精品av | 免费看一级片 | 国产精品无av码在线观看 | 在线午夜 | 综合色在线观看 | 午夜视频在线网站 | 久久黄视频 | 天天干天天拍 | 国产精在线 | 91看毛片 | 五月婷在线观看 | 91福利小视频 | 超碰国产在线播放 | 久久久久女教师免费一区 | 一级黄色大片 | 久久热首页 | 国产一区二区不卡视频 | 免费黄色激情视频 | 干天天 | 久久久久久久18 | 久久久久久精 | 国产九九九视频 | 日日夜夜草 | 2023天天干 | 在线视频第一页 | 激情综合色综合久久 | 亚洲 欧洲av| 欧美精品黑人性xxxx | 欧美日韩另类视频 | 亚洲涩涩一区 | 91色影院 | 高清av免费观看 | 蜜臀av夜夜澡人人爽人人 | 日韩理论在线观看 | 亚洲资源视频 | 国产高清不卡一区二区三区 | 五月天综合色 | 日韩欧美观看 | 五月婷婷视频在线观看 | 日韩精品一区二区三区在线播放 | 日韩精品视频网站 | 国产美女在线免费观看 | 99精品一区| 免费观看国产成人 | 在线观看日本高清mv视频 | 欧美激情视频一区 | 99久久久国产精品免费99 | 中文字幕高清有码 | 91亚瑟视频 | 免费久久久久久久 | 五月香视频在线观看 | 99色99| 人人爽人人射 | 婷婷久久一区二区三区 | 成人一区二区三区中文字幕 | 国产精品免费观看视频 | 欧美黑吊大战白妞欧美 | a级片久久久 | 91色一区二区三区 | 成人久久网 | 97涩涩视频 | 看av免费 | 午夜精品一区二区三区可下载 | 中文字幕在线视频精品 | 在线观看精品 | 免费的黄色的网站 | 香蕉影视在线观看 | 二区三区精品 | 久久不卡国产精品一区二区 | 免费观看一区二区三区视频 | 国产最新视频在线观看 | 国产视频亚洲视频 | 久久视频免费 | 国产精品毛片久久久久久 | 国产精品一区二区麻豆 | 黄色软件在线观看免费 | 免费看的黄网站软件 | 国产护士hd高朝护士1 | 国产丝袜 | 久草视频免费播放 | 欧美日韩免费观看一区=区三区 | 久草在线欧美 | 成人超碰在线 | 中文字幕在线影视资源 | 亚色视频在线观看 | 国产精品片 | 一本一本久久aa综合精品 | 人人干人人干人人干 | 国产成人在线免费观看 | 人人射人人插 | 中文字幕一区二区三区四区在线视频 | 久草在线一免费新视频 | 久久99精品久久久久婷婷 | 亚洲国产成人精品电影在线观看 | 五月天久久综合网 | 在线观看的av | 不卡av电影在线 | 国产色区 | 婷婷www| 激情视频免费观看 | 成人网大片 | 精品在线免费观看 | 国产一区在线视频播放 | 日韩av快播电影网 | 麻豆视频免费播放 | 人人草人人草 | 欧美日韩不卡在线观看 | 久久久一本精品99久久精品66 | 亚洲国产精品va在线看黑人动漫 | 91精品国自产在线观看 | 国产乱对白刺激视频在线观看女王 | 麻豆影视网站 | 国产高清绿奴videos | 97国产精品免费 | 992tv人人草| 欧美在线久久 | 国产精品日韩在线 | 日韩精品中文字幕有码 | 9免费视频| 人人爽人人爽人人片 | 国产中文字幕亚洲 | 日韩欧美在线视频一区二区 | 超碰人人91 | 国产日韩欧美精品在线观看 | 久草在线久草在线2 | 激情五月婷婷网 | 麻豆网站免费观看 | 亚洲精品美女视频 | 久久久久久久久免费视频 | 亚洲国产成人在线 | 又黄又爽又湿又无遮挡的在线视频 | 国产精品久久久久久久久软件 | 国产亲近乱来精品 | 欧美日韩免费观看一区二区三区 | 日本午夜在线观看 | 久久精品www人人爽人人 | 人人插人人费 | 久草国产在线 | 美女视频黄是免费的 | 中文字幕在线看视频 | 日韩欧美视频一区二区 | 99亚洲国产精品 | 欧美狠狠色 | 色婷婷亚洲婷婷 | 深爱激情站 | 九九热视频在线免费观看 | 国产成本人视频在线观看 | 不卡视频一区二区三区 | 婷五月天激情 | 久久国产亚洲 | 亚洲日日射| 天天色天天干天天色 | 久久久精品二区 | 欧美另类sm图片 | 国产 日韩 欧美 在线 | 日韩精品一区二区在线 | 欧美日韩中文字幕综合视频 | 97超碰在线久草超碰在线观看 | 在线观看网站av | 亚洲精品高清在线 | 在线日本v二区不卡 | 九九国产精品视频 | 色插综合 | 婷婷激情在线观看 | 国产一级片在线播放 | 91看片麻豆 | 国产精品久久久久一区二区三区 | 亚洲成人精品在线观看 | 日本女人的性生活视频 | 99麻豆久久久国产精品免费 | 人人舔人人射 | 四虎影视成人永久免费观看亚洲欧美 | 人人看黄色 | 久久9999久久免费精品国产 | 国产亚洲精品免费 | 波多野结衣在线播放一区 | 丁香视频 | 国产无区一区二区三麻豆 | 91精品一 | 欧美日韩精品在线免费观看 | 性色视频在线 | 五月婷婷免费 | 欧美日韩在线精品 | 一区二区中文字幕在线播放 | 在线国产中文 | 亚洲免费一级电影 | 91在线看网站 | 免费观看一级 | 亚洲欧美视频一区二区三区 | 青草视频在线免费 | 中文字幕中文字幕中文字幕 | 激情欧美一区二区三区免费看 | 久久久麻豆精品一区二区 | 欧美一区二区在线 | 91福利在线导航 | 中文字幕第一 | 欧美日韩国产mv | 成人午夜性影院 | 天天艹天天操 | 91亚洲精品久久久蜜桃网站 | 免费99视频| 国产视频 亚洲精品 | 黄色app网站在线观看 | av在线播放快速免费阴 | 国产一级二级视频 | 久久av高清 | 国产成人一区在线 | 久久网站av| 久久免费精品 | 国产精品国产三级在线专区 | 福利视频区 | 色偷偷88欧美精品久久久 | 91精品在线观看视频 | 久久久久中文 | 久久夜夜操 | 韩国av免费观看 | 久久精品站| 色综合 久久精品 | 亚洲精品456在线播放乱码 | 国产精品久久久久久久久久尿 | 国产一区二区午夜 | 操操操人人人 | 黄网站免费久久 | 91精品1区| 欧美在线视频二区 | 精品国产乱码一区二区三区在线 | 国产亚洲在线观看 | 成人av免费在线观看 | 国产精品视频地址 | 日韩精品视频一二三 | 中文字幕在线日亚洲9 | 成人动态视频 | 久久不射电影院 | 中文字幕乱码日本亚洲一区二区 | 玖玖国产精品视频 | 亚洲高清激情 | 亚洲天堂视频在线 | 99久久精品久久亚洲精品 | av中文字幕亚洲 | 六月丁香婷婷在线 | 国产一级片在线播放 | 97在线观看免费观看高清 | 国产日韩精品一区二区三区在线 | 久久久九色精品国产一区二区三区 | 99精品视频99 | 国产成人三级在线播放 | 精品 激情 | 欧美尹人 | 日韩在线免费视频观看 | 国产特级毛片aaaaaaa高清 | 色婷婷狠狠18 | 久久精品视频国产 | av一级片在线观看 | 久久婷婷亚洲 | 中文字幕在线视频一区二区三区 | 日本激情视频中文字幕 | 又黄又爽又湿又无遮挡的在线视频 | 国产精品久久久久久av | 激情视频在线观看网址 | 97香蕉久久超级碰碰高清版 | 国产精品99久久久久久小说 | 探花视频免费观看高清视频 | www.香蕉| 人人精品久久 | 欧美日本日韩aⅴ在线视频 插插插色综合 | 国产二区视频在线观看 | 激情深爱.com | 视频二区在线 | av一级久久 | 久久国产精品久久国产精品 | 亚洲欧美少妇 | 欧美一区二区三区在线看 | 五月婷香 | 日韩成人看片 | 久久久视屏 | 99性视频 | 人人爽人人爽人人片av免 | 中文字幕一区二区三区在线视频 | 在线黄色免费av | 精品国产片 | 国产精品99久久免费观看 | 日韩黄色大片在线观看 | 69国产成人综合久久精品欧美 | av中文字幕第一页 | 国产精品手机在线观看 | 日韩av不卡在线播放 | 丁香六月中文字幕 | 永久免费精品视频网站 | 国产精品一区二区在线观看 | 国产精品99久久久久久有的能看 | 91精品人成在线观看 | 成 人 黄 色视频免费播放 | 久久久久久久国产精品视频 | 午夜精品久久久久久久久久 | 日韩三级.com | 麻花豆传媒mv在线观看网站 | 黄色av成人在线观看 | 国产精品久久久区三区天天噜 | 精品不卡av | 人人爽人人片 | 久久99精品久久久久久清纯直播 | 国产视频一区在线播放 | 国产一区二区三区四区大秀 | 在线免费观看不卡av | 99久久精品费精品 | 99久久超碰中文字幕伊人 | 91黄视频在线 | 91精品国产九九九久久久亚洲 | 日韩中文字幕在线看 | 色丁香综合 | 91精品国产乱码 | 国产精品综合久久久久 | 久久久久免费看 | 性色av一区二区三区在线观看 | 久草在线看片 | 精品成人国产 | 色成人亚洲 | 欧美一区二区三区四区夜夜大片 | 黄污视频网站 | 久草精品视频在线播放 | 久久综合九色综合97_ 久久久 | 久久在线免费观看 | 美女网站在线免费观看 | 中文字幕日韩伦理 | 99久久夜色精品国产亚洲96 | 久久久久中文字幕 | 精品美女久久久久久免费 | 午夜精品久久久久久久爽 | 国产va在线观看免费 | 在线中文字幕播放 | 九九视频免费在线观看 | 国产精品午夜久久久久久99热 | 国产精品久久久毛片 | 国产在线精品一区二区不卡了 | 免费看的毛片 | av三区在线| 国产精品第二页 | 国内精品视频在线 | 在线视频一二区 | av国产网站 | 国产成人在线免费观看 | 在线av资源 | 亚洲免费a | 91视频高清免费 | 国产视频资源在线观看 | 久久另类小说 | 国产黑丝袜在线 | av一区二区在线观看中文字幕 | 国产综合片 | 国产不卡免费视频 | 欧美精品成人在线 | 婷婷丁香激情 | 国产69精品久久久久久 | 精品在线观看一区二区 | 国产精品每日更新 | 蜜臀av性久久久久蜜臀aⅴ四虎 | 国产精品国产三级国产 | 91传媒激情理伦片 | 精品国产精品久久 | 丁香六月欧美 | 久久久国际精品 | 91免费日韩 | 特级西西人体444是什么意思 | 国产亚洲精品久久久久久大师 | 怡红院av久久久久久久 | 日韩精品三区四区 | 欧美午夜久久 | 天天色天天射综合网 | 欧美网站黄色 | 久久9999久久 | 亚洲综合成人专区片 | 91精品视屏| 五月天视频网站 | 美女网站视频久久 | 奇米影视777影音先锋 | 亚洲综合色av | 国内丰满少妇猛烈精品播 | 欧美激情视频免费看 | 免费在线观看av网站 | 精品成人在线 | 69av视频在线观看 | 久久精品国产一区 | 亚洲精品国产成人av在线 | 久爱精品在线 | 免费av电影网站 | 黄色免费网站下载 | 免费观看91视频 | 色婷婷av在线 | 97电影网站 | 91麻豆精品国产自产在线游戏 | a级片久久久 | 精品99免费| 欧美精品免费视频 | 天天夜夜亚洲 | 国产精品免费观看久久 | av免费在线播放 | 国产成人亚洲在线观看 | 中文字幕在线观看第一区 | 欧美精品成人在线 | 日韩精品久久久 | 久久久久久久久久久成人 | 波多野结衣在线观看一区二区三区 | 91秒拍国产福利一区 | 日韩欧美在线综合网 | 国产1区2| 在线 你懂| 欧美日韩性视频在线 | 国产九九九九九 | 成人午夜电影网 | 看av免费网站| 欧美一级黄色片 | 人人插人人玩 | 亚洲欧洲日韩在线观看 | 国产精品99久久免费黑人 | 精品国产乱码一区二区三区在线 | 中文字幕一区二区三区在线播放 | 蜜臀av夜夜澡人人爽人人 | 蜜桃av人人夜夜澡人人爽 | 91日韩在线专区 | 欧美大片www |