面试经典——有意思的Runtime(USE)

2023-11-12

分类(Category):

    在不子类化的情况下,为已经存在的类增加功能;

    分类中的方法会成为类的组成部分,并且会被子类继承;

 

扩展:

    是一种匿名分类,可以声明实例变量、属性和方法,我们常见的.m文件中@interface的一段就是一个扩展;

    通常用在类实现的.m文件中,声明私有的实例变量、属性和方法;

 

扩展和分类的区别在于 他可以声明实例变量和属性;

 

在runtime中Category定义的是如下的结构体:

 

Category

typedef struct objc_category * Categoy;

 

struct objc_category{

    char * category_name;

    char * class_name;

    struct objc_method_list * instance_methods;

    struct objc_method_list * class_methods;

    struct objc_protocol_list * protocols;

}

    我们注意到Category是一个执行该结构体的指针,和我们之前见到的Object、Class类似;

    该结构体中包含了 对象方法列表,类方法列表,协议列表,相应的Category支持添加对象方法、类方法和协议,但不能保存成员变量;

 

如果想在Category中添加可用的属性可以使用 关联对象:

    在分类中加入属性,虽然运行没有问题,但是不会生成对应的成员变量,getter和setter也没有;调用的时候也会报错;

    使用关联对象在分类的实现文件.m中,使用objc_setAssociatedObject和objc_getAssociatedObject即可实现;

    而且该属性还可以被子类调用;关联对象会在对象释放时,由Runtime查找并释放,不需要手动释放;

 


 

我们使用了runtime通过关联对象在分类中增加了属性,接下来看看runtime一些其他的操作;

 

Method Swizzling方法交换:

    这个可以在程序运行时,修改一个方法的实现;下面示例我们要修改doSomethingSwizzled方法的实现;

1)动态添加方法-替换:

 

2)方法存在-直接交换:

 

为了保证方法只会被交换一次,可以把这个放在只会调用一次的方法中,然后再放到dispatch_once里;

(RSSwizzle可以使这个过程更简单)

 


 

打印类的属性、方法、成员变量和遵循的协议: 

【Code-MyObject】(代码包含了方法替换的部分,关注最下边的实现就好)

#import <Foundation/Foundation.h>

@protocol MyObjectDelegate <NSObject>

-(void)MyObjectDelegateMethod;

@end

@interface MyObject : NSObject

@property (nonatomic , copy) NSString * pro1;
@property (nonatomic , copy) NSString * pro2;
@property (nonatomic , copy) NSString * pro3;

-(void)doSomethingOriginal;
-(void)doSomethingSwizzled;
-(void)method_swizzling;

-(void)printPropertyMethodIvarList;

@end
#import "MyObject.h"
#import "objc/runtime.h"

@interface MyObject()<MyObjectDelegate>{
    int _ivar1;
    int _ivar2;
    int _ivar3;
}

@end

@implementation MyObject

-(void)MyObjectDelegateMethod{
    
}

-(void)doGreeting{
    NSLog(@"%@",@"Hello");
}
-(void)doSomethingOriginal{
    NSLog(@"%@",@"doSomethingOriginal");
}
-(void)doSomethingSwizzled{
    NSLog(@"%@",@"doSomethingSwizzled");
}
-(void)method_swizzling{
    Method greeting = class_getInstanceMethod([self class], @selector(doGreeting));
    
    Method original = class_getInstanceMethod([self class], @selector(doSomethingOriginal));
    Method swizzled = class_getInstanceMethod([self class], @selector(doSomethingSwizzled));
    
    if (!class_addMethod([self class], @selector(doSomethingOriginal), method_getImplementation(greeting), method_getTypeEncoding(greeting))) {
        NSLog(@"由于方法已存在 添加失败 直接交换(这样做是为了避免父类方法与子类方法交换带来的问题)");
        method_exchangeImplementations(original, swizzled);
    }else{
        NSLog(@"方法不存在 添加方法成功 此添加方法的实现来自greeting 使用添加的方法 替换doSomethingSwizzled方法的实现");
        original = class_getInstanceMethod([self class], @selector(doSomethingOriginal));
        class_replaceMethod([self class], @selector(doSomethingSwizzled), method_getImplementation(original), method_getTypeEncoding(original));
    }
}

