feat:fix iOS memory leak in debugging

This commit is contained in:
pengfei.zhou 2021-03-04 10:02:29 +08:00 committed by osborn
parent b972cce1cd
commit 627f323ae3
14 changed files with 124 additions and 36 deletions

View File

@ -41,14 +41,12 @@ public class DoricDebugDriver implements IDoricDriver {
private final DoricDebugJSEngine doricDebugJSEngine; private final DoricDebugJSEngine doricDebugJSEngine;
private final ExecutorService mBridgeExecutor; private final ExecutorService mBridgeExecutor;
private final Handler mUIHandler; private final Handler mUIHandler;
private final Handler mJSHandler;
private String theContextId = null; private String theContextId = null;
public DoricDebugDriver(WSClient wsClient) { public DoricDebugDriver(WSClient wsClient) {
doricDebugJSEngine = new DoricDebugJSEngine(wsClient); doricDebugJSEngine = new DoricDebugJSEngine(wsClient);
mBridgeExecutor = Executors.newCachedThreadPool(); mBridgeExecutor = Executors.newCachedThreadPool();
mUIHandler = new Handler(Looper.getMainLooper()); mUIHandler = new Handler(Looper.getMainLooper());
mJSHandler = doricDebugJSEngine.getJSHandler();
} }
@Override @Override
@ -64,7 +62,7 @@ public class DoricDebugDriver implements IDoricDriver {
@Override @Override
public AsyncResult<JSDecoder> invokeDoricMethod(final String method, final Object... args) { public AsyncResult<JSDecoder> invokeDoricMethod(final String method, final Object... args) {
return AsyncCall.ensureRunInHandler(mJSHandler, new Callable<JSDecoder>() { return AsyncCall.ensureRunInExecutor(mBridgeExecutor, new Callable<JSDecoder>() {
@Override @Override
public JSDecoder call() { public JSDecoder call() {
try { try {
@ -80,11 +78,10 @@ public class DoricDebugDriver implements IDoricDriver {
@Override @Override
public <T> AsyncResult<T> asyncCall(Callable<T> callable, ThreadMode threadMode) { public <T> AsyncResult<T> asyncCall(Callable<T> callable, ThreadMode threadMode) {
switch (threadMode) { switch (threadMode) {
case JS:
return AsyncCall.ensureRunInHandler(mJSHandler, callable);
case UI: case UI:
return AsyncCall.ensureRunInHandler(mUIHandler, callable); return AsyncCall.ensureRunInHandler(mUIHandler, callable);
case INDEPENDENT: case INDEPENDENT:
case JS:
default: default:
return AsyncCall.ensureRunInExecutor(mBridgeExecutor, callable); return AsyncCall.ensureRunInExecutor(mBridgeExecutor, callable);
} }
@ -92,7 +89,7 @@ public class DoricDebugDriver implements IDoricDriver {
@Override @Override
public AsyncResult<Boolean> createContext(final String contextId, final String script, final String source) { public AsyncResult<Boolean> createContext(final String contextId, final String script, final String source) {
return AsyncCall.ensureRunInHandler(mJSHandler, new Callable<Boolean>() { return AsyncCall.ensureRunInExecutor(mBridgeExecutor, new Callable<Boolean>() {
@Override @Override
public Boolean call() { public Boolean call() {
try { try {
@ -108,7 +105,7 @@ public class DoricDebugDriver implements IDoricDriver {
@Override @Override
public AsyncResult<Boolean> destroyContext(final String contextId) { public AsyncResult<Boolean> destroyContext(final String contextId) {
return AsyncCall.ensureRunInHandler(mJSHandler, new Callable<Boolean>() { return AsyncCall.ensureRunInExecutor(mBridgeExecutor, new Callable<Boolean>() {
@Override @Override
public Boolean call() { public Boolean call() {
try { try {

View File

@ -11,16 +11,21 @@ import org.json.JSONObject;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import pub.doric.devkit.WSClient; import pub.doric.devkit.WSClient;
public class RemoteJSExecutor implements WSClient.Interceptor { public class RemoteJSExecutor implements WSClient.Interceptor {
private final Map<String, JavaFunction> globalFunctions = new HashMap<>(); private final Map<String, JavaFunction> globalFunctions = new HashMap<>();
private JSDecoder temp;
private final WSClient wsClient; private final WSClient wsClient;
private final Thread currentThread; private final Thread currentThread;
private final AtomicInteger callIdCounter = new AtomicInteger();
private Map<Integer, Thread> mThreads = new HashMap<>();
private Map<Integer, JSDecoder> mResults = new HashMap<>();
public RemoteJSExecutor(WSClient wsClient) { public RemoteJSExecutor(WSClient wsClient) {
this.wsClient = wsClient; this.wsClient = wsClient;
this.wsClient.addInterceptor(this); this.wsClient.addInterceptor(this);
@ -63,6 +68,7 @@ public class RemoteJSExecutor implements WSClient.Interceptor {
.put("value", javaValue.getValue()) .put("value", javaValue.getValue())
.toJSONObject()); .toJSONObject());
} }
int callId = callIdCounter.incrementAndGet();
wsClient.sendToDebugger( wsClient.sendToDebugger(
"invokeMethod", "invokeMethod",
new JSONBuilder() new JSONBuilder()
@ -70,11 +76,13 @@ public class RemoteJSExecutor implements WSClient.Interceptor {
.put("objectName", objectName) .put("objectName", objectName)
.put("functionName", functionName) .put("functionName", functionName)
.put("values", jsonArray) .put("values", jsonArray)
.put("callId", callId)
.put("hashKey", hashKey) .put("hashKey", hashKey)
.toJSONObject()); .toJSONObject());
Thread thread = Thread.currentThread();
LockSupport.park(Thread.currentThread()); mThreads.put(callId, thread);
return temp; LockSupport.park(thread);
return mResults.remove(callId);
} }
public void destroy() { public void destroy() {
@ -99,15 +107,16 @@ public class RemoteJSExecutor implements WSClient.Interceptor {
} }
break; break;
case "invokeMethod": { case "invokeMethod": {
int callId = payload.optInt("callId");
try { try {
Object result = payload.opt("result"); Object result = payload.opt("result");
ValueBuilder vb = new ValueBuilder(result); ValueBuilder vb = new ValueBuilder(result);
temp = new JSDecoder(vb.build()); mResults.put(callId, new JSDecoder(vb.build()));
System.out.println(result); System.out.println(result);
} catch (Exception ex) { } catch (Exception ex) {
ex.printStackTrace(); ex.printStackTrace();
} finally { } finally {
LockSupport.unpark(currentThread); LockSupport.unpark(mThreads.remove(callId));
} }
} }
break; break;

View File

@ -166,4 +166,11 @@ public abstract class GroupNode<F extends ViewGroup> extends SuperNode<F> {
} }
return null; return null;
} }
@Override
public void clearSubModel() {
super.clearSubModel();
mChildNodes.clear();
mChildViewIds.clear();
}
} }

View File

@ -23,10 +23,13 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <DoricCore/Doric.h> #import <DoricCore/Doric.h>
#import "DoricWSClient.h" #import "DoricWSClient.h"
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@interface DoricDebugDriver : NSObject <DoricDriverProtocol> @interface DoricDebugDriver : NSObject <DoricDriverProtocol>
- (instancetype)initWithWSClient:(DoricWSClient *)wsClient; - (instancetype)initWithWSClient:(DoricWSClient *)wsClient;
- (void)teardown;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -57,7 +57,7 @@ - (DoricRegistry *)registry {
- (DoricAsyncResult *)invokeDoricMethod:(NSString *)method argumentsArray:(NSArray *)args { - (DoricAsyncResult *)invokeDoricMethod:(NSString *)method argumentsArray:(NSArray *)args {
DoricAsyncResult *ret = [[DoricAsyncResult alloc] init]; DoricAsyncResult *ret = [[DoricAsyncResult alloc] init];
__weak typeof(self) _self = self; __weak typeof(self) _self = self;
[self.jsExecutor ensureRunOnJSThread:^{ [self runInJSQueue:^{
__strong typeof(_self) self = _self; __strong typeof(_self) self = _self;
if (!self) return; if (!self) return;
@try { @try {
@ -78,7 +78,7 @@ - (DoricAsyncResult *)invokeDoricMethod:(NSString *)method argumentsArray:(NSArr
[array addObject:arg]; [array addObject:arg];
} }
__weak typeof(self) _self = self; __weak typeof(self) _self = self;
[self.jsExecutor ensureRunOnJSThread:^{ [self runInJSQueue:^{
__strong typeof(_self) self = _self; __strong typeof(_self) self = _self;
if (!self) return; if (!self) return;
@try { @try {
@ -110,7 +110,7 @@ - (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString
arg = va_arg(args, JSValue *); arg = va_arg(args, JSValue *);
} }
__weak typeof(self) _self = self; __weak typeof(self) _self = self;
[self.jsExecutor ensureRunOnJSThread:^{ [self runInJSQueue:^{
__strong typeof(_self) self = _self; __strong typeof(_self) self = _self;
if (!self) return; if (!self) return;
@try { @try {
@ -132,7 +132,7 @@ - (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString
[array addObject:arg]; [array addObject:arg];
} }
__weak typeof(self) _self = self; __weak typeof(self) _self = self;
[self.jsExecutor ensureRunOnJSThread:^{ [self runInJSQueue:^{
__strong typeof(_self) self = _self; __strong typeof(_self) self = _self;
if (!self) return; if (!self) return;
@try { @try {
@ -148,7 +148,7 @@ - (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString
- (DoricAsyncResult *)createContext:(NSString *)contextId script:(NSString *)script source:(NSString *)source { - (DoricAsyncResult *)createContext:(NSString *)contextId script:(NSString *)script source:(NSString *)source {
DoricAsyncResult *ret = [[DoricAsyncResult alloc] init]; DoricAsyncResult *ret = [[DoricAsyncResult alloc] init];
__weak typeof(self) _self = self; __weak typeof(self) _self = self;
[self.jsExecutor ensureRunOnJSThread:^{ [self runInJSQueue:^{
__strong typeof(_self) self = _self; __strong typeof(_self) self = _self;
if (!self) return; if (!self) return;
@try { @try {
@ -164,7 +164,7 @@ - (DoricAsyncResult *)createContext:(NSString *)contextId script:(NSString *)scr
- (DoricAsyncResult *)destroyContext:(NSString *)contextId { - (DoricAsyncResult *)destroyContext:(NSString *)contextId {
DoricAsyncResult *ret = [[DoricAsyncResult alloc] init]; DoricAsyncResult *ret = [[DoricAsyncResult alloc] init];
NSString *theContextId = self.theContextId; NSString *theContextId = self.theContextId;
[self.jsExecutor ensureRunOnJSThread:^{ [self runInJSQueue:^{
@try { @try {
if ([contextId isEqualToString:theContextId]) { if ([contextId isEqualToString:theContextId]) {
[DoricDev.instance stopDebugging:NO]; [DoricDev.instance stopDebugging:NO];
@ -177,6 +177,12 @@ - (DoricAsyncResult *)destroyContext:(NSString *)contextId {
return ret; return ret;
} }
- (void)runInJSQueue:(dispatch_block_t)block {
[self.jsExecutor ensureRunOnJSThread:^{
block();
}];
}
- (void)ensureSyncInMainQueue:(dispatch_block_t)block { - (void)ensureSyncInMainQueue:(dispatch_block_t)block {
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) { if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {
block(); block();
@ -184,4 +190,12 @@ - (void)ensureSyncInMainQueue:(dispatch_block_t)block {
dispatch_async(dispatch_get_main_queue(), block); dispatch_async(dispatch_get_main_queue(), block);
} }
} }
- (void)dealloc {
[self.jsExecutor teardown];
}
- (void)teardown {
[self.jsExecutor teardown];
}
@end @end

View File

@ -42,4 +42,21 @@ - (instancetype)initWithWSClient:(DoricWSClient *)wsClient {
- (void)initJSEngine { - (void)initJSEngine {
self.jsExecutor = [[DoricRemoteJSExecutor alloc] initWithWSClient:self.wsClient]; self.jsExecutor = [[DoricRemoteJSExecutor alloc] initWithWSClient:self.wsClient];
} }
- (void)teardown {
[super teardown];
if ([self.jsExecutor isKindOfClass:DoricRemoteJSExecutor.class]) {
[((DoricRemoteJSExecutor *) (self.jsExecutor)) teardown];
}
}
- (void)ensureRunOnJSThread:(dispatch_block_t)block {
if ([self.jsExecutor isKindOfClass:DoricRemoteJSExecutor.class]
&& ((DoricRemoteJSExecutor *) (self.jsExecutor)).invokingMethod) {
NSThread *thread = [[NSThread alloc] initWithBlock:block];
[thread start];
} else {
[super ensureRunOnJSThread:block];
}
}
@end @end

View File

@ -32,6 +32,8 @@ @interface DoricContextDebuggable : NSObject
@property(nonatomic, weak) DoricContext *doricContext; @property(nonatomic, weak) DoricContext *doricContext;
@property(nonatomic, weak) id <DoricDriverProtocol> nativeDriver; @property(nonatomic, weak) id <DoricDriverProtocol> nativeDriver;
@property(nonatomic, weak) DoricWSClient *wsClient; @property(nonatomic, weak) DoricWSClient *wsClient;
@property(nonatomic, weak) DoricDebugDriver *debugDriver;
@end @end
@implementation DoricContextDebuggable @implementation DoricContextDebuggable
@ -46,10 +48,12 @@ - (instancetype)initWithWSClient:(DoricWSClient *)client context:(DoricContext *
- (void)startDebug { - (void)startDebug {
[self.doricContext setDriver:[[DoricDebugDriver alloc] initWithWSClient:self.wsClient]]; [self.doricContext setDriver:[[DoricDebugDriver alloc] initWithWSClient:self.wsClient]];
self.debugDriver = self.doricContext.driver;
[self.doricContext reload:self.doricContext.script]; [self.doricContext reload:self.doricContext.script];
} }
- (void)stopDebug:(BOOL)resume { - (void)stopDebug:(BOOL)resume {
[self.debugDriver teardown];
self.doricContext.driver = self.nativeDriver; self.doricContext.driver = self.nativeDriver;
if (resume) { if (resume) {
[self.doricContext reload:self.doricContext.script]; [self.doricContext reload:self.doricContext.script];

View File

@ -27,7 +27,11 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@interface DoricRemoteJSExecutor : NSObject <DoricJSExecutorProtocol> @interface DoricRemoteJSExecutor : NSObject <DoricJSExecutorProtocol>
@property(nonatomic, assign) BOOL invokingMethod;
- (instancetype)initWithWSClient:(DoricWSClient *)wsClient; - (instancetype)initWithWSClient:(DoricWSClient *)wsClient;
- (void)teardown;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -49,8 +49,9 @@ typedef NS_ENUM(NSUInteger, DoricJSRemoteArgType) {
@interface DoricRemoteJSExecutor () <DoricWSClientInterceptor> @interface DoricRemoteJSExecutor () <DoricWSClientInterceptor>
@property(nonatomic, weak) DoricWSClient *wsClient; @property(nonatomic, weak) DoricWSClient *wsClient;
@property(nonatomic, strong) NSMutableDictionary <NSString *, id> *blockMDic; @property(nonatomic, strong) NSMutableDictionary <NSString *, id> *blockMDic;
@property(nonatomic, strong) JSValue *temp; @property(nonatomic) NSInteger counter;
@property(nonatomic, strong) dispatch_semaphore_t semaphore; @property(nonatomic, strong) NSMutableDictionary <NSNumber *, dispatch_semaphore_t> *semaphores;
@property(nonatomic, strong) NSMutableDictionary <NSNumber *, JSValue *> *results;
@end @end
@implementation DoricRemoteJSExecutor @implementation DoricRemoteJSExecutor
@ -59,7 +60,9 @@ - (instancetype)initWithWSClient:(DoricWSClient *)wsClient {
_wsClient = wsClient; _wsClient = wsClient;
[_wsClient addInterceptor:self]; [_wsClient addInterceptor:self];
_blockMDic = [NSMutableDictionary new]; _blockMDic = [NSMutableDictionary new];
_semaphore = dispatch_semaphore_create(0); _semaphores = [NSMutableDictionary new];
_results = [NSMutableDictionary new];
_counter = 0;
} }
return self; return self;
} }
@ -114,17 +117,23 @@ - (JSValue *)invokeObject:(NSString *)objName method:(NSString *)funcName args:(
NSDictionary *dic = [self dicForArg:arg]; NSDictionary *dic = [self dicForArg:arg];
[argsMArr addObject:dic]; [argsMArr addObject:dic];
} }
NSInteger callId = ++self.counter;
[self.wsClient sendToDebugger:@"invokeMethod" payload:@{ [self.wsClient sendToDebugger:@"invokeMethod" payload:@{
@"cmd": @"invokeMethod", @"cmd": @"invokeMethod",
@"objectName": objName, @"objectName": objName,
@"functionName": funcName, @"functionName": funcName,
@"callId": @(callId),
@"values": [argsMArr copy] @"values": [argsMArr copy]
}]; }];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
DC_LOCK(self.semaphore); self.semaphores[@(callId)] = semaphore;
self.invokingMethod = YES;
return self.temp; DoricLog(@"Lock %@",@(callId));
DC_LOCK(semaphore);
JSValue *result = self.results[@(callId)];
[self.results removeObjectForKey:@(callId)];
self.invokingMethod = NO;
return result;
} }
- (NSDictionary *)dicForArg:(id)arg { - (NSDictionary *)dicForArg:(id)arg {
@ -161,32 +170,43 @@ - (BOOL)interceptType:(NSString *)type command:(NSString *)cmd payload:(NSDictio
NSArray *argsArr = payload[@"arguments"]; NSArray *argsArr = payload[@"arguments"];
id tmpBlk = self.blockMDic[name]; id tmpBlk = self.blockMDic[name];
if (argsArr.count == 0) { if (argsArr.count == 0) {
((Block0) tmpBlk)(); ((Block0) tmpBlk)();
} else if (argsArr.count == 1) { } else if (argsArr.count == 1) {
((Block1) tmpBlk)(argsArr[0]); ((Block1) tmpBlk)(argsArr[0]);
} else if (argsArr.count == 2) { } else if (argsArr.count == 2) {
((Block2) tmpBlk)(argsArr[0], argsArr[1]); ((Block2) tmpBlk)(argsArr[0], argsArr[1]);
} else if (argsArr.count == 3) { } else if (argsArr.count == 3) {
((Block3) tmpBlk)(argsArr[0], argsArr[1], argsArr[2]); ((Block3) tmpBlk)(argsArr[0], argsArr[1], argsArr[2]);
} else if (argsArr.count == 4) { } else if (argsArr.count == 4) {
((Block4) tmpBlk)(argsArr[0], argsArr[1], argsArr[2], argsArr[3]); ((Block4) tmpBlk)(argsArr[0], argsArr[1], argsArr[2], argsArr[3]);
} else if (argsArr.count == 5) { } else if (argsArr.count == 5) {
((Block5) tmpBlk)(argsArr[0], argsArr[1], argsArr[2], argsArr[3], argsArr[4]); ((Block5) tmpBlk)(argsArr[0], argsArr[1], argsArr[2], argsArr[3], argsArr[4]);
} else { } else {
DoricLog(@"error:args to more than 5. args:%@", argsArr); DoricLog(@"error:args to more than 5. args:%@", argsArr);
} }
} else if ([cmd isEqualToString:@"invokeMethod"]) { } else if ([cmd isEqualToString:@"invokeMethod"]) {
NSNumber *callId = payload[@"callId"];
@try { @try {
self.temp = [JSValue valueWithObject:payload[@"result"] inContext:nil]; JSValue *value = [JSValue valueWithObject:payload[@"result"] inContext:nil];
self.results[callId] = value;
} @catch (NSException *exception) { } @catch (NSException *exception) {
DoricLog(@"debugger ", NSStringFromSelector(_cmd), exception.reason); DoricLog(@"debugger ", NSStringFromSelector(_cmd), exception.reason);
} @finally { } @finally {
DC_UNLOCK(self.semaphore); DoricLog(@"Unlock:%@",payload);
dispatch_semaphore_t semaphore = self.semaphores[callId];
[self.semaphores removeObjectForKey:callId];
DC_UNLOCK(semaphore);
} }
} }
} }
return NO; return NO;
} }
- (void)teardown {
[self.blockMDic removeAllObjects];
[self.semaphores enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, dispatch_semaphore_t obj, BOOL *stop) {
DC_UNLOCK(obj);
}];
}
@end @end

View File

@ -101,6 +101,7 @@ - (void)build:(CGSize)size {
- (void)reload:(NSString *)script { - (void)reload:(NSString *)script {
[self.driver destroyContext:self.contextId]; [self.driver destroyContext:self.contextId];
self.rootNode.viewId = nil; self.rootNode.viewId = nil;
[self.rootNode clearSubModel];
self.script = script; self.script = script;
[self.driver createContext:self.contextId script:script source:self.source]; [self.driver createContext:self.contextId script:script source:self.source];
[self init:self.extra]; [self init:self.extra];

View File

@ -195,4 +195,8 @@ - (void)ensureSyncInMainQueue:(dispatch_block_t)block {
dispatch_async(dispatch_get_main_queue(), block); dispatch_async(dispatch_get_main_queue(), block);
} }
} }
- (void)dealloc {
[self.jsExecutor teardown];
}
@end @end

View File

@ -48,6 +48,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)ensureRunOnJSThread:(dispatch_block_t)block; - (void)ensureRunOnJSThread:(dispatch_block_t)block;
- (void)initJSEngine; - (void)initJSEngine;
- (void)teardown;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -101,9 +101,9 @@ - (instancetype)init {
return self; return self;
} }
- (void)dealloc { - (void)teardown {
_destroyed = YES; _destroyed = YES;
}; }
- (void)ensureRunOnJSThread:(dispatch_block_t)block { - (void)ensureRunOnJSThread:(dispatch_block_t)block {
if (NSThread.currentThread == _jsThread) { if (NSThread.currentThread == _jsThread) {

View File

@ -181,4 +181,10 @@ - (void)requestLayout {
[node requestLayout]; [node requestLayout];
} }
} }
- (void)clearSubModel {
[super clearSubModel];
self.childNodes = @[];
self.childViewIds = @[];
}
@end @end