返回顶部
首页 > 资讯 > 精选 >IOS开发Objective-C Runtime如何使用
  • 685
分享到

IOS开发Objective-C Runtime如何使用

2023-07-05 03:07:14 685人浏览 薄情痞子
摘要

这篇文章主要介绍“iOS开发Objective-C Runtime如何使用”,在日常操作中,相信很多人在ioS开发Objective-C Runtime如何使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法

这篇文章主要介绍“iOS开发Objective-C Runtime如何使用”,在日常操作中,相信很多人在ioS开发Objective-C Runtime如何使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”IOS开发Objective-C Runtime如何使用”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

前言

Runtime 是使用 C 和汇编实现的运行时代码库,Objective-C 中有很多语言特性都是通过它来实现。了解 Runtime 开发可以帮助我们更灵活的使用 Objective-C 这门语言,我们可以将程序功能推迟到运行时再去决定怎么做,还可以利用 Runtime 来解决项目开发中的一些设计和技术问题,使开发过程更加具有灵活性。

一些关键字

  • self:类的隐藏参数变量,指向当前调用方法的对象

  • super:是编译器的标示符,通过 super 调用方法会被翻译成 objc_msgSendSuper(self, _cmd,…)

  • SEL:以方法名为内容的 C 字符串

  • IMP:指向方法实现的函数指针

  • id:指向类对象或实例对象的指针

  • isa:为 id 对象所属类型 (objc_class),Objc 中的继承就是通过 isa 指针找到 objc_class,然后再通过 super_class 去找对应的父类

  • metaclass:在 Objc 中,类本身也是对象,实例对象的 isa 指向它所属的类,而类对象的 isa 指向元类 (metaclass),元类的 isa 直接指向根元类,根元类的isa指向它自己,它们之间的关系如下图所示。

IOS开发Objective-C Runtime如何使用

消息传递 (Messaging)

Objective-C 对于调用对象的某个方法这种行为叫做给对象发送消息,实际上就是沿着它的 isa 指针去查找真正的函数地址。下面我们来了解一下这个过程:

我们写一个给对象发送消息的代码

[array insertObject:obj atIndex:5];

编译器首先会将上面代码翻译成这种样子

objc_msgSend(array, @selector(insertObject:atIndex:), obj, 5);

系统在运行时会通过 array 对象的 isa 指针找到对应的 class(如果是给类发消息,则找到的是metaclass),然后在 class 的 cache 方法列表中用 SEL 去找对应 method,如果找不到便去 class 的方法列表中去找,如果在方法列表中也找不对对应 method 时,便沿着继承体系继续向上查找,找到后将 method 放入 cache,以便下次能快速定位,然后再去执行 method 的 IMP,找不到时系统便报错:unrecognized selector sent to insertObject:atIndex:

Runtime 提供了三种方法避免因为找不到方法而崩溃

当找不到方法实现时,Runtime 会先发送 +resolveInstanceMethod: 或 +resolveClaSSMethod: 消息,我们可以重写它然后为对象指定一个处理方法。

void dynamicXXXMethod(id obj, SEL _cmd) {    NSLog(@"ok...");}+ (BOOL)resolveInstanceMethod:(SEL)aSEL {    if(aSEL == @selector(xxx:)) {        class_addMethod([self class], aSEL, (IMP)dynamicXXXMethod, "v@:");        return YES;    }    return [super resolveInstanceMethod];}

class_addMethod 方法的最后一个参数用来指定所添加方法的参数及返回值,叫 Type Encodings。

如果 resolve 方法返回 NO,Runtime 会发送 -forwardingTargetForSelector: 消息,允许我们将消息转发给能处理它的其它对象。

- (id)forwardingTargetForSelector:(SEL)aSelector {    if(aSelector == @selector(xxx:)){        return otherObject;    }    return [super forwardingTargetForSelector:aSelector];}

当 -forwardingTargetForSelector: 返回 nil 时,Runtime 会发送 -methodSignatureForSelector: 和 -forwardInvocation: 消息。我们可以选择忽略消息、抛出异常、将消息转由当前对象或其它对象的任意消息来处理。