-(void)printPropertyMethodIvarList{
    NSLog(@"_________________打印属性_____________________");

    {
        unsigned int count;
        objc_property_t * propertyList = class_copyPropertyList([self class], &count);
        for (unsigned int i = 0; i < count; i++) {
            const char * propertyName = property_getName(propertyList[i]);
            NSLog(@"%@",[NSString stringWithUTF8String:propertyName]);
        }
        free(propertyList);
    }
    NSLog(@"_________________打印方法_____________________");

    {
        unsigned int count;
        Method * methodList = class_copyMethodList([self class], &count);
        for (unsigned int i = 0; i < count; i++) {
            Method method = (methodList[i]);
            NSLog(@"%@",NSStringFromSelector(method_getName(method)));
        }
        free(methodList);
        
    }
    NSLog(@"_________________打印变量_____________________");

    {
        unsigned int count;
        Ivar * ivarList = class_copyIvarList([self class], &count);
        for (unsigned int i = 0; i < count; i++) {
            Ivar ivar = (ivarList[i]);
            const char * ivarName = ivar_getName(ivar);
            NSLog(@"%@",[NSString stringWithUTF8String:ivarName]);
        }
        free(ivarList);
        
    }
    NSLog(@"_________________打印协议_____________________");

    {
        unsigned int count;
        
        __unsafe_unretained Protocol ** protocolList = class_copyProtocolList([self class], &count);
        for (unsigned int i = 0; i < count; i++) {
            Protocol * protocol = (protocolList[i]);
            const char * protocolName = protocol_getName(protocol);
            NSLog(@"%@",[NSString stringWithUTF8String:protocolName]);
        }
        free(protocolList);
        
    }
}

@end

log:

2019-01-29 11:21:37.617924+0800 test[94980:4682027] _________________打印属性_____________________
2019-01-29 11:21:37.618056+0800 test[94980:4682027] testP
2019-01-29 11:21:37.618138+0800 test[94980:4682027] pro1
2019-01-29 11:21:37.618224+0800 test[94980:4682027] pro2
2019-01-29 11:21:37.618293+0800 test[94980:4682027] pro3
2019-01-29 11:21:37.618381+0800 test[94980:4682027] hash
2019-01-29 11:21:37.618475+0800 test[94980:4682027] superclass
2019-01-29 11:21:37.618544+0800 test[94980:4682027] description
2019-01-29 11:21:37.618613+0800 test[94980:4682027] debugDescription
2019-01-29 11:21:37.618693+0800 test[94980:4682027] _________________打印方法_____________________
2019-01-29 11:21:37.618774+0800 test[94980:4682027] printPropertyMethodIvarList
2019-01-29 11:21:37.618856+0800 test[94980:4682027] setTestP:
2019-01-29 11:21:37.618925+0800 test[94980:4682027] testP
2019-01-29 11:21:37.619000+0800 test[94980:4682027] doGreeting
2019-01-29 11:21:37.619076+0800 test[94980:4682027] doSomethingOriginal
2019-01-29 11:21:37.619191+0800 test[94980:4682027] doSomethingSwizzled
2019-01-29 11:21:37.619361+0800 test[94980:4682027] MyObjectDelegateMethod
2019-01-29 11:21:37.619463+0800 test[94980:4682027] method_swizzling
2019-01-29 11:21:37.619624+0800 test[94980:4682027] pro1
2019-01-29 11:21:37.619809+0800 test[94980:4682027] setPro1:
2019-01-29 11:21:37.621500+0800 test[94980:4682027] pro2
2019-01-29 11:21:37.621576+0800 test[94980:4682027] setPro2:
2019-01-29 11:21:37.621633+0800 test[94980:4682027] pro3
2019-01-29 11:21:37.621710+0800 test[94980:4682027] setPro3:
2019-01-29 11:21:37.621784+0800 test[94980:4682027] .cxx_destruct
2019-01-29 11:21:37.621862+0800 test[94980:4682027] _________________打印变量_____________________
2019-01-29 11:21:37.621966+0800 test[94980:4682027] _ivar1
2019-01-29 11:21:37.622067+0800 test[94980:4682027] _ivar2
2019-01-29 11:21:37.622127+0800 test[94980:4682027] _ivar3
2019-01-29 11:21:37.622201+0800 test[94980:4682027] _pro1
2019-01-29 11:21:37.622273+0800 test[94980:4682027] _pro2
2019-01-29 11:21:37.622338+0800 test[94980:4682027] _pro3
2019-01-29 11:21:37.622499+0800 test[94980:4682027] _________________打印协议_____________________
2019-01-29 11:21:37.622682+0800 test[94980:4682027] MyObjectDelegate

 

