首页 > 其他分享 >iOS开发基础108-Runtime

iOS开发基础108-Runtime

时间:2024-07-17 14:22:23浏览次数:14  
标签:selector self iOS class 108 void Runtime 方法 method

Objective-C 的 Runtime 是一个强大的特性,它让语言具备了很多灵活的动态能力。通过 Runtime,开发者可以在运行时解析类、方法、变量,并进行动态的消息传递、方法交换等操作。以下将详细介绍 Runtime,包括具体的应用场景和底层实现原理。

什么是 Runtime

Runtime 是 Objective-C 运行时库(objc-runtime),它为 Objective-C 提供了许多动态功能,包括:

  1. 消息传递(Message Passing)
  2. 动态方法解析(Dynamic Method Resolution)
  3. 方法交换(Method Swizzling)
  4. 动态对象创建(Dynamic Object Creation)
  5. 类与协议的操作(Class and Protocol Manipulation)

开发中的应用场景

1. 消息传递(Message Passing)

Objective-C 的方法调用实际上是通过发送消息完成的,而不是直接调用方法。

// 正常方法调用
[object method];

// 等价的消息传递
objc_msgSend(object, @selector(method));

2. 动态方法解析(Dynamic Method Resolution)

在运行时动态添加方法。可以在方法调用时决定该方法的具体实现。

void dynamicMethodIMP(id self, SEL _cmd) {
    NSLog(@"Dynamic method resolution");
}

@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(someMethod)) {
        class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end

3. 方法交换(Method Swizzling)

在运行时交换两个方法的实现,常用于方法拦截和日志记录。

@implementation UIViewController (Swizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod(self, @selector(viewDidLoad));
        Method swizzledMethod = class_getInstanceMethod(self, @selector(swizzled_viewDidLoad));
        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

- (void)swizzled_viewDidLoad {
    [self swizzled_viewDidLoad];
    // 追加自定义逻辑
    NSLog(@"viewDidLoad Swizzled");
}

@end

4. 动态对象创建

可以在运行时动态创建对象,包括类、实例变量和方法。

Class newClass = objc_allocateClassPair([NSObject class], "MyDynamicClass", 0);
class_addIvar(newClass, "_dynamicIvar", sizeof(id), log2(sizeof(id)), @encode(id));
class_addMethod(newClass, @selector(dynamicMethod), (IMP)dynamicMethodIMP, "v@:");
objc_registerClassPair(newClass);
id instance = [[newClass alloc] init];
[instance performSelector:@selector(dynamicMethod)];

5. 获取类和协议信息

获取类的所有属性、方法、成员变量等,并在需要时使用。

unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i++) {
    objc_property_t property = properties[i];
    const char* propertyName = property_getName(property);
    NSLog(@"Property: %s", propertyName);
}
free(properties);

分析底层原理

1. 消息传递的底层实现

消息传递采用 objc_msgSend 函数,它接收消息,并按照如下步骤处理:

  1. 在 Cache 中查找方法实现:每个类都有一个缓存,用于快速查找方法实现。
  2. 在类的方法列表中查找实现:在缓存未命中时,在类的方法列表中查找。
  3. 在父类中查找:若当前类未找到,在父类中递归查找。
  4. 动态方法解析:如果前几步全部失败,则会触发动态方法解析。
  5. 消息转发:如果找不到并且未解决,执行消息转发,最终抛出异常。
void objc_msgSend(id self, SEL _cmd) {
    // 伪代码示意
    Method method = cache_lookup(self, _cmd);
    if (!method) {
        method = class_lookup(self->isa, _cmd);
    }
    if (!method) {
        method = [self methodSignatureForSelector:_cmd];
    }
    if (!method) {
        // method forwarding
        [self forwardInvocation:...];
    }
    if (!method) {
        // throw exception: unrecognized selector
        [self doesNotRecognizeSelector:_cmd];
    }
}

2. 动态方法解析

动态方法解析允许你在消息传递机制中动态地提供方法实现。

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    // 当找不到某方法实现时,会调用此方法来尝试动态添加方法实现
}

3. 方法交换

