面试题答案
一键面试实现步骤
- 导入必要头文件:在项目中导入
#import <objc/runtime.h>
,这是进行方法交换等操作所必需的头文件。 - 创建一个分类(Category):为
UIViewController
创建一个分类,例如UIViewController+Hook.h
和UIViewController+Hook.m
。在分类中声明和实现要Hook的方法。// UIViewController+Hook.h #import <UIKit/UIKit.h> @interface UIViewController (Hook) - (void)hook_viewDidLoad; @end
// UIViewController+Hook.m #import "UIViewController+Hook.h" #import <objc/runtime.h> @implementation UIViewController (Hook) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(viewDidLoad); SEL swizzledSelector = @selector(hook_viewDidLoad); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } - (void)hook_viewDidLoad { // 这里先调用原方法 [self hook_viewDidLoad]; // 然后添加自定义功能 NSLog(@"Custom functionality in hook_viewDidLoad"); } @end
- 确保分类加载:
load
方法会在类被加载到内存时调用一次,通过dispatch_once
保证方法交换只执行一次。
注意事项
- 处理继承体系:由于分类会影响到整个类及其所有子类,在Hook方法时,要确保自定义功能不会对继承体系造成负面影响。例如,如果子类重写了
viewDidLoad
方法,也要保证能正确调用到Hook后的逻辑。在上述实现中,hook_viewDidLoad
先调用原方法,这样子类重写的viewDidLoad
也能正常执行其逻辑。 - 防止循环引用:在自定义功能部分,如果涉及到对象之间的强引用关系,要特别注意防止循环引用。例如,不要在
hook_viewDidLoad
中创建一个强引用self
的闭包或对象,避免造成内存泄漏。 - 方法签名一致性:在进行方法交换时,要确保交换的两个方法的参数列表和返回值类型一致,否则可能会导致程序崩溃。通过
method_getTypeEncoding
获取方法的类型编码,保证方法签名正确。 - 系统版本兼容性:iOS系统可能会对某些类和方法进行更新或修改,要测试Hook功能在不同iOS版本上的兼容性,确保功能正常。