//根据 SEL 生成 NSInvocation 对象,然后再由 -forwardInvocation: 方法进行转发。- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];    if (!signature) {        signature = [otherObject instanceMethodSignatureForSelector:aSelector];    }    return signature;}- (void)forwardInvocation:(NSInvocation *)invocation {    SEL sel = invocation.selector;    if([otherObject respondsToSelector:sel]) {        [invocation invokeWithTarget:otherObject]; // 转发消息    }     else {        [self doesNotRecognizeSelector:sel]; // 抛出异常    }}

KVO

当我们为对象添加观察者后,Runtime 会在运行时创建这个对象所在类的子类,并且将该对象的 isa 指针指向这个子类,然后重写监听属性的 set 方法并在方法中调用 -willChangeValueForKey: 和 -didChangeValueForKey: 来通知观察者,所以如果直接修改实例变量便不会触发监听方法。当移除观察者后,Runtime 便会将这个子类删除。

所以 isa 指针并不总是指向实例对象所属的类,也有可能指向一个中间类,所以不能依靠它来确定类型,而是应该用 class 方法来确定实例对象的类。

关联对象 (Associated Objects)

在 CateGory 中可以为类添加实例方法或类方法,但是不支持添加实例变量,所以即使我们在 Category 中为类添加了 property,也不能直接使用它,Runtime 可以解决这个问题,我们只需要定义一个指针,然后通过 objc_setAssociatedObject 方法将指针与对象进行关联并指定内存管理方式,数据以 KeyValue 的形式存储在一个 HashMap 里。

Objc 中的类和对象都是结构体,Category 也是这样,定义的方法和属性在结构体中的存储,并在运行时按倒序添加到主类中(添加的方法会放在方法列表的上面),所以如果添加的方法与原类中的一样,那么在调用此方法时,优先找到的便是我们添加的这个方法。如果有多个 Category 添加同样名称的方法,那么这些方法在方法列表中的顺序取决于他们的编译顺序,也就是这些 Category 文件在 Compile Sources 中的顺序。

@interface NSObject (JC)@property (nonatomic, copy) NSString *ID;@end@implementation NSObject (JC)static const void *IDKey;- (NSString *)ID {    return objc_getAssociatedObject(self, &IDKey);}- (void)setID:(NSString *)ID {    objc_setAssociatedObject(self, &IDKey, ID, OBJC_ASSOCIATION_COPY_NONATOMIC);}@end

aop(Method Swizzling)

我们可以通过继承、Category、AOP 方式来扩展类的功能。

  • 继承比较适合在设计底层代码架构时使用,不适当的使用会让代码看起来很啰嗦,并且增加维护难度。

  • Category 适合为现有类添加方法。

  • 当需要修改现有类的方法并且拿不到源码时,继承和 AOP 都能解决问题,但是用 AOP 来解决代码耦合度更低。其实就算能拿到源码,往往直接去改源码也不是个好办法。

在 Objective-C 中,可以通过 Method Swizzling 技术来实现 AOP,下面我们通过交换两个方法的实现代码来向已存在的方法中添加其它功能。

#import <objc/runtime.h> @implementation UIViewController (Tracking) + (void)load {     static dispatch_once_t onceToken;     dispatch_once(&onceToken, ^{         Class aClass = [self class];         SEL originalSelector = @selector(viewWillAppear:);         SEL swizzledSelector = @selector(swizzled_viewWillAppear:);         Method originalMethod = class_getInstanceMethod(aClass, originalSelector);         Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);         // 如果要对类方法进行交换,使用下面注释的代码        // Class aClass = object_getClass((id)self);        //         // Method originalMethod = class_getClassMethod(aClass, originalSelector);        // Method swizzledMethod = class_getClassMethod(aClass, swizzledSelector); // 交换两个方法的实现 // 防止 aClass 不存在 originalSelector,所以添加一下试试,但指向地址为新方法地址        BOOL didAddMethod = class_addMethod(aClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));         if (didAddMethod) {         // 添加成功,说明 aClass 不存在 originalSelector,所以替换 swizzledSelector 的 IMP 为 originalMethod,实质上它们都指向 swizzledMethod            class_replaceMethod(aClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));         }         else {          // 添加失败,说明 aClass 存在 originalSelector,直接交换            method_exchangeImplementations(originalMethod, swizzledMethod);         }     }); } #pragma mark - Method Swizzling // 由于方法实现已经被交换,所以系统在调用 viewWillAppear: 时,实际上会调用 swizzled_viewWillAppear:- (void)swizzled_viewWillAppear:(BOOL)animated { // 下面代码表面上看起来会引起递归调用,由于函数实现已经被交换,实际上会调用 viewWillAppear:   [self swizzled_viewWillAppear:animated]; // 在原有基础上添加其它功能(写日志等)} @end