方法交换使用 method_exchangeImplementations 函数:

void method_exchangeImplementations(Method m1, Method m2) {
    // 交换两个方法的实现
    IMP m1_imp = method_getImplementation(m1);
    IMP m2_imp = method_getImplementation(m2);
    method_setImplementation(m1, m2_imp);
    method_setImplementation(m2, m1_imp);
}

4. 动态对象创建

动态对象创建通过 objc_allocateClassPairobjc_registerClassPair 函数实现:

Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes);
void objc_registerClassPair(Class cls);

这些函数允许你在运行时创建新的类,并添加实例变量和方法。

5. 获取类和协议信息

获取类和协议信息使用了大量基于类型信息的函数,比如 class_copyPropertyList,这些函数返回一个数组,包含类的所有成员和方法信息。

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount);
Method *class_copyMethodList(Class cls, unsigned int *outCount);
Ivar *class_copyIvarList(Class cls, unsigned int *outCount);

其他场景

除了之前提到的消息传递、动态方法解析、方法交换等,Objective-C 的 Runtime 还在很多实用场景中得到了应用。以下是一些更实用且常见的应用场景:

1. 动态替换并增强类方法

通过方法交换,可以在不改变原有代码的基础上,为现有的方法增加功能,比如日志记录、性能监测等:

@implementation UIViewController (Logging)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod(self, @selector(viewDidAppear:));
        Method swizzledMethod = class_getInstanceMethod(self, @selector(logging_viewDidAppear:));
        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

- (void)logging_viewDidAppear:(BOOL)animated {
    [self logging_viewDidAppear:animated]; // 由于 method swizzling,实际上调用的是原来的 viewDidAppear:
    NSLog(@"View did appear: %@", NSStringFromClass([self class]));
}

@end

2. 自动解析字典到模型对象

通过 Runtime,可以自动将 JSON 字典解析成自定义的模型对象,从而减少重复的手动解析工作:

#import <objc/runtime.h>

@interface NSObject (Dictionary)

- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues;

@end

@implementation NSObject (Dictionary)

- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues {
    unsigned int outCount;
    objc_property_t *properties = class_copyPropertyList([self class], &outCount);
    for (int i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        const char *propName = property_getName(property);
        NSString *key = [NSString stringWithUTF8String:propName];
        id value = [keyedValues objectForKey:key];
        if (value) {
            [self setValue:value forKey:key];
        }
    }
    free(properties);
}

@end

3. 键值观察(KVO)原理探索及自定义

利用 Runtime 可以深入理解和优化 KVO 实现,比如避免过度嵌套和性能瓶颈。

#import <objc/runtime.h>

@implementation NSObject (KVO)

- (void)my_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
    // 为类动态创建子类并重写 setter 方法
    Class class = object_getClass(self);
    NSString *className = NSStringFromClass(class);
    NSString *subclassName = [NSString stringWithFormat:@"KVO_%@", className];
    Class subclass = NSClassFromString(subclassName);
    
    if (!subclass) {
        subclass = objc_allocateClassPair(class, subclassName.UTF8String, 0);
        objc_registerClassPair(subclass);
        Method setter = class_getInstanceMethod(class, @selector(setValue:forKey:));
        const char *types = method_getTypeEncoding(setter);
        class_addMethod(subclass, @selector(setValue:forKey:), (IMP)new_setter, types);
    }
    
    object_setClass(self, subclass);
    
    // 调用原始的 addObserver:forKeyPath:options:context:
    [self my_addObserver:observer forKeyPath:keyPath options:options context:context];
}

void new_setter(id self, SEL _cmd, id value, NSString *key) {
    struct objc_super superStruct = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    objc_msgSendSuper(&superStruct, _cmd, value, key);
    // 通知所有观察者
    NSLog(@"KVO new value for key: %@", key);
}

