内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

详解iOS开发中的装逼技术RunTime(2)

2017-10-12 16:12 出处:清屏网 人气: 评论(0

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 来重构一下:

1

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")));
2

那么当我们调用 [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: 小明

工程地址

项目地址: github.com/CainRun/iOS…

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

分享给小伙伴们:
本文标签: RunTimeiOS开发

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号