文章分享至我的个人技术博客:https://cainluo.github.io/15034036545472.html
在前一章里, 我们把RunTime
的一些基础概念和一些小东西给弄明白了, 正式踏入装逼队伍行列.
如果没有加入到装逼队伍行列里的小伙伴, 可以去看看.
转载声明:如需要转载该文章, 请联系作者, 并且注明出处, 以及不能擅自修改本文.
objc_msgSend的使用
在前面一篇文章里, 我们用Clang
把RunTimeModel.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"#importstatic 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] TestModel2017-08-23 00:09:38.236 1.RunTime[35345:2926512] TestModel2017-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
在目录中, 我并没有放到工程里.