使用 Method Swizzling 需要注意下面几个问题

  • 需要在 +load 方法中执行 Method Swizzling,+initialize 方法有可能不会被调用

  • 避免父类与子类同时 hook 父类的某方法,避免不了时至少要保证不在 +load 方法中执行 super.load(),否则父类中的 +load 方法会被执行两次

  • 需要在 dispatch_once 中执行,避免因多线程等问题倒致的偶数次交换后失效的问题

  • 如果你用了 swizzled_viewWillAppear 作为方法名,那么如果你引用的第三方 SDK 中也用了这个方法名来做方法交换,那会造成方法的递归调用,所以你最好换一个不太会被重复使用的方法名,例如 mx_swizzled_viewWillAppear

  • 即便使用 mx_swizzled_viewWillAppear 尽量避免了与第三方库或自己项目中别的地方对 viewWillAppear 交换倒致的递归调用问题,仍然会存在调用顺序问题,解决办法就是在 Build Phases 中调整类文件的顺序

其它

我们可以通过 Runtime 特性来获得类的所有属性名称和类型,然后再通过 KVC 将 JSON 中的值填充给该类的对象。还可以在程序运行时为类添加方法或替换方法从而使对象能够更灵活的根据需要来选择实现方法。总之 Runtime 库就象一堆积木,只要发挥想象力便能实现各种各样的功能,但前提是你需要了解它。

到此,关于“IOS开发Objective-C Runtime如何使用”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

--结束END--

本文标题: IOS开发Objective-C Runtime如何使用