Runtime用途-AOP:

    可以提供对面向切面编程的支持(AOP);

    比如我们要统计所有的Btn点击事件,表示出当前点击按钮的currentTitle;

 

我们新建一个UIButton+aop的分类,来实现下,这样做技能对业务逻辑分离,也能降低耦合:

【Code-UIButton+aop】

#import "UIButton+aop.h"
#import "objc/runtime.h"

@implementation UIButton (aop)

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [[self class] method_swizzling];
    });
}
//-(void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
//    [super sendAction:action to:target forEvent:event];
//}
-(void)sendForAopAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
    NSString * title = [self currentTitle];
    NSLog(@"%@",title);
    
    [super sendAction:action to:target forEvent:event];
}

+(void)method_swizzling{
    Method tmp = class_getInstanceMethod([self class], @selector(sendForAopAction:to:forEvent:));
    
    Method original = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
    Method swizzled = class_getInstanceMethod([self class], @selector(sendForAopAction:to:forEvent:));
    
    if (!class_addMethod([self class], @selector(sendAction:to:forEvent:), method_getImplementation(tmp), method_getTypeEncoding(tmp))) {
        NSLog(@"由于方法已存在 添加失败 直接交换(这样做是为了避免父类方法与子类方法交换带来的问题)");
        method_exchangeImplementations(original, swizzled);
    }else{
        NSLog(@"方法不存在 添加方法成功 此添加方法的实现来自sendForAopAction:to:forEvent: 使用添加的方法 替换sendAction:to:forEvent:方法的实现");
        original = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
        class_replaceMethod([self class], @selector(sendForAopAction:to:forEvent:), method_getImplementation(original), method_getTypeEncoding(original));
    }
}
@end

点击按钮log如下(使用了两种方式):

 


 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