// 替换系统的 addObserver:forKeyPath:options:context: 方法
+ (void)load {
    Method originalMethod = class_getInstanceMethod([NSObject class], @selector(addObserver:forKeyPath:options:context:));
    Method swizzledMethod = class_getInstanceMethod([NSObject class], @selector(my_addObserver:forKeyPath:options:context:));
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

@end

4. 通过关联对象为类添加属性

有时需要在类别(Category)中为已有类添加属性,这可以通过关联对象实现:

#import <objc/runtime.h>

@interface NSObject (AssociatedObject)

@property (nonatomic, strong) id associatedObject;

@end

@implementation NSObject (AssociatedObject)

- (void)setAssociatedObject:(id)object {
    objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)associatedObject {
    return objc_getAssociatedObject(self, @selector(associatedObject));
}

@end

5. AOP(面向切面编程)

利用 Runtime 技术可以在不修改原有代码的情况下,对方法的调用进行拦截,并加入新的处理逻辑,这种技术在日志记录、性能监控、权限控制等场景中尤为有用。

#import <objc/runtime.h>

typedef void(^AspectBlock)(id<AspectInfo> aspectInfo);

@interface Aspects : NSObject

+ (void)hookSelector:(SEL)selector ofClass:(Class)klass withAspectBlock:(AspectBlock)block;

@end

@implementation Aspects

+ (void)hookSelector:(SEL)selector ofClass:(Class)klass withAspectBlock:(AspectBlock)block {
    Method originalMethod = class_getInstanceMethod(klass, selector);
    IMP originalIMP = method_getImplementation(originalMethod);

    void (^swizzleBlock)(id) = ^(id obj) {
        block((id<AspectInfo>)obj);
        ((void(*)(id, SEL))originalIMP)(obj, selector);
    };

    IMP swizzledIMP = imp_implementationWithBlock(swizzleBlock);
    method_setImplementation(originalMethod, swizzledIMP);
}

@end

// 使用示例
[Aspects hookSelector:@selector(viewWillAppear:) ofClass:[UIViewController class] withAspectBlock:^(id<AspectInfo> aspectInfo) {
    NSLog(@"View will appear: %@", aspectInfo);
}];

6. 自动追踪用户事件

在用户行为统计和分析中,利用 Runtime 可以自动追踪用户在应用中的各种操作,比如点击按钮、页面跳转等。

#import <objc/runtime.h>

@implementation UIButton (Tracking)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
        Method swizzledMethod = class_getInstanceMethod(self, @selector(tracking_sendAction:to:forEvent:));
        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

- (void)tracking_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    NSLog(@"Button clicked: %@", NSStringFromSelector(action));
    [self tracking_sendAction:action to:target forEvent:event]; // 实际上调用的是原始的 sendAction:to:forEvent:
}

@end

原码窥探其底层实现

Objective-C 的 Runtime 是其动态特性的核心,通过深入理解 Runtime 的底层实现,可以更好地理解和利用其强大功能。以下是对于 Runtime 的一些更深入的底层研究,涵盖类与对象的结构、消息传递机制、方法缓存等方面。

一、对象与类的结构

1. NSObject 的结构

在 Objective-C 中,所有对象都继承自 NSObjectNSObject 的核心结构包含 isa 指针,这是一个指向类对象的指针。

struct objc_object {
    Class isa;  // 指向对象所属类的虚拟表
};

struct objc_class {
    Class superclass;  // 指向父类的虚拟表
    Cache cache;       // 方法缓存
    MethodList *methods;   // 方法列表
    // 其他信息
};

2. isa 指针

isa 指针既指向类对象,也指向元类(Meta-class)。元类本身是一个类对象,但它存储的是类方法而不是实例方法。

// 示例图示
(MyClass isa -> MyClass)    // 实例对象指向类对象
(MyClass isa -> MetaClass)  // 类对象指向元类对象
(MetaClass isa -> MetaClass) // 元类对象指向根元类(MetaClass)对象本身

3. 类与元类的关系

每个类对象都有一个元类对象,这个元类对象存储了类方法。

class MyClass : NSObject {
    @classmethod
    func classMethod() {}
}

@implementation MyClass 
+ (void)initialize {
    if (self == [MyClass class]) {
        // 初始化类相关的静态数据
    }
}

二、消息传递机制

1. objc_msgSend 函数

