文章分享至個人我的技術博客:https://cainluo.github.io/15034036545472.htmlhtml
在前一章裏, 咱們把RunTime
的一些基礎概念和一些小東西給弄明白了, 正式踏入裝逼隊伍行列.git
若是沒有加入到裝逼隊伍行列裏的小夥伴, 能夠去看看玩轉iOS開發:iOS開發中的裝逼技術 - RunTime(一).github
轉載聲明:如須要轉載該文章, 請聯繫做者, 而且註明出處, 以及不能擅自修改本文.vim
在前面一篇文章裏, 咱們用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);
}
複製代碼
打印的結果: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_msgSend
以外, 還有不少個, 好比:
這裏咱們就重點說說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);
複製代碼
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
在目錄中, 我並無放到工程裏.