玩轉iOS開發:iOS開發中的裝逼技術 - RunTime(二)

文章分享至個人我的技術博客:https://cainluo.github.io/15034036545472.htmlhtml


在前一章裏, 咱們把RunTime的一些基礎概念和一些小東西給弄明白了, 正式踏入裝逼隊伍行列.git

若是沒有加入到裝逼隊伍行列裏的小夥伴, 能夠去看看玩轉iOS開發:iOS開發中的裝逼技術 - RunTime(一).github

轉載聲明:如須要轉載該文章, 請聯繫做者, 而且註明出處, 以及不能擅自修改本文.vim


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);
}
複製代碼

打印的結果:ui

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, 依然能夠調用.atom

再看看代碼, 咱們還會發現, 這裏的objc_msgSend作了一個強轉的操做, 若是咱們把那個強轉幹掉的話, Xcode就會報錯:spa

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: 小明
複製代碼

工程地址

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

注意: TestModel.cpp在目錄中, 我並無放到工程裏.


最後

碼字很費腦, 看官賞點飯錢可好

微信

支付寶
相關文章
相關標籤/搜索