Objective-C 的方法调用本质上是消息传递,通过 objc_msgSend 函数来实现:

void objc_msgSend(id self, SEL _cmd, ...) {
    // 伪代码表示
    Class cls = self->isa;
    IMP imp = lookupMethodInClass(cls, _cmd);
    if (imp) {
        return imp(self, _cmd, args...);
    } else {
        // 动态方法解析和消息转发
    }
}
IMP lookupMethodInClass(Class cls, SEL sel) {
    // 在缓存中查找方法
    IMP imp = cache_lookup(cls, sel);
    if (imp) return imp;

    // 递归查找父类
    while (cls) {
        MethodList *methods = cls->methods;
        for (int i = 0; i < methods->count; i++) {
            if (methods->method_list[i].name == sel) {
                return methods->method_list[i].imp;
            }
        }
        cls = cls->superclass;
    }
    return NULL;
}

2. 方法缓存

为了提高方法调用的效率,Objective-C 使用方法缓存来快速定位方法的实现。在类对象结构中,缓存用于存储最近使用的方法和响应的实现指针。

struct Cache {
    unsigned int mask;        // 缓存的大小掩码
    unsigned int occupied;    // 已使用缓存数量
    MethodCacheEntry *buckets;// 缓存桶
};

struct MethodCacheEntry {
    SEL sel;  // 方法选择器
    IMP imp;  // 方法实现指针
};

三、动态方法解析和消息转发

1. 动态方法解析

当一个类接收到一个未知的方法调用时,首先会通过动态方法解析机制来为其动态提供方法实现。

