Objective-C 擴(kuò)展了 C 語(yǔ)言屉凯,并加入了面向?qū)ο筇匦院?Smalltalk 式的消息傳遞機(jī)制博烂。而這個(gè)擴(kuò)展的核心是一個(gè)用 C 和 編譯語(yǔ)言 寫(xiě)的 Runtime 庫(kù)持痰。它是 Objective-C 面向?qū)ο蠛蛣?dòng)態(tài)機(jī)制的基石蕴坪。學(xué)會(huì)使用Runtime api ,在iOS開(kāi)發(fā)中實(shí)現(xiàn)更多的騷操作待牵。
Runtime介紹
Runtime
其實(shí)有兩個(gè)版本: “modern
” 和 “legacy
”馒过。我們現(xiàn)在用的 Objective-C 2.0
采用的是現(xiàn)行 (Modern
) 版的 Runtime
系統(tǒng)坚俗,只能運(yùn)行在 iOS
和 macOS 10.5
之后的 64
位程序中禽作。而 macOS
較老的32
位程序仍采用 Objective-C 1
中的(早期)Legacy
版本的 Runtime
系統(tǒng)尸昧。這兩個(gè)版本最大的區(qū)別在于當(dāng)你更改一個(gè)類的實(shí)例變量的布局時(shí),在早期版本中你需要重新編譯它的子類旷偿,而現(xiàn)行版就不需要烹俗。
高級(jí)編程語(yǔ)言想要成為可執(zhí)行文件需要先編譯為匯編語(yǔ)言再匯編為機(jī)器語(yǔ)言碍沐,機(jī)器語(yǔ)言也是計(jì)算機(jī)能夠識(shí)別的唯一語(yǔ)言,但是OC
并不能直接編譯為匯編語(yǔ)言衷蜓,而是要先轉(zhuǎn)寫(xiě)為純C
語(yǔ)言再進(jìn)行編譯和匯編的操作,從OC
到C
語(yǔ)言的過(guò)渡就是由runtime來(lái)實(shí)現(xiàn)的尘喝。然而我們使用OC
進(jìn)行面向?qū)ο箝_(kāi)發(fā)磁浇,而C
語(yǔ)言更多的是面向過(guò)程開(kāi)發(fā),這就需要將面向?qū)ο蟮念愞D(zhuǎn)變?yōu)槊嫦蜻^(guò)程的結(jié)構(gòu)體朽褪。
閱讀使用官方Api置吓,解決項(xiàng)目各種騷需求。
Runtime應(yīng)用
Runtime
提供了很多api缔赠,功能強(qiáng)大衍锚,應(yīng)用場(chǎng)景非常多,下面主要介紹3中應(yīng)用場(chǎng)景嗤堰。
- 關(guān)聯(lián)對(duì)象(Objective-C Associated Objects)給分類增加屬性戴质;
- 方法魔法(Method Swizzling)方法替換;
- 實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換踢匣;
關(guān)聯(lián)對(duì)象(Objective-C Associated Objects)給分類增加屬性
我們都是知道分類(category)中是不能自定義屬性和變量的告匠。但通過(guò)Runtime
的關(guān)聯(lián)對(duì)象方法,就可以給分類添加屬性离唬。
關(guān)聯(lián)對(duì)象Runtime提供餓了下面幾個(gè)api:
//關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//獲取關(guān)聯(lián)的對(duì)象
id objc_getAssociatedObject(id object, const void *key)
//移除關(guān)聯(lián)的對(duì)象
void objc_removeAssociatedObjects(id object)
參數(shù)解釋
id object:被關(guān)聯(lián)的對(duì)象
const void *key:關(guān)聯(lián)的key后专,要求唯一
id value:關(guān)聯(lián)的對(duì)象
objc_AssociationPolicy policy:內(nèi)存管理的策略
內(nèi)存管理策略枚舉
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. */
};
想要使用 runtime 的 api 需要引入#import "objc/runtime.h"
頭文件。
下面實(shí)現(xiàn)一個(gè) UIViewController
的 Category
添加自定義屬性NSString * newTitle
输莺。
ViewController+Property.h
@interface ViewController (Property)
@property (nonatomic , strong) NSString *newTitle;
@end
--------------------------------------------------------------------------
ViewController+Property.m
#import "ViewController+Property.h"
#import "objc/runtime.h"
static char kNewTitleKey;
@implementation ViewController (Property)
- (void)setNewTitle:(NSString *)newTitle {
objc_setAssociatedObject(self, &kNewTitleKey, newTitle, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString*)newTitle {
return objc_getAssociatedObject(self, &kNewTitleKey);
}
@end
viewController 中的操作
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self testCategoryProperty];
}
- (void)testCategoryProperty {
self.newTitle = @"FK";
NSLog(@"%@",self.newTitle);
NSLog(@"運(yùn)行成功");
}
如果不使用runtime中的關(guān)聯(lián)api對(duì)newTitle進(jìn)行關(guān)聯(lián)戚哎,運(yùn)行后會(huì)閃退,并顯示如下報(bào)錯(cuò):
關(guān)聯(lián)成功后嫂用,newTitle
setter
getter
方法使用正常型凳。
成功為分類設(shè)置自定義屬性。
方法魔法(Method Swizzling)方法替換
曾經(jīng)在了解 +(void)load
方法時(shí)尸折,也有了解過(guò)一下 Method Swizzling
啰脚,今天針對(duì)方法魔法進(jìn)行實(shí)踐操作。
實(shí)現(xiàn)方法替換的核心:
//方法實(shí)現(xiàn)替換api
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
下面實(shí)現(xiàn)一個(gè)替換 ViewController
的 viewDidLoad
方法的例子实夹。
@implementation ViewController
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewDidLoad);
SEL swizzledSelector = @selector(FKViewdidLoad);
Method originalMethod = class_getInstanceMethod(class,originalSelector);
Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSLog(@"viewDidLoad");
}
- (void)FKViewdidLoad {
NSLog(@"FKViewdidLoad");
}
@end
最后的打印結(jié)果
viewDidLoad
和 FKViewdidLoad
兩個(gè)方法替換成功橄浓。
此處值得注意的是 swizzling
應(yīng)該只在+load
中完成。 在 Objective-C
的運(yùn)行時(shí)中亮航,每個(gè)類有兩個(gè)方法都會(huì)自動(dòng)調(diào)用荸实。+load
是在一個(gè)類被初始裝載時(shí)調(diào)用,+initialize
是在應(yīng)用第一次調(diào)用該類的類方法或?qū)嵗椒ㄇ罢{(diào)用的缴淋。兩個(gè)方法都是可選的准给,并且只有在方法被實(shí)現(xiàn)的情況下才會(huì)被調(diào)用泄朴。
swizzling
應(yīng)該只在dispatch_once
中完成,由于swizzling
改變了全局的狀態(tài),所以我們需要確保每個(gè)預(yù)防措施在運(yùn)行時(shí)都是可用的露氮。原子操作就是這樣一個(gè)用于確保代碼只會(huì)被執(zhí)行一次的預(yù)防措施祖灰,就算是在不同的線程中也能確保代碼只執(zhí)行一次。Grand Central Dispatch
的 dispatch_once
滿足了所需要的需求畔规,并且應(yīng)該被當(dāng)做使用swizzling
的初始化單例方法的標(biāo)準(zhǔn)局扶。
實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換
原理描述:用runtime
提供的函數(shù)遍歷Model
自身所有屬性,如果屬性在json
中有對(duì)應(yīng)的值叁扫,則將其賦值三妈。 核心api:
//遍歷類型中所有屬性
class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
在 NSObject
中添加方法:
@implementation NSObject (RuntimeModel)
- (void)initWithDict:(NSDictionary *)dict {
//(1)獲取類的屬性及屬性對(duì)應(yīng)的類型
NSMutableArray * keys = [NSMutableArray array];
NSMutableArray * attributes = [NSMutableArray array];
/*
* 例子
* name = value3 attribute = T@"NSString",C,N,V_value3
* name = value4 attribute = T^i,N,V_value4
*/
unsigned int outCount;
objc_property_t * properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
//通過(guò)property_getName函數(shù)獲得屬性的名字
NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//通過(guò)property_getAttributes函數(shù)可以獲得屬性的名字和@encode編碼
NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
//立即釋放properties指向的內(nèi)存
free(properties);
//(2)根據(jù)類型給屬性賦值
for (NSString * key in keys) {
if ([dict valueForKey:key] == nil) continue;
[self setValue:[dict valueForKey:key] forKey:key];
}
}
@end
viewController
中的操作
- (void)viewDidLoad {
[super viewDidLoad];
[self.runtimeM initWithDict:@{@"time" : @"5點(diǎn)" , @"name" : @"FK" , @"sex" : @"Man"}];
NSLog(@"%@ - %@ - %@",self.runtimeM.time , self.runtimeM.name , self.runtimeM.sex);
}
打印結(jié)果如下:
成功將字典類型轉(zhuǎn)換為模型類型。
拓展
使用 class_copyPropertyList
遍歷方法莫绣,可以遍歷對(duì)象中所有屬性畴蒲,并且可以把所有屬性制空。
總結(jié)
到這里对室,本文想介紹的 runtime
3個(gè)應(yīng)用場(chǎng)景就介紹完了模燥。本文已貼出Demo中核心代碼,如果想閱讀完整代碼掩宜,可以下載Demo查看涧窒。