玩转iOS开发:iOS开发中的装逼技术 - RunTime(二)

news/2024/7/5 7:00:05 标签: runtime, 移动开发, c/c++

文章分享至我的个人技术博客:https://cainluo.github.io/15034036545472.html


在前一章里, 我们把RunTime的一些基础概念和一些小东西给弄明白了, 正式踏入装逼队伍行列.

如果没有加入到装逼队伍行列里的小伙伴, 可以去看看玩转iOS开发:iOS开发中的装逼技术 - RunTime(一).

转载声明:如需要转载该文章, 请联系作者, 并且注明出处, 以及不能擅自修改本文.


objc_msgSend的使用

在前面一篇文章里, 我们用ClangRunTimeModel.m文件重写了, 得到了RunTimeModel.cpp, 里面大多数都是C代码实现的.

那我们也可以仿着objc_msgSend来写写看, 工程仍然是之前的那个, 这里我们添加了一个用来测试的类:

#import "TestModel.h"

@implementation TestModel

- (void)country {
    NSLog(@"中国");
}

- (void)getProvince:(NSString *)provinceName {
    NSLog(@"%@", provinceName);
}

- (void)getCity:(NSString *)cityName
        station:(NSString *)stationName {
    
    NSLog(@"%@, %@", cityName, stationName);
}

- (NSString *)getWeather {
    
    return @"晴天";
}

@end
复制代码

调用:

- (void)test {
    
    TestModel *objct = [[TestModel alloc] init];
    
    ((void (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("country"));
    
    ((void (*) (id, SEL, NSString *)) objc_msgSend) (objct, sel_registerName("getProvince:"), @"广东省");
    
    ((void (*) (id, SEL, NSString *, NSString *)) objc_msgSend) (objct, sel_registerName("getCity:station:"), @"深圳市", @"世界之窗");
    
    NSString *weather = ((NSString* (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("getWeather"));
    
    NSLog(@"%@", weather);
}
复制代码

打印的结果:

2017-08-22 20:52:00.497 1.RunTime[34290:2794192] 中国
2017-08-22 20:52:00.497 1.RunTime[34290:2794192] 广东省
2017-08-22 20:52:00.497 1.RunTime[34290:2794192] 深圳市, 世界之窗
2017-08-22 20:52:00.498 1.RunTime[34290:2794192] 晴天
复制代码

这里看清楚咯, 我只是在TestModel.m文件里声明了方法, 但是通过objc_msgSend, 依然可以调用.

再看看代码, 我们还会发现, 这里的objc_msgSend做了一个强转的操作, 如果我们把那个强转干掉的话, Xcode就会报错:

Too many arguments to function call, expected 0, have 4.
复制代码

这个错误是根据你的方法参数大小来决定的.


objc_msgSendSuper

其实除开我们刚刚看到的objc_msgSend之外, 还有很多个, 比如:

  • objc_msgSend: 发送具有简单返回值的消息到类的实例.
  • objc_msgSend_fpret: 发送带有浮点返回值的消息到类的实例
  • objc_msgSend_stret: 将具有数据结构返回值的消息发送到类的实例
  • objc_msgSendSuper: 发送一个简单返回值的消息到类的实例的超类
  • objc_msgSendSuper_stret: 将具有数据结构返回值的消息发送到类的实例的超类

这里我们就重点说说objc_msgSendSuper, 它是在#import<objc/message.h>文件中, 被定义成:

OBJC_EXPORT id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
复制代码

平常我们在调用Super方法的时候, Runtime都会去调用objc_msgSendSuper, 比如:

[super methodName];
复制代码

我们可以在刚刚的TestModel里重写init方法, 并且打印一下:

- (instancetype)init {
    
    self = [super init];
    
    if (self) {
        
        NSLog(@"%@", [self class]);
        NSLog(@"%@", [super class]);
    }
    
    return self;
}
复制代码

写完之后, 我们可以用Clang来重构一下:

PS: 记得你在哪个文件夹里Clang重写, 那么新生成的文件就在哪里.

然后就在TestModel.cpp文件里找到:

NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ycmkjs0s48l_knc_xnscdqq00000gn_T_TestModel_ece3b7_mi_0, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));

NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ycmkjs0s48l_knc_xnscdqq00000gn_T_TestModel_ece3b7_mi_1, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("TestModel"))}, sel_registerName("class")));
复制代码

那么当我们调用[super methodName]的时候, Runtime就会转成objc_msgSendSuper, 它的过程是:

  • 先构造objc_super结构体
    • 第一个成员变量是self.
    • 第二个是(id)class_getSuperclass(objc_getClass(“TestModel”)).
  • 然后就是去超类里找到- (Class)class方法, 如果没有找到, 就会继续往上一层去找, 一直找到NSObject, 找到了之后, 内部就会使用objc_msgSend(objc_super->receiver, @selector(class))去调用, 这里就会和[self class]调用一样, 所以输出来的结果都是为TestModel.

对象关联

对象关联, 可以允许开发者对已存在的类的Category的类添加属性:

OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
复制代码
  • object: 是源对象
  • key: 是关联的键,
  • value: 被关联的对象
  • policy: 是一个枚举

policy枚举:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};
复制代码

如果我们要获取一个属性的话, 那就可以使用下面这个方法, 也是用刚刚关联的Key:

OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key) OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
复制代码

如果要删除一个被关联的对象, 只要设置一下objc_setAssociatedObject并且把对象设置为nil就好了:

