Tôi đang thực hiện một "Mã Injector Class", mà thông qua phương pháp swizzling có thể cung cấp cho bạn khả năng để làm điều gì đó như thế này:Swizzling một phương pháp với đối số thay đổi và chuyển tiếp tin nhắn - Bad Truy cập
FLCodeInjector *injector = [FLCodeInjector injectorForClass:[self class]];
[injector injectCodeBeforeSelector:@selector(aSelector:) code:^{
NSLog(@"This code should be injected");
}];
aSelector
có thể là một phương thức có số lượng đối số biến và kiểu trả về biến. Đối số/và kiểu trả về có thể là các đối tượng hoặc kiểu nguyên thủy.
Trước tiên, tôi đính kèm mã của injectCodeBeforeSelector:
để cho bạn hiểu những gì tôi đang làm (Tôi đã gỡ bỏ không phần thú vị của mã):
- (void)injectCodeBeforeSelector:(SEL)method code:(void (^)())completionBlock
{
NSString *selector = NSStringFromSelector(method);
[self.dictionaryOfBlocks setObject:completionBlock forKey:selector];
NSString *swizzleSelector = [NSString stringWithFormat:@"SWZ%@", selector];
// add a new method to the swizzled class
Method origMethod = class_getInstanceMethod(self.mainClass, NSSelectorFromString(selector));
const char *encoding = method_getTypeEncoding(origMethod);
[self addSelector:NSSelectorFromString(swizzleSelector) toClass:self.mainClass methodTypeEncoding:encoding];
SwizzleMe(self.mainClass, NSSelectorFromString(selector), NSSelectorFromString(swizzleSelector));
}
-(void)addSelector:(SEL)selector toClass:(Class)aClass methodTypeEncoding:(const char *)encoding
{
class_addMethod(aClass,
selector,
(IMP)genericFunction, encoding);
}
Về cơ bản, tôi sử dụng class_addMethod để thêm giả/sự gian lận phương pháp đến lớp đích, sau đó thực hiện sự lộn xộn. Việc thực hiện phương pháp này được thiết lập để một chức năng như thế này:
id genericFunction(id self, SEL cmd, ...) {
// call the block to inject
...
// now forward the message to the right method, since method are swizzled
// I need to forward to the "fake" selector SWZxxx
NSString *actualSelector = NSStringFromSelector(cmd);
NSString *newSelector = [NSString stringWithFormat:@"SWZ%@", actualSelector];
SEL finalSelector = NSSelectorFromString(newSelector);
// forward the argument list
va_list arguments;
va_start (arguments, cmd);
return objc_msgSend(self, finalSelector, arguments);
}
bây giờ vấn đề: Tôi có một EXC_BAD_INSTRUCTION (objc_msgSend_corrupt_cache_error()) trên dòng cuối cùng. Sự cố xảy ra nếu tôi chuyển tiếp đối số va_list tới bộ chọn giả. Nếu tôi thay đổi dòng cuối cùng thành
return objc_msgSend(self, cmd, arguments);
không có lỗi, nhưng rõ ràng là một đệ quy vô hạn bắt đầu.
Tôi đã cố gắng để:
- sử dụng va_copy
- loại bỏ các sự gian lận trước khi gửi thông điệp
nhưng không có kết quả. Tôi nghĩ rằng vấn đề có liên quan đến thực tế này: va_list không phải là một con trỏ đơn giản, nó có thể là một cái gì đó tương tự như một bù đắp tương đối với địa chỉ ngăn xếp của phương pháp. Vì vậy, tôi không thể gọi objc_msgsend của một hàm (hàm swizzled) với danh sách arg của một hàm khác (cái không bị quấy rối).
Tôi đã cố gắng thay đổi cách tiếp cận và sao chép tất cả đối số trong NSInvocation, nhưng tôi đã gặp phải các vấn đề khác quản lý giá trị trả về của lời gọi và thậm chí sao chép từng đối số (quản lý tất cả các loại khác nhau). Tôi thích quay lại phương pháp này, điều đó có vẻ sạch hơn đối với tôi (imho)
Bạn có gợi ý gì không? Cảm ơn
cảm ơn cả hai, cả hai câu trả lời rất hữu ích, tôi sẽ đánh dấu điều này là chính xác vì có một lời giải thích cộng với một ví dụ thú vị. Vì những câu trả lời này, tôi đã quyết định thử lại với NSInvocation, tôi sẽ đăng một câu hỏi khác về nó. Cảm ơn một lần nữa – LombaX
Nếu có ai quan tâm, đây là liên kết cho phiên bản đầu tiên của lớp trên github: https://github.com/lombax85/FLCodeInjector – LombaX