本文链接: https://lsjlt.com/news/349219.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • IOS开发Objective-C Runtime如何使用
    这篇文章主要介绍“IOS开发Objective-C Runtime如何使用”,在日常操作中,相信很多人在IOS开发Objective-C Runtime如何使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法...
    99+
    2023-07-05
  • IOS开发Objective-C Runtime使用示例详解
    目录前言一些关键字消息传递 (Messaging)KVO关联对象 (Associated Objects)AOP(Method Swizzling)其它前言 Runtime&nbs...
    99+
    2023-02-13
    Objective-C Runtime iOS开发
  • iOS开发之Objective-c的Runtime理解指南
    目录一、Runtime1、概念:2、特性:编写的代码具备有运行时、动态特性,从而衍生出 以下4、53、原理:Runtimer在Object-c的使用 程序在三个不同的层次上与运行时系...
    99+
    2024-04-02
  • 如何使用Redis和Objective-C开发缓存预热功能
    如何使用Redis和Objective-C开发缓存预热功能在开发互联网应用时,为了提高性能和响应速度,我们通常会使用缓存来存储频繁访问的数据。而缓存预热是一种常见的优化策略,通过预先将热门数据加载到缓存中,可以避免用户第一次访问时的等待时间...
    99+
    2023-10-22
    redis 缓存预热 Objective-C
  • 如何使用Redis和Objective-C开发实时地理位置跟踪功能
    如何使用Redis和Objective-C开发实时地理位置跟踪功能地理位置跟踪功能已成为许多应用程序不可或缺的一部分,例如实时位置共享、打车应用和社交媒体等。在移动应用程序中实现实时地理位置跟踪功能面临许多挑战,例如数据存储、定位服务和数据...
    99+
    2023-10-22
    redis Objective-C 实时地理位置跟踪
  • c开发如何使用redis
    c开发使用redis的示例:在linux下使用hiredis接口,hiredis是redis数据库的C接口,代码:#include "hiredis.h"#define NO_QFORKIMPL#pragma comment(lib,"hi...
    99+
    2024-04-02
  • 如何通过Objective-C的枚举学习iOS中位操作.md详解
    开篇 今天在修改项目的时候,看见enum中出现了<<操作符(位操作),之前对这个一直都不了解。这次趁着项目比较清闲,抽出时间来全面了解一下位操作。 位操作 位操作是对...
    99+
    2022-05-20
    objective-c 枚举 位操作
  • iOS开发中AvaudioPlayer怎么使用
    在iOS开发中,你可以使用AVAudioPlayer类来播放音频文件。下面是使用AVAudioPlayer的基本步骤:1. 导入AV...
    99+
    2023-09-13
    iOS
  • iOS开发中rangeOfString怎么使用
    rangeOfString是一个NSString类的方法,用于在字符串中查找指定的子字符串。它返回一个NSRange结构体,指示子字...
    99+
    2023-09-15
    iOS
  • iOS开发中nstimeinterval怎么使用
    NSTimeInterval是一个双精度浮点型的时间间隔,它表示自2001年1月1日午夜(GMT)以来的秒数。在iOS开发中,我们可...
    99+
    2023-08-24
    nstimeinterval
  • 如何开始使用C++进行Web开发?
    要使用 c++++ 进行 web 开发,需要使用支持 c++ web 应用程序开发的框架,如 boost.asio、beast 和 cpp-netlib。开发环境中,需要安装 c++ 编...
    99+
    2024-05-11
    c++ web开发 git
  • iOS 11开发中如何编写第一个iOS 11应用
    这篇文章主要介绍iOS 11开发中如何编写第一个iOS 11应用,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!编写第一个iOS 11应用本节将以一个iOS 11应用程序为例,为开发者讲解如何使用Xcode 9.0去创...
    99+
    2023-06-04
  • ios开发中如何使用navigationBar隐藏显示的过度
    这篇文章给大家分享的是有关ios开发中如何使用navigationBar隐藏显示的过度的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。navigationBar隐藏显示的过度相信在...
    99+
    2024-04-02
  • iOS开发中UITabBarController的使用示例
    首先我们看一下它的view层级图: - (BOOL)application:(UIApplication *)application didFinishLaunchingWith...
    99+
    2022-05-22
    iOS
  • iOS开发CGContextRef画图使用总结
    本文实例为大家汇总了iOS开发CGContextRef画图使用,供大家参考,具体内容如下 1.创建画布 CGContextRef ctx = UIGraphicsGetCurrent...
    99+
    2024-04-02
  • iOS开发CGContextRef画图怎么使用
    这篇“iOS开发CGContextRef画图怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“iOS开发CGContex...
    99+
    2023-06-30
  • 如何用C#开发WinForm
    这篇文章主要讲解了“如何用C#开发WinForm”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何用C#开发WinForm”吧!首先,你必须要初始化这个组件(见下面程序中初始化Label一样...
    99+
    2023-06-17
  • iOS开发之AssetsLibrary框架使用详解
    一、引言 AssetsLibrary框架是专门用来操作相册相关资源的一个框架,其是iOS4到iOS9之间常使用的一个框架,在iOS9之后,系统系统了Photos框架代替了Asset...
    99+
    2022-06-01
    iOS AssetsLibrary
  • iOS开发之UIMenuController使用示例详解
    目录简介接口介绍使用探索如何创建并显示 UIMenuController实现 Item 点击事件菜单 Item 太多???UIResponderStandardEditActions...
    99+
    2024-04-02
  • Vue3 如何去开发安卓 或者 ios
    Vue3 有没有一款好用的开发原生的工具 uniapp 我个人认为uniapp 适合开发小程序之类的,用这个去开发原生应用会存在一些问题 性能限制:由于 Uniapp 是通过中间层实现跨平台,应用在访问底层功能时可能存在性能损失。与原生开...
    99+
    2023-08-18
    android ios
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作