面试经典——有意思的Runtime(USE) 的相关文章

  • Gmail 中删除了 iOS 深层链接

    我正在尝试发送一封带有深层链接的电子邮件到我的 iOS 应用程序 使用 myapp 格式从电子邮件中将其打开 它可以在任何 iOS 邮件客户端 邮件 邮箱等 中运行 即点击它打开应用程序 但不能在 Gmail 应用程序 甚至网络 中运行 因
  • 无法为 FacebookSDK 4 构建模块“FBSDKCoreKit”

    我在将 Facebook SDK 3 更新到 4 时遇到问题 当我添加框架时 它已成功添加 但是当我添加头文件时出现错误 无法构建模块 FBSDKCoreKit 不知道原因 在设置中 允许非模块化包含在框架模块中 设置为YES在构建设置中解
  • Xcode 8:使用 iOS 9.3 基础 SDK 编译?

    我在 Xcode 8 0 beta 8S128d 中将 iOS 应用程序升级到 Swift 3 0 我以为一切都已准备就绪 并将其上传到 iTunes Connect 当我点击 提交审核 时 它给了我一个包含 26 个错误的列表 每个嵌入式
  • 隐藏故事板中的导航栏

    谁能告诉我如何隐藏故事板中的导航栏 我下面的代码在模拟器中运行时工作正常 但它仍然出现在我的故事板中 这真的很烦我 因为它弄乱了我的图像的位置 有人可以帮忙吗 void viewWillAppear BOOL animated super
  • 带有 Retina-Tiles 的 MKTileOverlay

    我在 MKMapKit 中加载 512x512px 图块时遇到问题 服务器提供 512x512 jpeg 图块 我在 MKMapView 中找不到自定义视网膜图块的任何解决方案或示例实现 我做什么 当我将它们加载到 MKMapView 中时
  • 在 iOS 中,如何以编程方式填写 pdf 表单字段?

    我需要获取一个现有的 pdf 文件 并以编程方式填写带有文本的表单字段列表 然后保存 pdf 而不将其显示给用户 例如 如果 pdf 文件包含名为 LastName 和 FirstName 的字段 我想将 FirstName 的值设置为 L
  • 如何从 UI 测试访问 App Delegate?

    我想访问特定的属性router在 UI 测试期间从启动的应用程序的应用程序委托中获取 但我不知道这是否可行 我努力了 let appDelegate UIApplication sharedApplication delegate as A
  • 如何从TableViewCell上的自定义CollectionViewCell推送VC?

    我有一个tableView和细胞 在细胞上我有一个collectionView并在其上显示一些内容 我想发送一个有关选择的链接indexPath 我想从自定义中推送 呈现我的视图CollectionViewCell这是在TableViewC
  • 如何将 Android 应用程序添加到已在 iOS 应用程序中使用的现有 Firebase 项目?

    我一直在我的 iOS 应用程序中使用 Firebase 项目 我现在想开始为 Android 应用程序使用相同的 Firebase 项目及其所有数据库和存储 在您的应用程序下Overview菜单 你应该按添加另一个应用程序并选择Androi
  • 如何本地化 iOS 故事板

    我有一个包含一些视图的 iPhone 故事板 例如 导航项标题名为News 应翻译为其他语言 当我向故事板添加新的本地化版本时 它会为新语言创建当前故事板的副本 在这里我可以更改导航项的标题 但对我来说它似乎不太有用 如果我的故事板包含 1
  • 如何将 NSMutableArray 添加到 NSMutableArray Objective-c

    我正在从 Java 切换到 Objective c 但遇到了一些困难 我已经搜索过这个问题但没有取得太大成功 我有一个存储 NSMutableArrays 的 NSMutableArray 如何向数组添加数组 您可以在数组中存储对另一个数组
  • 与 parse-server 和 auth0 的自定义身份验证集成

    我想将 auth0 com 与开源解析服务器结合使用 我当前的方法是通过 iOS 的 Lock 库使用标准登录从 auth0 获取令牌 使用该令牌 我想在解析服务器上调用自定义身份验证方法 该方法检查令牌是否有效 如果有效则将登录用户 我的
  • 单击按钮时退出应用程序 - iOS [重复]

    这个问题在这里已经有答案了 可能的重复 iOS 4 0 中退出应用程序 https stackoverflow com questions 3097244 exit application in ios 4 0 我有一个 AlertView
  • 在 Xcode 中查找未使用的文件

    我最近开始开发一个新应用程序 它基本上是我以前制作的应用程序的副本 但做了一些更改 为了制作这个新应用程序 我复制了旧应用程序并删除了一些不需要的内容 我想知道 有没有办法知道 Xcode 中正在使用哪些类文件 或者有什么关于如何查找未使用
  • gestureRecognizer shouldReceiveTouch 持续存在于已释放的视图中导致崩溃

    我有一个相当简单的 UITableView 它在堆栈上推送一个新视图 新视图有一个像这样初始化的gestureRecognizer synthesize swipeGestureLeft void viewDidLoad swipeGest
  • iOS 7 NS 单线程安全合并冲突

    重新排序两行后 在单线程应用程序上保存简单的数据时遇到问题 我已经成功地简化了编码以重现错误 并且希望其他人尝试这一点时得到第二个意见 这是一次健全性检查 因为我怀疑 iOS 7 引入的核心数据问题 而这在 iOS 6 中工作正常 首先 启
  • SDK 和 iOS 部署目标。

    我使用最新的 SDK 4 1 构建项目并设置 iOS 部署目标 3 0 如果我使用4 0 sdk的某些方法 我可以在真正的iPhone 3 0上运行我的项目吗 您只能在 OS 3 设备上运行您的项目 如果有条件地编码围绕您想要使用的 OS
  • 如何将nsmutable数组添加到sqlite数据库表中

    如何将nsmutablearray添加到sqlite数据库表中 有人可以帮我编码吗 您可以使用 for int i 0 i lt mutArray count i NSString string mutArray objectAtIndex
  • Swift - 保存在 TableView 中选择的复选标记

    我对 Swift 相当陌生 并且在 TableView 多重选择方面遇到问题 我有多个选择 可以用复选标记进行检查 类似于待办事项列表 当我检查项目时 我希望能够返回 ListView 并保存我的选择 我假设将其保持在已保存状态的代码将位于
  • Facebook 登录打开错误的应用程序

    我正在尝试使用 facebook 实现应用程序的登录 但每次我尝试登录时 它都建议打开错误的应用程序 我尝试了一些在这里找到的东西 但没有成功 在 Facebook 的开发者页面上我添加了一个后缀 我的 plist 如下 有谁知道发生了什么