@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(dynamicMethod)) {
        class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void dynamicMethodIMP(id self, SEL _cmd) {
    NSLog(@"Dynamic method resolved.");
}
@end

2. 消息转发

如果动态方法解析也未能提供方法实现,则会进入消息转发阶段。消息转发有两步:

  1. 快速转发:重载 forwardingTargetForSelector: 方法,将消息转发给另一个对象。
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(someMethod)) {
        return anotherObject; // 转发目标对象
    }
    return [super forwardingTargetForSelector:aSelector];
}
  1. 慢速转发:重载 methodSignatureForSelector:forwardInvocation: 方法,构建 NSInvocation 对象并处理。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(someMethod)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = [anInvocation selector];
    if ([anotherObject respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:anotherObject];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

四、Method Swizzling 的底层实现

方法交换是 Objective-C 中一个强大的技术,它允许你在运行时交换两个方法的实现,常用于日志记录和方法增强。

void method_exchangeImplementations(Method m1, Method m2) {
    IMP imp1 = method_getImplementation(m1);
    IMP imp2 = method_getImplementation(m2);
    
    method_setImplementation(m1, imp2);
    method_setImplementation(m2, imp1);
}

五、内存管理与引用计数

1. 引用计数与自动引用计数(ARC)

Objective-C 使用引用计数来管理内存,ARC 是一种编译器特性,自动为代码插入 retainrelease

// 在 ARC 下,由编译器自动插入 retain 和 release
id obj = [[NSObject alloc] init]; // 增加引用计数
obj = nil;                        // 减少引用计数,释放内存

2. @autoreleasepool

在手动引用计数和 ARC 下,可以使用 @autoreleasepool 来管理临时对象的生命周期。

@autoreleasepool {
    // 临时对象在这里创建
    id tempObj = [[NSObject alloc] init];
    // 对象出了作用域会自动释放
}

这些是关于 Objective-C Runtime 的一些更深入的底层研究,通过了解这些底层机制,可以更好地理解和利用 Runtime,提高代码的灵活性、性能和可维护性。

总结

Objective-C 的 Runtime 提供了丰富的动态特性,使得开发者能够在运行时动态地操作对象、方法和类等。这种能力不仅增加了语言的灵活性,也带来了许多强大的功能,如动态方法解析、方法交换和消息传递等。在实际开发中,合理使用 Runtime 可以极大地提升应用的扩展性和灵活性,但要小心使用,避免引起难以调试的问题。

标签:selector,self,iOS,class,108,void,Runtime,方法,method
From: https://www.cnblogs.com/chglog/p/18307218

相关文章

  • iOS开发基础117-Hybrid
    HybridHybrid(混合)开发是一种结合了Web技术和原生应用开发技术的方法,旨在简化跨平台应用开发。通过Hybrid开发,开发者可以用HTML、CSS和JavaScript等前端技术编写代码,并将其运行在一个内嵌的浏览器环境中,从而实现跨平台的移动应用。什么是Hybrid开发?Hybrid开发主要是指将应用的用......
  • iOS开发基础116-性能监控
    在iOS开发中,性能监控是确保应用流畅运行和用户体验的关键。常用的性能监控工具能够帮助开发者实时监控系统性能,检测和诊断性能问题。下面列举几款常用的iOS性能监控工具,深入解析其底层原理、优缺点。1.InstrumentsInstruments是由Apple官方提供的用于性能分析和调试的工具。它......
  • iOS开发基础114-YYCache
    YYCache是一个高性能、易用的缓存组件,广泛用于iOS开发中。其设计宗旨是高效且灵活,可以处理不同类型的缓存需求。以下将介绍YYCache的常见应用场景,并深入分析其底层原理。应用场景1.图片缓存在展示大量图像的应用(比如社交媒体应用)中,缓存机制可以大幅减少网络请求,提升用户......
  • iOS开发基础115-Socket
    在现代网络编程中,Socket(套接字)是实现网络通信的主要机制。Socket提供了端到端的双向通信接口,使得不同主机上的进程能够通过网络直接通信。在iOS开发中,经常需要使用Socket进行网络请求、实时通信(如聊天、游戏等)。以下将详细介绍Socket的概念,并列举iOS开发中常用的三方Socket框架,深......
  • iOS开发基础113-Unity3D
    在iOS项目中接入Unity3D项目可以创建更复杂且互动性强的应用。Unity3D通常用于游戏开发,它可以与原生iOS项目进行集成。以下是详细的步骤和示例代码,且深入讨论其底层原理。步骤1.创建Unity3D项目打开Unity3D并创建一个新项目。完成项目场景和逻辑编写。在Unity3D项目中,设置i......
  • iOS开发基础112-GCD
    GrandCentralDispatch(GCD)在iOS中的常见运用场景GCD是Apple提供的多线程编程技术,旨在提供高效、轻量级的方式来执行并发任务。GCD使得管理线程变得简单且提高了应用程序的性能。以下是GCD在iOS中的一些常见运用场景,并详细介绍其底层原理。1.异步任务处理场景:网络请求使用GCD......
  • iOS开发基础110-Core Graphics应用场景
    CoreGraphics是一种强大的二维图形绘制框架,广泛应用于iOS开发中。以下是几个常见的运用场景以及对应的代码示例:1.自定义视图绘制通过覆盖UIView的drawRect:方法,可以自定义视图的外观。示例代码:#import<UIKit/UIKit.h>@interfaceCustomView:UIView@end@implementat......
  • iOS开发基础109-网络安全
    在iOS开发中,保障应用的网络安全是一个非常重要的环节。以下是一些常见的网络安全措施及对应的示例代码:Swift版1.使用HTTPS确保所有的网络请求使用HTTPS协议,以加密数据传输,防止中间人攻击。示例代码:在Info.plist中配置AppTransportSecurity(ATS):<key>NSAppTransportSecur......
  • 少儿编程启蒙宝典:Scratch动画游戏108变
    一、编程教育的时代价值与意义随着数字时代的深入发展,社会对人才的需求正发生深刻变革,计算思维与编程能力已成为衡量个人竞争力的重要指标。在此背景下,培养孩子们运用计算思维解决实际问题的能力,成为教育领域的重要任务。编程教育以其独特的优势,不仅能够锻炼学生的逻辑思维能......
  • iOS开发基础108-常见的编程范式
    1.面向过程编程(Process-OrientedProgramming,POP)代码示例(Swift)importUIKitclassViewController:UIViewController{overridefuncviewDidLoad(){super.viewDidLoad()printGreeting()printNumber(num:42)}/......