面试经典——有意思的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) 的相关文章

  • 如何在 AVAudioSession 内使用 iPhone XS 中内置的立体声(2 通道)麦克风?

    我试图从 iPhone XS 的所谓立体声后置麦克风获取两个通道 但在不同位置只能看到一个通道AVAudioSession and AVAudioSessionPortDescription与后置摄像头相关联 我尝试过使用AVAudioSe
  • 部署目标是什么意思?

    这是我假设的一个非常简单的问题 有人可以告诉我部署目标是什么意思吗 如果我选择 iOS 10 是否意味着只有 iOS 10 的用户才能下载该应用程序 选择较低的部署目标是否不好 另外 继续部署目标 是否不建议在较低的部署目标上运行 假设您已
  • 删除 UICollectionView 中的最后一个单元格会导致崩溃

    您好 我正在使用自定义 UICollectionView https github com SureCase WaterfallCollectionView https github com SureCase WaterfallCollec
  • iOS 中如何清除特定域的 cookie?

    我已经搜索了 StackOverflow 上的几乎所有问题来寻找我的问题的答案 我还没有找到任何有用的链接或教程来说明哪种方式最好清除特定域的 cookie 如果有人可以帮助我 请 我自己找到了解决方案 如果您想删除 UIWebView 中
  • 从数组中获取随机字符串[重复]

    这个问题在这里已经有答案了 我试图从数组 firstArray 中获取随机字符串并将其打印在 UILabel label 中 我似乎无法弄清楚并且出现错误 感谢您的帮助 我尝试搜索但找不到任何最新的教程 方法 import UIKit cl
  • UITextView 动画更改框架不会动画文本重新分配

    我有一个 UITextView 我试图在用户点击按钮时为框架的变化设置动画 基本上 文本视图会变大以适应屏幕 以便可以显示更多文本 然后当用户再次点击按钮时 它会缩小到原始框架 我使用块执行动画 如下所示 if isDisplayingDe
  • 将第 3 方库 ZXing 导入 Xcode

    我尝试了多种方法将第 3 方库 ZXing 导入我的 iOS 应用程序 但所有方法都很痛苦 或者根本不起作用 如果有人可以建议我做错了什么 或者提出导入 ZXing 等库的更好方法 我将非常感激 一定比这个容易 这就是我所做的 结果是 My
  • 自定义字体显示在 IB 中,但不显示在模拟器中

    我已经设置了一个UITextView and a UILabel使用自定义字体 它是垂直镜像的蒙古文字体 但我还添加了英文文本 以便您可以看到效果 这些文字显示在 Interface Builder 中 但在模拟器中大部分字符都在UITex
  • iPad 照片选择器崩溃

    我正在使用以下函数根据 UIActionSheet 的结果激活设备相机或图像选择器 如果 fromCamera YES 那么它适用于 iPhone 和 iPad 如果 fromCamera NO 那么它可以在 iPhone 上运行并出现图像
  • SwiftUI 列表与右侧的部分索引?

    是否可以有一个在右侧有索引的列表 就像下面 SwiftUI 中的示例一样 我在 SwiftUI 中做了这个 Contacts swift TestCalendar Created by Christopher Riner on 9 11 2
  • 本地化现有的 iOS 应用程序

    我不敢相信以前没有人问过这个问题 要么是我的编码实践太无组织性 要么是我没有使用正确的关键字 How can I localize an existing iOS app that does not use NSLocalizedStrin
  • 如何打开定位服务

    当有人第一次拒绝时 如何从实际应用程序重新打开定位服务 我可以选择关闭或打开它 您只能提示他们在屏幕上打开定位服务 如下所示 UIApplication sharedApplication openURL NSURL URLWithStri
  • 打乱 NSMutableArray 而不重复并显示在 UIButton 中

    在我看来 我有 12 个按钮 一个数组包含 6 个名称 我想在其中打印数组名称UIButton标题 这是我的代码 texts NSMutableArray alloc initWithObjects 1 2 3 4 5 6 nil UIBu
  • 在横向中自动调整 UITableCells 内容的大小

    在 UITableView 中 我通过 UILabels 将内容添加到单元格中 定义最佳尺寸 与单元格宽度允许的一样大 我注意到只有tableView contentSize width是可靠的 因为cell contentView bou
  • UIScrollView setZoomScale 将应用的旋转设置回零

    我已经从事地图替换工作很长一段时间了 整个事情的工作原理是UIScrollView由一个支持CATiledLayer 为了旋转我的地图 我旋转图层本身 使用CATransform3DMakeRotation 到目前为止效果很好 但如果我打电
  • 当地图视图只是屏幕的一部分时,如何在 iOS 模拟器中进行捏合?

    我在 iPad 上有一个视图 我正在添加MKMapView也就是说 全屏高度的一半 然而 当我尝试在 iOS 模拟器上进行捏合时 它不起作用 因为 to nubs 填充了模拟器上的整个 iPad 视图 And so with the map
  • 在 Xcode 5 中重命名 iOS 项目[重复]

    这个问题在这里已经有答案了 我需要重命名一个 iOS 项目 有没有办法在不开始一个全新项目的情况下做到这一点 我发现的所有其他信息都与 Xcode 4 或旧版本相关 这些方法似乎使项目崩溃 我在尝试任何名称更改之前创建了一个快照 在 Xco
  • 在 Swift 中从 UIScrollView 创建 PDF 文件

    我想从 UIScrollView 的内容创建一个 PDF 文件 func createPdfFromView aView UIView saveToDocumentsWithFileName fileName String let pdfD
  • 如何接收有关与我共享的记录中所做更改的 CloudKit 通知?

    我有两个 iCloud 帐户 A and B 在两个不同的设备上 来自其中之一 A 我将 ckrecord 分享给另一个人 B 像这样 let controller UICloudSharingController controller p
  • Swift 中的 import 语句是否有相关成本?

    阅读字符串宣言 我看到一个段落 https github com apple swift blob master docs StringManifesto md batteries included关于避免Foundation不需要的时候导

随机推荐

  • 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的一段就是一个扩展 通常用在类