首先介紹一下本文的目錄結(jié)構(gòu)
一.Runtime認識
通常我們向一個類或者對象發(fā)送一個方法如:[object message]
其實都是轉(zhuǎn)為Runtime
的objc_msgSend(object, message)
實現(xiàn)。這點可以通過clang
查看OC語言底層實現(xiàn)過程如:
我們在一個工程中創(chuàng)建一個對象:
NSObject *object = [NSObject new];
通過clang -rewrite-objc main.m
編譯我們的main.m
文件可以得到一個main.cpp
C++文件,打開可以看到主要的實現(xiàn)代碼如下
可以看到程序使用了一個
objc_msgSend
方法,向NSObject
類發(fā)送了一個被注冊的new
消息。
使用到的方法如下:
objc_getClass(name)
:獲得name對應(yīng)的類名。
sel_registerName(name)
:返回類中注冊的方法選擇器。
關(guān)于前綴比如objc、class、sel、...
可以理解為:大部分關(guān)于類的操作是以class
開頭,對象的操作是以objc
或object
開頭,方法選擇器的操作是以sel
開頭。
二.模擬Runtime消息發(fā)送
現(xiàn)在我們通過使用objc_msgSend方法,來模擬Runtime
消息發(fā)送:
我們在項目中創(chuàng)建一個MyClass
類,并為其添加一個屬性name
如圖:
在main.m
文件中我們使用如下代碼來創(chuàng)建MyClass
實例、為其屬性賦值并打印屬性值
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *myClass = objc_msgSend(objc_getClass("MyClass"), sel_registerName("new"));
objc_msgSend(myClass, sel_registerName("setName:"), @"jack");
NSLog(@"%@", objc_msgSend(myClass, sel_registerName("name")));
}
return 0;
}
最終我們可以看到打印結(jié)果如下:
三.Runtime使用
1.為Category添加屬性(關(guān)聯(lián)對象)
一般情況下,如果在分類文件的.h
文件中添加一個@property
屬性只會在.m
文件中創(chuàng)建這個@property
的getter和setter聲明但是無法達到我們添加成員屬性的地步。關(guān)于這點可以查看深入理解Objective-C:Category。但是怎么創(chuàng)建成員屬性了?
首先我們可能會考慮到在分類中使用static
全局變量來達到這一效果,但是這種方式是獨立于實例的變量因此可以不用考慮。
第二種就是通過runtime使用關(guān)聯(lián)屬性添加屬性。例如:創(chuàng)建一個NSObject
的分類然后在分類中添加name
屬性
然后在
.m
文件中使用如下代碼創(chuàng)建關(guān)聯(lián)對象
const char myKey;
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, &myKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, &myKey);
}
值得注意的是,在objc_setAssociatedObject
方法中,最后一個枚舉參數(shù)objc_AssociationPolicy
中,有如下類型可供選擇
OBJC_ASSOCIATION_ASSIGN //相當于(assign)
OBJC_ASSOCIATION_RETAIN_NONATOMIC // 相當于(nonatomic, strong)
OBJC_ASSOCIATION_COPY_NONATOMIC // 相當于(nonatomic, copy)
OBJC_ASSOCIATION_RETAIN // 相當于(strong)
OBJC_ASSOCIATION_COPY // 相當于(copy)
在mian.m
文件中,我們就可以使用如下代碼,為NSObject
類添加屬性了
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *objc = [NSObject new];
objc.name = @"jack";
NSLog(@"%@", objc.name);
}
return 0;
}
2.方法交換(Method Swizzling)
在開發(fā)過程中,我們可能會考慮使用自己的方法來替換系統(tǒng)的方法。比如我遇到一個需求,要求是在- (void)viewWillDisappear:
方法中停止音頻播放,因為太多的控制器包含播放的view,如果每個控制器都寫中一下停止播放,估計整個項目文件都得改一遍了,任務(wù)繁重而復(fù)雜。
但是如果使用runtime
方法交換,修改系統(tǒng)方法實現(xiàn)可以讓程序變得非常簡單。
實現(xiàn)交換代碼如下:
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewDidDisappear:);
SEL swizzledSelector = @selector(fml_viewDidDisappear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
// 如果類中不存在要替換的方法,那就先用class_addMethod和class_replaceMethod函數(shù)添加和替換兩個方法的實現(xiàn)
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
// 如果類中已經(jīng)有了想要替換的方法,那么就調(diào)用method_exchangeImplementations函數(shù)交換了兩個方法的 IMP
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)fml_viewDidDisappear:(BOOL)animated
{
[self fml_viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter] postNotificationName:FMLResetAudioViewNotification object:nil];
}
首先我們對比看一下+ load
和+ initialize
兩個方法的區(qū)別:
-
+ load
方法會在程序加載進內(nèi)存就會調(diào)用,而且只會調(diào)用一次。 -
+ initialize
程序第一次被調(diào)用的時候調(diào)用,并且有可能不會被調(diào)用,還有可能會調(diào)用多次。
為了讓程序有效的運行一次,我們會將交換方法放入+ load
中,并且在dispatch_once
代碼快中實現(xiàn)。
然后讓我們來了解幾個方法:
-
class_getInstanceMethod
:獲取實例方法 -
class_getClassMethod
:獲取類方法 -
class_addMethod
:添加方法 -
class_replaceMethod
:替換方法的實現(xiàn),如果不存在要替換的方法則相當于調(diào)用class_addMethod
方法 -
method_exchangeImplementations
:交換兩個方法的實現(xiàn) -
method_getImplementation
:返回一個指向方法實現(xiàn)函數(shù)的指針
這里我們需要了解兩個概念SEL和IMP。
(1).SEL又叫選擇器,是表示一個方法的selector的指針,它會在程序編譯的時候根據(jù)方法名和參數(shù)序列生成為唯一一個整型標示(Int類型的地址)以用來在各自的方法列表中查找IMP。
(2).IMP實際上是一個函數(shù)指針,指向方法實現(xiàn)的首地址,可以通過SEL快速查找到它。 -
method_getTypeEncoding
:獲取描述方法參數(shù)和返回值類型的字符串比如v@:
更多的類型可以查看Type Encodings。
可能會有人有疑問,在- fml_viewDidDisappear:
方法中,調(diào)用了[self fml_viewDidDisappear:animated]
會不會引發(fā)死循環(huán)?其實這樣是不會引發(fā)死循環(huán)的,因為我們在調(diào)用這個方法的時候,其實是在調(diào)用viewDidDisappear
,因此不會觸發(fā)死循環(huán)。
因為Method Swizzling
方法會導(dǎo)致不可預(yù)計的后果,比如并不知道代碼中使用了這中方法,因此在使用的時候要慎重。
參考鏈接:
http://southpeak.github.io/2014/10/25/objective-c-runtime-1/
http://southpeak.github.io/2014/11/03/objective-c-runtime-3/
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
因為文章太長,下一篇準備總結(jié)消息轉(zhuǎn)發(fā)