随机推荐

  • Autofac与WebApi集成

    目录 Autofac原理 如何将Autofac与Web API集成 Autofac功能详解 1 HttpConfiguration 2 控制器 3 解析器 4 过滤器 1 注册过滤器提供程序 2 实现过滤器接口 3 注册过滤器 4 过滤器覆
  • React结合Ant Design Pro开发项目理解(service、model、index等文件逻辑关系)

    组件中传值 react 值写在了视图层 Antd pro 值写在model层 通过saveState扩展表达式 把值更新到state后会触发视图层的render方法 通过props获取model中的值 下图是整个项目各模块的关系 大概流程是
  • 基于AntDesign实现的React.js自定义可编辑表格,带翻译功能

    基于React AntDesign实现的一个自定义的可编辑表格 主要用于数据库表字段的编辑 带翻译功能 稍作修改后也可复用到其他地方 主要包括三个文件 index js EditableTable js EditableTable less
  • Matlab R2018b激活教程

    最近重装了系统 又正好需要用一下Matlab 所以就来研究一下如何激活 目前Matlab最新的版本是R2018b 所以我找的也是R2018b的激活教程 再次提醒 如果有条件请支持正版 资源准备 很幸运 我发现已经有人将Matlab R201
  • Linux文件操作命令及磁盘分区与文件系统

    一 cd命令 cd change directory 切换目录 这是用来切换工作目录的命令 注意目录名称和cd命令之间存在一个空格 在介绍cd命令的时候 首先我们来了解相对路径和绝对路径 绝对路径 路径的写法 一定由跟目录 写起 例如 da
  • QTabWidet设置不同Position,tabBar文字均水平显示

    设置QTabWidget在不同方向上的文字均水平显示 有两种方法 如下 1 设置不同Position tabBar文字均水平显示 QTabWidget设置在设置Position为East或者West时 文字方向默认会从下到上显示 先通过以下
  • Connection Refused Error:[WinError 10061]

    问题 Vscode左边栏选项 运行和调试 运行Python Django 程序报错 sock connect host port ConnectionRefusedError WinError 10061 由于目标计算机积极拒绝 无法连接
  • js数据类型学习笔记

  • Scala安装与环境配置

    一 Scala和Python java的区别 Scala是一门多范式编程语言 用于操作Spark 相较于java 由于Spark底层用Scala编写 因此对于大数据Spark项目场景Scala代码更加简洁 java过于冗长 并且Scala支
  • 自编码器(Auto Encoder)原理及其python实现

    目录 一 原理 二 为什么要使用自编码器 三 代码实现 1 原始自编码器 2 多层 堆叠 自编码器 3 卷积自编码器 4 正则自编码器 4 1稀疏自编码器 四 降噪自编码器 五 逐层贪婪训练堆叠自编码器 参考 一 原理 自编码器由两部分组成
  • Android-数据存储(上)

    一 Debug调试 1 跟踪程序的运行过程 找出问题出现的地方 更快的解决问题 bug 2 梳理已有功能代码的运行逻辑流程 二 数据存储 1 sp存储 2 手机内部存储 3 手机外部存储 三 练习
  • java中定义byte数组,浅谈java的byte数组的不同写法

    由于篇幅原因阐述的不够详细科学 不喜勿喷 经常看到java中对byte数组的不同定义 粗略整理的一下 一个字节 byte 8位 bit byte数组 里面全部是 byte 即每一个byte都可以用二进制 十六进制 十进制来表示 二进制 00
  • Unity——LitJSON的安装

    一 LitJSON介绍 特点 LitJSON是一个轻量级的C JSON库 用于在Unity游戏开发中进行JSON数据的序列化和反序列化操作 它提供了简单而高效的接口 帮助开发者处理JSON数据 以下是LitJSON库的一些主要特点和功能 1
  • 用面向对象方法进行俄罗斯方块游戏设计(持续更新中)

    游戏介绍 顾名思义 俄罗斯方块自然是俄罗斯人发明的 这位伟人叫做阿列克谢 帕基特诺夫 Alexey Pazhitnov 这款游戏操作简单 老少皆宜 也是一个不错的练手项目 首先给几个经典的游戏界面先 当然 我们的目的是做出类似的效果 游戏界
  • 女程序员的反思

    鉴于大家对此的热情 我增添些具体的开发感受 希望能帮到和我有一样困惑的姐妹们 也希望各路IT大牛前来指导 工作已有8个多月 不间断的大小项目共5个 一直觉得很疲惫 除了本应该烦累的工作外 自己性格上的各种不适应加重了疲惫感 可能是前一个项目
  • Buildroot用户指南

    第一章 关于Buildroot Buildroot是一个包含Makefile和修补程序 patch 的集合 这个集合可以使你很容易的为你的目标构建交叉工具链 cross compilationtoolchain 根文件系统 root fil
  • python解析excel文件

    通过openpyxl库解析excel文件 from openpyxl import load workbook class ParseExcel object 解析excel文件 def init self 获取excel路径 self d
  • iOS app上架图文教程及注意事项

    网上的ios app上架流程比较多 但基本上都不全 此文档按照步骤进行的 比较推荐 自己按照教程正式走了一遍发现了很多问题 很多都是遇到问题解决问题 整个流程所花费的流程不下一周时间 所以为了让大家了解苹果公司的相关规定 让兄弟们少走弯路
  • 剑指offer——day2

    题目一 思路 两次遍历 一次遍历出链表的元素个数 第二次依次插入数据 int reversePrint struct ListNode head int returnSize int i 0 struct ListNode cur head
  • 面试经典——有意思的Runtime(USE)

    分类 Category 在不子类化的情况下 为已经存在的类增加功能 分类中的方法会成为类的组成部分 并且会被子类继承 扩展 是一种匿名分类 可以声明实例变量 属性和方法 我们常见的 m文件中 interface的一段就是一个扩展 通常用在类