objc_setAssociatedObject(self, AttributeKey, nil, OBJC_ASSOCIATION_COPY_NONATOMIC);
复制代码

如果我们使用objc_removeAssociatedObjects的话, 就会把所有关联的对象给全部移除:

OBJC_EXPORT void objc_removeAssociatedObjects(id object) OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
复制代码

我们直接来看代码吧:

#import "TestModel.h"

@interface TestModel (String)

@property (nonatomic, copy) NSString *testString;

@end
复制代码
#import "TestModel+String.h"
#import <objc/runtime.h>

static void *TestStringKey = &TestStringKey;

@implementation TestModel (String)

- (void)setTestString:(NSString *)testString {
    
    objc_setAssociatedObject(self, TestStringKey, testString, OBJC_ASSOCIATION_COPY);
}

- (NSString *)testString {
    
    return objc_getAssociatedObject(self, TestStringKey);
}

@end
复制代码

然后回到Controller里引入头文件, 在调用:

- (void)test {
    
    TestModel *objct = [[TestModel alloc] init];
    
    ((void (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("country"));
    
    ((void (*) (id, SEL, NSString *)) objc_msgSend) (objct, sel_registerName("getProvince:"), @"广东省");
    
    ((void (*) (id, SEL, NSString *, NSString *)) objc_msgSend) (objct, sel_registerName("getCity:station:"), @"深圳市", @"世界之窗");
    
    NSString *weather = ((NSString* (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("getWeather"));
    
    NSLog(@"%@", weather);
    
    objct.testString = @"小明";
    
    NSLog(@"Category: %@", objct.testString);
}
复制代码
2017-08-23 00:09:38.236 1.RunTime[35345:2926512] TestModel
2017-08-23 00:09:38.236 1.RunTime[35345:2926512] TestModel
2017-08-23 00:09:38.236 1.RunTime[35345:2926512] 中国
2017-08-23 00:09:38.237 1.RunTime[35345:2926512] 广东省
2017-08-23 00:09:38.237 1.RunTime[35345:2926512] 深圳市, 世界之窗
2017-08-23 00:09:38.237 1.RunTime[35345:2926512] 晴天
2017-08-23 00:09:38.237 1.RunTime[35345:2926512] Category: 小明
复制代码

工程地址

项目地址: https://github.com/CainRun/iOS-Project-Example/tree/master/RunTime/Two

注意: TestModel.cpp在目录中, 我并没有放到工程里.


最后

码字很费脑, 看官赏点饭钱可好


http://www.niftyadmin.cn/n/842064.html

相关文章

Android逆向工程 实践篇 三

今天就说下给应用去除广告把.在使用一个应用的时候有个广告弹出. 非常恶心. (有些应用是自带广告的, 有些是后来被人加进去的.) 怎么进去的都好. 只要我安装了那个应用, 觉得不爽. 我就立马把广告清除. 上一张没有去除广告的样子把.分析: 如何去除呢./? 类似这个类型的广告只需…

运行Vue在ASP.NET Core应用程序并部署在IIS上

前言 从.NET Core 1.0开始我们就将其应用到项目中&#xff0c;但是呢我对ASP.NET Core一些原理也还未开始研究&#xff0c;仅限于会用&#xff0c;不过园子中已有大量文章存在&#xff0c;借着有点空余时间&#xff0c;我们来讲讲如何利用ASP.NET Core结合Vue在IIS上运行。 ASP…

eclispe设置workspace text file encoding

在windows下开发&#xff0c;经常会遇到eclipse新导入的工程 java代码中的注释或者字符串中文显示乱码&#xff0c;每次都要一个个项目更改麻烦&#xff0c;特地找了下&#xff0c;可通过如下方法一次性设置。

Unable to process parts as no multi-part configuration has been provided错误的解决

出现Unable to process parts as no multi-part configuration has been provided错误&#xff0c;解决方法如下&#xff1a; 文件解析器的配置ID必须等于multipartResolver&#xff0c;不能是其他值

谈谈 js 深浅拷贝 那点事(一)

深拷贝与浅拷贝 不知道大家是咋理解的 有没有从内存空间角度去理解 前天看了一个内存空间 堆栈 下面卖弄下 在学习数据结构时候 堆栈是很熟悉了 在一端去对数据的操作 简单说 栈为自动分配内存空间 由系统自动释放堆是动态分配的内存 大小不定也不会自动释放 栈数据结构网上好的…

Kafka 学习笔记之 删除Topic

删除Topic 1. 显示所有Topic信息&#xff0c;testTopic是我们将要删除的Topic 2. 首先确认server.properties下面配置是否已经加上delete.topic.enabletrue 3. 我们来验证是否test topic已经被真正的删除了。 a. 验证Kafka log: b. 验证Zookeeper 连接Zookeeper zkCli.sh -serv…

编码对象

要被写入和读取的对象必须继承NSObject,&#xff0c;并且实现NSCoding关键还要实现NSCoding 的两个必要的方法public func encode(with aCoder: NSCoder)public init?(coder aDecoder: NSCoder) 格式如下&#xff1a; // 编码的时候调用这个方法func encode(with aCoder: NSCo…

物联网如何跳出“看起来很美”?

摘要&#xff1a; 物联网市场正处在大爆发的前夜。今年2月&#xff0c;思科宣布14亿美元收购物联网公司Jasper&#xff0c;4月底Cypress宣布以5.5亿美元收购博通的无线物联网业务&#xff0c;其后不到一周时间&#xff0c;微软宣布收购意大利物联网平台Solair。 物联网市场正处…