面试题答案
一键面试一、Objective-C运行时(Runtime)机制深入阐述
- 运行时本质
- Objective - C是一门动态语言,其运行时(Runtime)是一个用C和汇编语言编写的库。它提供了一种在运行时动态加载类、方法,动态关联对象和方法等功能的机制。
- 在编译阶段,编译器会将Objective - C的方法调用转化为
objc_msgSend
函数调用,这个函数会在运行时根据对象的类信息找到对应的方法实现并执行。
- 类结构
- 每个类在运行时都有一个
objc_class
结构体实例,它包含了类的元数据,如类名、超类指针、类的属性列表、方法列表、协议列表等。例如:
- 每个类在运行时都有一个
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if!__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
- 方法查找与消息发送
- 当向一个对象发送消息时,首先会在对象的类的方法缓存(
objc_cache
)中查找,如果找到则直接调用。 - 如果缓存中未找到,会在类的方法列表中查找。若类的方法列表中没有找到,会沿着继承体系向超类的方法列表中查找。
- 如果一直到根类
NSObject
都没找到,就会进入动态方法解析阶段。
- 当向一个对象发送消息时,首先会在对象的类的方法缓存(
二、利用运行时机制优化应用性能
- 动态方法解析
- 原理:当运行时在方法列表中找不到对应的方法实现时,会给类一次动态添加方法实现的机会。在
+ (BOOL)resolveInstanceMethod:(SEL)sel
(实例方法)或+ (BOOL)resolveClassMethod:(SEL)sel
(类方法)方法中,可以通过class_addMethod
函数动态添加方法实现。 - iOS开发场景应用:在一些情况下,某些方法可能在编译时不确定是否需要实现,但在运行时根据具体条件才确定。比如在一个地图应用中,可能根据用户当前所在地区决定是否需要加载特定的地图图层绘制方法,通过动态方法解析可以避免在编译时就加载不必要的代码,从而减少应用的启动时间和内存占用。
- 原理:当运行时在方法列表中找不到对应的方法实现时,会给类一次动态添加方法实现的机会。在
- 消息转发
- 备用接收者:如果动态方法解析没有处理消息,运行时会询问对象是否有其他对象能处理该消息,通过
- (id)forwardingTargetForSelector:(SEL)aSelector
方法返回一个备用接收者,如果返回非nil
对象,则会向该对象发送消息。 - 完整转发:如果没有备用接收者,运行时会进入完整转发阶段。首先会调用
- (void)forwardInvocation:(NSInvocation *)anInvocation
方法,在这个方法中可以手动设置消息的接收者、方法等信息来处理消息。如果此方法也未处理,最终会调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
来获取方法签名,如果获取不到签名,就会抛出unrecognized selector sent to instance
异常。 - iOS开发场景应用:在开发框架时,可能希望对一些未实现的方法进行统一处理,避免应用崩溃。比如在一个基础库中,对某些可能在不同业务场景下有不同实现的方法,通过消息转发机制可以在运行时灵活处理,提高框架的通用性和健壮性。
- 备用接收者:如果动态方法解析没有处理消息,运行时会询问对象是否有其他对象能处理该消息,通过
三、通过运行时机制实现对项目中所有ViewController的生命周期方法进行统一埋点统计
- 方法交换
- 利用
method_exchangeImplementations
函数来交换两个方法的实现。例如,要统计UIViewController
的viewDidLoad
方法调用次数,可以先创建一个自定义的统计方法,然后与viewDidLoad
方法交换实现。 - 代码示例:
- 利用
#import <objc/runtime.h>
@implementation UIViewController (LifeCycleHook)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(viewDidLoad);
SEL swizzledSelector = @selector(swizzled_viewDidLoad);
Method originalMethod = class_getInstanceMethod(self, originalSelector);
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
BOOL didAddMethod =
class_addMethod(self,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(self,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)swizzled_viewDidLoad {
// 埋点统计逻辑,例如记录调用时间等
NSLog(@"viewDidLoad called at %@", [NSDate date]);
[self swizzled_viewDidLoad];
}
@end
- 遍历所有ViewController
- 可以通过
UIWindow
的rootViewController
来递归遍历所有的UIViewController
,在UIApplicationDidFinishLaunching
通知中启动遍历,这样可以确保在应用启动后对所有的ViewController
进行埋点统计的初始化。 - 代码示例:
- 可以通过
#import "AppDelegate.h"
#import <objc/runtime.h>
@interface UIViewController (LifeCycleHook)
@end
@implementation UIViewController (LifeCycleHook)
+ (void)load {
// 方法交换代码同上述
}
- (void)swizzled_viewDidLoad {
// 埋点统计逻辑
NSLog(@"viewDidLoad called at %@", [NSDate date]);
[self swizzled_viewDidLoad];
}
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self traverseViewControllers:self.window.rootViewController];
return YES;
}
- (void)traverseViewControllers:(UIViewController *)viewController {
if (viewController) {
// 这里可以对viewController进行埋点统计相关的初始化操作
NSLog(@"Traversing view controller: %@", viewController);
if ([viewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navController = (UINavigationController *)viewController;
for (UIViewController *childVC in navController.viewControllers) {
[self traverseViewControllers:childVC];
}
} else if ([viewController isKindOfClass:[UITabBarController class]]) {
UITabBarController *tabController = (UITabBarController *)viewController;
for (UIViewController *childVC in tabController.viewControllers) {
[self traverseViewControllers:childVC];
}
} else {
for (UIViewController *childVC in viewController.childViewControllers) {
[self traverseViewControllers:childVC];
}
}
}
}
@end
通过上述方法交换和遍历ViewController
的方式,就可以在大型iOS项目中利用运行时机制对所有ViewController
的生命周期方法进行统一埋点统计。