From 8af5b26287282502a15c329005cfd96f356e1087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E8=90=A7=E7=8E=89?= Date: Sat, 3 Aug 2019 19:40:31 +0800 Subject: [PATCH] Add APIs for BHInvocation: methodSignature getReturnValue: setReturnValue: getArgument:atIndex: setArgument:atIndex: --- BlockHook.podspec | 2 +- BlockHook/BlockHook.h | 30 ++++- BlockHook/BlockHook.m | 58 +++++---- BlockHookSampleTests/BlockHookSampleTests.m | 128 +++++++++++++------- README.md | 19 +-- 5 files changed, 161 insertions(+), 76 deletions(-) diff --git a/BlockHook.podspec b/BlockHook.podspec index d1a1b1f..dec0bc6 100644 --- a/BlockHook.podspec +++ b/BlockHook.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "BlockHook" -s.version = "1.5.3" +s.version = "1.5.4" s.summary = "Hook Objective-C blocks." s.description = <<-DESC Hook Objective-C blocks with libffi. It's a powerful AOP tool for blocks. BlockHook can run your code before/instead/after invoking a block. BlockHook can even notify you when a block dealloc. You can trace the whole lifecycle of a block using BlockHook! diff --git a/BlockHook/BlockHook.h b/BlockHook/BlockHook.h index 39fd82a..0718e5b 100644 --- a/BlockHook/BlockHook.h +++ b/BlockHook/BlockHook.h @@ -29,12 +29,12 @@ NS_ASSUME_NONNULL_BEGIN /** Arguments of invoking the block. Need type casting. */ -@property (nonatomic, readonly) void *_Nullable *_Null_unspecified args; +@property (nonatomic, readonly) void *_Nullable *_Null_unspecified args DEPRECATED_MSG_ATTRIBUTE("Use getArgument:atIndex: or setArgument:atIndex: instead"); /** Return value of invoking the block. Need type casting. */ -@property (nonatomic, nullable, readonly) void *retValue; +@property (nonatomic, nullable, readonly) void *retValue DEPRECATED_MSG_ATTRIBUTE("Use getReturnValue: or setReturnValue: instead"); /** Mode you want to insert your custom logic: Before, Instead, After OR Dead. @@ -62,10 +62,36 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)retainArguments; +/** + Gets the receiver's return value. + If the NSInvocation object has never been invoked, the result of this method is undefined. + + @param retLoc An untyped buffer into which the receiver copies its return value. It should be large enough to accommodate the value. See the discussion in NSInvocation for more information about buffer. + */ - (void)getReturnValue:(void *)retLoc; + +/** + Sets the receiver’s return value. + + @param retLoc An untyped buffer whose contents are copied as the receiver's return value. + @discussion This value is normally set when you send an invokeOriginalBlock message. + */ - (void)setReturnValue:(void *)retLoc; +/** + Sets an argument of the receiver. + + @param argumentLocation An untyped buffer containing an argument to be assigned to the receiver. See the discussion in NSInvocation relating to argument values that are objects. + @param idx An integer specifying the index of the argument. Indices 0 indicates self, use indices 1 and greater for the arguments normally passed in an invocation. + */ - (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx; + +/** + Sets an argument of the receiver. + + @param argumentLocation An untyped buffer containing an argument to be assigned to the receiver. See the discussion in NSInvocation relating to argument values that are objects. + @param idx An integer specifying the index of the argument. Indices 0 indicates self, use indices 1 and greater for the arguments normally passed in an invocation. + */ - (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx; @end diff --git a/BlockHook/BlockHook.m b/BlockHook/BlockHook.m index 32ee747..0018aa8 100644 --- a/BlockHook/BlockHook.m +++ b/BlockHook/BlockHook.m @@ -242,6 +242,7 @@ @interface BHInvocation () @property (nonatomic, readwrite) BlockHookMode mode; @property (nonatomic) NSMutableData *dataArgs; @property (nonatomic) NSMutableData *dataRet; +@property (nonatomic) NSMutableDictionary *mallocMap; @property (nonatomic) NSMutableDictionary *retainMap; @property (nonatomic, getter=isArgumentsRetained, readwrite) BOOL argumentsRetained; @property (nonatomic) dispatch_queue_t argumentsRetainedQueue; @@ -291,14 +292,6 @@ - (void)setArgumentsRetained:(BOOL)argumentsRetained - (void)invokeOriginalBlock { [self.token invokeOriginalBlockWithArgs:self.realArgs retValue:self.realRetValue]; - if (self.isArgumentsRetained) { - for (NSUInteger idx = 0; idx < self.numberOfRealArgs; idx++) { - void *argBuf = self.realArgs[idx]; - if (argBuf != NULL) { - free(argBuf); - } - } - } } - (NSMethodSignature *)methodSignature @@ -311,7 +304,8 @@ - (void)retainArguments if (!self.isArgumentsRetained) { self.dataArgs = [NSMutableData dataWithLength:self.numberOfRealArgs * sizeof(void *)]; self.retainMap = [NSMutableDictionary dictionaryWithCapacity:self.numberOfRealArgs + 1]; - void **args = [self.dataArgs mutableBytes]; + self.mallocMap = [NSMutableDictionary dictionaryWithCapacity:self.numberOfRealArgs + 1]; + void **args = self.dataArgs.mutableBytes; for (NSUInteger idx = 0; idx < self.numberOfRealArgs; idx++) { const char *type = NULL; if (self.token.hasStret) { @@ -328,7 +322,9 @@ - (void)retainArguments NSUInteger argSize; NSGetSizeAndAlignment(type, &argSize, NULL); - void *argBuf = malloc(argSize); + NSMutableData *argData = [NSMutableData dataWithLength:argSize]; + self.mallocMap[@(idx)] = argData; + void *argBuf = argData.mutableBytes; memcpy(argBuf, self.realArgs[idx], argSize); args[idx] = argBuf; [self _retainPointer:args[idx] encode:type key:@(idx)]; @@ -339,10 +335,14 @@ - (void)retainArguments self.retValue = *((void **)args[0]); } else { + self.dataRet = [NSMutableData dataWithLength:sizeof(void *)]; + void *ret = self.dataRet.mutableBytes; NSUInteger retSize = self.methodSignature.methodReturnLength; - self.dataRet = [NSMutableData dataWithLength:sizeof(retSize)]; - void *ret = [self.dataRet mutableBytes]; - memcpy(ret, self.retValue, retSize); + NSMutableData *retData = [NSMutableData dataWithLength:retSize]; + self.mallocMap[@-1] = retData; + void *retBuf = retData.mutableBytes; + memcpy(retBuf, self.retValue, retSize); + ret = retBuf; [self _retainPointer:ret encode:self.methodSignature.methodReturnType key:@-1]; self.args = args; self.retValue = ret; @@ -355,23 +355,31 @@ - (void)retainArguments - (void)getReturnValue:(void *)retLoc { + if (!retLoc || !self.retValue) { + return; + } NSUInteger retSize = self.methodSignature.methodReturnLength; memcpy(retLoc, self.retValue, retSize); } - (void)setReturnValue:(void *)retLoc { + if (!retLoc || !self.retValue) { + return; + } NSUInteger retSize = self.methodSignature.methodReturnLength; - BOOL result = [self _retainPointer:retLoc encode:self.methodSignature.methodReturnType key:@-1]; - if (!result) { - memcpy(self.retValue, retLoc, retSize); + if (self.isArgumentsRetained) { + [self _retainPointer:retLoc encode:self.methodSignature.methodReturnType key:@-1]; } + memcpy(self.retValue, retLoc, retSize); } - (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx { + if (!argumentLocation || !self.args || !self.args[idx]) { + return; + } void *arg = self.args[idx]; - assert(arg); const char *type = [self.methodSignature getArgumentTypeAtIndex:idx]; NSUInteger argSize; NSGetSizeAndAlignment(type, &argSize, NULL); @@ -380,15 +388,17 @@ - (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx - (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx { + if (!argumentLocation || !self.args || !self.args[idx]) { + return; + } void *arg = self.args[idx]; - assert(arg); const char *type = [self.methodSignature getArgumentTypeAtIndex:idx]; NSUInteger argSize; NSGetSizeAndAlignment(type, &argSize, NULL); - BOOL result = [self _retainPointer:argumentLocation encode:type key:@(idx)]; - if (!result) { - memcpy(arg, argumentLocation, argSize); + if (self.isArgumentsRetained) { + [self _retainPointer:argumentLocation encode:type key:@(idx)]; } + memcpy(arg, argumentLocation, argSize); } #pragma mark - Private Helper @@ -396,7 +406,7 @@ - (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx - (BOOL)_retainPointer:(void **)pointer encode:(const char *)encode key:(NSNumber *)key { void *p = *pointer; - if (p == NULL) { + if (!p) { return NO; } if (encode[0] == '@') { @@ -413,7 +423,7 @@ - (BOOL)_retainPointer:(void **)pointer encode:(const char *)encode key:(NSNumbe char *arg = p; NSMutableData *data = [NSMutableData dataWithLength:sizeof(char) * strlen(arg)]; self.retainMap[key] = data; - char *str = [data mutableBytes]; + char *str = data.mutableBytes; strcpy(str, arg); *pointer = str; return YES; @@ -585,7 +595,7 @@ - (void *)_allocate:(size_t)howmuch { NSMutableData *data = [NSMutableData dataWithLength:howmuch]; [self.allocations addObject:data]; - return [data mutableBytes]; + return data.mutableBytes; } - (ffi_type *)_ffiTypeForStructEncode:(const char *)str diff --git a/BlockHookSampleTests/BlockHookSampleTests.m b/BlockHookSampleTests/BlockHookSampleTests.m index e9a7cb9..c91ad81 100644 --- a/BlockHookSampleTests/BlockHookSampleTests.m +++ b/BlockHookSampleTests/BlockHookSampleTests.m @@ -66,7 +66,10 @@ struct TestStruct (^StructReturnBlock)(int) = ^(int x) }; [StructReturnBlock block_hookWithMode:BlockHookModeAfter usingBlock:^(BHInvocation *invocation, int x){ - (*(struct TestStruct *)(invocation.retValue)).a = 100; + struct TestStruct ret; + [invocation getReturnValue:&ret]; + ret.a = 100; + [invocation setReturnValue:&ret]; NSAssert(x == 8, @"Wrong arg!"); }]; @@ -82,7 +85,10 @@ - (void)testStructPointerReturn { }; [StructReturnBlock block_hookWithMode:BlockHookModeAfter usingBlock:^(BHInvocation *invocation){ - (**(struct TestStruct **)(invocation.retValue)).a = 100; + struct TestStruct *ret; + [invocation getReturnValue:&ret]; + ret->a = 100; + [invocation setReturnValue:&ret]; }]; __unused struct TestStruct *result = StructReturnBlock(); @@ -114,7 +120,10 @@ - (void)testCGRectArgAndRet { [StructReturnBlock block_hookWithMode:BlockHookModeBefore usingBlock:^(BHInvocation *invocation, CGRect test){ // Hook 改参数 - (*(CGRect *)(invocation.args[1])).origin.x = 100; + CGRect arg; + [invocation getArgument:&arg atIndex:1]; + arg.origin.x = 100; + [invocation setArgument:&arg atIndex:1]; }]; StructReturnBlock((CGRect){1,2,3,4}); } @@ -127,7 +136,10 @@ - (void)testStructPointerArg { [StructReturnBlock block_hookWithMode:BlockHookModeBefore usingBlock:^(BHInvocation *invocation, struct TestStruct test){ // Hook 改参数 - (**(struct TestStruct **)(invocation.args[1])).a = 100; + struct TestStruct *arg; + [invocation getArgument:&arg atIndex:1]; + arg->a = 100; + [invocation setArgument:&arg atIndex:1]; }]; StructReturnBlock(&_testRect); } @@ -153,7 +165,7 @@ - (void)testProtocol { }; const char *fakeResult = "lalalala"; [protocolBlock block_hookWithMode:BlockHookModeAfter usingBlock:^(BHInvocation *invocation, id delegate, int(^block)(int x, int y)){ - *(const char **)(invocation.retValue) = fakeResult; + [invocation setReturnValue:(void *)&fakeResult]; }]; id z = [NSObject new]; __unused const char *result = protocolBlock(z, block); @@ -170,15 +182,18 @@ - (void)testHookBlock { }; __unused BHToken *tokenDead = [block block_hookWithMode:BlockHookModeDead usingBlock:^(BHInvocation *invocation){ - // BHToken is the only arg. + // BHInvocation is the only arg. NSLog(@"block dead! token:%@", invocation.token); }]; BHToken *tokenInstead = [block block_hookWithMode:BlockHookModeInstead usingBlock:^(BHInvocation *invocation, int x, int y){ [invocation invokeOriginalBlock]; - NSLog(@"let me see original result: %d", *(int *)(invocation.retValue)); + int ret = 0; + [invocation getReturnValue:&ret]; + NSLog(@"let me see original result: %d", ret); // change the block imp and result - *(int *)(invocation.retValue) = x * y; + ret = x * y; + [invocation setReturnValue:&ret]; NSLog(@"hook instead: '+' -> '*'"); }]; @@ -186,11 +201,13 @@ - (void)testHookBlock { __unused BHToken *tokenAfter = [block block_hookWithMode:BlockHookModeAfter usingBlock:^(BHInvocation *invocation, int x, int y){ // print args and result - NSLog(@"hook after block! %d * %d = %d", x, y, *(int *)(invocation.retValue)); + int ret = 0; + [invocation getReturnValue:&ret]; + NSLog(@"hook after block! %d * %d = %d", x, y, ret); }]; __unused BHToken *tokenBefore = [block block_hookWithMode:BlockHookModeBefore usingBlock:^(BHInvocation *invocation){ - // BHToken has to be the first arg. + // BHInvocation has to be the first arg. NSLog(@"hook before block! invocation:%@", invocation); }]; @@ -217,25 +234,30 @@ - (void)testRemoveAll { }; [block block_hookWithMode:BlockHookModeDead usingBlock:^(BHInvocation *invocation){ - // BHToken is the only arg. + // BHInvocation is the only arg. NSLog(@"block dead! token:%@", invocation.token); }]; [block block_hookWithMode:BlockHookModeInstead usingBlock:^(BHInvocation *invocation, int x, int y){ [invocation invokeOriginalBlock]; - NSLog(@"let me see original result: %d", *(int *)(invocation.retValue)); + int ret = 0; + [invocation getReturnValue:&ret]; + NSLog(@"let me see original result: %d", ret); // change the block imp and result - *(int *)(invocation.retValue) = x * y; + ret = x * y; + [invocation setReturnValue:&ret]; NSLog(@"hook instead: '+' -> '*'"); }]; [block block_hookWithMode:BlockHookModeAfter usingBlock:^(BHInvocation *invocation, int x, int y){ // print args and result - NSLog(@"hook after block! %d * %d = %d", x, y, *(int *)(invocation.retValue)); + int ret = 0; + [invocation getReturnValue:&ret]; + NSLog(@"hook after block! %d * %d = %d", x, y, ret); }]; [block block_hookWithMode:BlockHookModeBefore usingBlock:^(BHInvocation *invocation){ - // BHToken has to be the first arg. + // BHInvocation has to be the first arg. NSLog(@"hook before block! invocation:%@", invocation); }]; @@ -262,7 +284,7 @@ - (void)testOverstepArgs }; __unused BHToken *tokenDead = [block block_hookWithMode:BlockHookModeDead usingBlock:^(BHInvocation *invocation, int a){ - // BHToken is the only arg. + // BHInvocation is the only arg. NSLog(@"block dead! token:%@", invocation.token); NSAssert(a == 0, @"Overstep args for DeadMode not pass!."); }]; @@ -271,9 +293,12 @@ - (void)testOverstepArgs __unused BHToken *tokenInstead = [block block_hookWithMode:BlockHookModeInstead usingBlock:^(BHInvocation *invocation, int x, int y, int a){ [invocation invokeOriginalBlock]; - NSLog(@"let me see original result: %d", *(int *)(invocation.retValue)); + int ret = 0; + [invocation getReturnValue:&ret]; + NSLog(@"let me see original result: %d", ret); // change the block imp and result - *(int *)(invocation.retValue) = x * y; + ret = x * y; + [invocation setReturnValue:&ret]; NSLog(@"hook instead: '+' -> '*'"); NSAssert(a == 0, @"Overstep args for DeadMode not pass!."); }]; @@ -310,24 +335,27 @@ - (void)testMultiModeHook { }; BHToken *token = [block block_hookWithMode:BlockHookModeDead|BlockHookModeBefore|BlockHookModeInstead|BlockHookModeAfter usingBlock:^(BHInvocation *invocation, int x, int y) { + int ret = 0; + [invocation getReturnValue:&ret]; switch (invocation.mode) { case BlockHookModeBefore: - // BHToken has to be the first arg. + // BHInvocation has to be the first arg. NSLog(@"hook before block! invocation:%@", invocation); break; case BlockHookModeInstead: [invocation invokeOriginalBlock]; - NSLog(@"let me see original result: %d", *(int *)(invocation.retValue)); + NSLog(@"let me see original result: %d", ret); // change the block imp and result - *(int *)(invocation.retValue) = x * y; + ret = x * y; + [invocation setReturnValue:&ret]; NSLog(@"hook instead: '+' -> '*'"); break; case BlockHookModeAfter: // print args and result - NSLog(@"hook after block! %d * %d = %d", x, y, *(int *)(invocation.retValue)); + NSLog(@"hook after block! %d * %d = %d", x, y, ret); break; case BlockHookModeDead: - // BHToken is the only arg. + // BHInvocation is the only arg. NSLog(@"block dead! token:%@", invocation.token); break; default: @@ -358,11 +386,12 @@ - (void)testSyncInterceptor { }; [testblock block_interceptor:^(BHInvocation *invocation, IntercepterCompletion _Nonnull completion) { - __unused NSObject *arg = (__bridge NSObject *)*(void **)(invocation.args[1]); + NSObject * __unsafe_unretained arg; + [invocation getArgument:&arg atIndex:1]; NSAssert(arg == testArg, @"Sync Interceptor wrong argument!"); - *(void **)(invocation.args[1]) = (__bridge void *)(testArg1); + [invocation setArgument:(void *)&testArg1 atIndex:1]; completion(); - *(void **)(invocation.retValue) = (__bridge void *)ret1; + [invocation setReturnValue:(void *)&ret1]; }]; NSObject *result = testblock(testArg); @@ -382,11 +411,13 @@ - (void)testAsyncInterceptor { [testblock block_interceptor:^(BHInvocation *invocation, IntercepterCompletion _Nonnull completion) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - __unused NSObject *arg = (__bridge NSObject *)*(void **)(invocation.args[1]); + NSObject * __unsafe_unretained arg; + [invocation getArgument:&arg atIndex:1]; NSAssert(arg == testArg, @"Async Interceptor wrong argument!"); - *(void **)(invocation.args[1]) = (__bridge void *)(testArg1); + [invocation setArgument:(void *)&testArg1 atIndex:1]; completion(); - *(void **)(invocation.retValue) = (__bridge void *)([NSObject new]); + NSObject *ret = [NSObject new]; + [invocation setReturnValue:(void *)&ret]; [expectation fulfill]; }); }]; @@ -407,14 +438,16 @@ - (void)testSyncCharArgInterceptor { }; [testblock block_interceptor:^(BHInvocation *invocation, IntercepterCompletion _Nonnull completion) { - __unused char *arg = *(char **)(invocation.args[1]); + char *arg; + [invocation getArgument:&arg atIndex:1]; NSAssert(strcmp(arg, "origin") == 0, @"Sync Char Arg Interceptor wrong argument!"); - *(void **)(invocation.args[1]) = (void *)("hooked"); + char *hooked = "hooked"; + [invocation setArgument:(void *)&hooked atIndex:1]; completion(); - *(void **)(invocation.retValue) = (__bridge void *)ret1; + [invocation setReturnValue:(void *)&ret1]; }]; - NSObject *result = testblock(origChar); + __unused NSObject *result = testblock(origChar); origChar[1] = '1'; free(origChar); NSAssert(result == ret1, @"Sync Char Arg Interceptor change return value failed!"); @@ -431,11 +464,14 @@ - (void)testAsyncCharArgInterceptor { [testblock block_interceptor:^(BHInvocation *invocation, IntercepterCompletion _Nonnull completion) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - __unused char *arg = *(char **)(invocation.args[1]); + char *arg; + [invocation getArgument:&arg atIndex:1]; NSAssert(strcmp(arg, "origin") == 0, @"Async Char Arg Interceptor wrong argument!"); - *(void **)(invocation.args[1]) = (void *)("hooked"); + char *hooked = "hooked"; + [invocation setArgument:(void *)&hooked atIndex:1]; completion(); - *(void **)(invocation.retValue) = (__bridge void *)([NSObject new]); + NSObject *ret = [NSObject new]; + [invocation setReturnValue:(void *)&ret]; [expectation fulfill]; }); }]; @@ -461,11 +497,15 @@ struct TestStruct (^StructReturnBlock)(NSObject *) = ^(NSObject *a) }; [StructReturnBlock block_interceptor:^(BHInvocation *invocation, IntercepterCompletion _Nonnull completion) { - __unused NSObject *arg = (__bridge NSObject *)*(void **)(invocation.args[1]); + NSObject * __unsafe_unretained arg; + [invocation getArgument:&arg atIndex:1]; NSAssert(arg == testArg, @"Sync Interceptor wrong argument!"); - *(void **)(invocation.args[1]) = (__bridge void *)(testArg1); + [invocation setArgument:(void *)&testArg1 atIndex:1]; completion(); - (*(struct TestStruct *)(invocation.retValue)).a = 100; + struct TestStruct ret; + [invocation getReturnValue:&ret]; + ret.a = 100; + [invocation setReturnValue:&ret]; }]; __unused struct TestStruct result = StructReturnBlock(testArg); @@ -486,11 +526,15 @@ struct TestStruct (^StructReturnBlock)(NSObject *) = ^(NSObject *a) [StructReturnBlock block_interceptor:^(BHInvocation *invocation, IntercepterCompletion _Nonnull completion) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - __unused NSObject *arg = (__bridge NSObject *)*(void **)(invocation.args[1]); + NSObject * __unsafe_unretained arg; + [invocation getArgument:&arg atIndex:1]; NSAssert(arg == testArg, @"Sync Interceptor wrong argument!"); - *(void **)(invocation.args[1]) = (__bridge void *)(testArg1); + [invocation setArgument:(void *)&testArg1 atIndex:1]; completion(); - (*(struct TestStruct *)(invocation.retValue)).a = 100; + struct TestStruct ret; + [invocation getReturnValue:&ret]; + ret.a = 100; + [invocation setReturnValue:&ret]; [expectation fulfill]; }); }]; diff --git a/README.md b/README.md index e636dd8..f33a593 100644 --- a/README.md +++ b/README.md @@ -67,25 +67,27 @@ int(^block)(int x, int y) = ^int(int x, int y) { }; BHToken *token = [block block_hookWithMode:BlockHookModeDead|BlockHookModeBefore|BlockHookModeInstead|BlockHookModeAfter usingBlock:^(BHInvocation *invocation, int x, int y) { - NSLog(@"block dead! token:%@", invocation.token); + int ret = 0; + [invocation getReturnValue:&ret]; switch (invocation.mode) { case BlockHookModeBefore: - // BHToken has to be the first arg. + // BHInvocation has to be the first arg. NSLog(@"hook before block! invocation:%@", invocation); break; case BlockHookModeInstead: [invocation invokeOriginalBlock]; - NSLog(@"let me see original result: %d", *(int *)(invocation.retValue)); + NSLog(@"let me see original result: %d", ret); // change the block imp and result - *(int *)(invocation.retValue) = x * y; + ret = x * y; + [invocation setReturnValue:&ret]; NSLog(@"hook instead: '+' -> '*'"); break; case BlockHookModeAfter: // print args and result - NSLog(@"hook after block! %d * %d = %d", x, y, *(int *)(invocation.retValue)); + NSLog(@"hook after block! %d * %d = %d", x, y, ret); break; case BlockHookModeDead: - // BHToken is the only arg. + // BHInvocation is the only arg. NSLog(@"block dead! token:%@", invocation.token); break; default: @@ -135,7 +137,10 @@ NSObject *(^testblock)(NSObject *) = ^(NSObject *a) { [testblock block_interceptor:^(BHInvocation *invocation, IntercepterCompletion _Nonnull completion) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - *(void **)(invocation.args[1]) = (__bridge void *)(testArg1); + NSObject * __unsafe_unretained arg; + [invocation getArgument:&arg atIndex:1]; + NSLog(@"Original argument:%@", arg); + [invocation setArgument:(void *)&testArg1 atIndex:1]; completion(); }); }];