關于Runtime
Runtime根據(jù)字面理解就是運行時祭陷,當我們的代碼運行的時候所體現(xiàn)的東西苍凛,舉一個比較簡單一點的例子,這里有一個Person類颗胡,然后創(chuàng)建一個Student類繼承Person毫深,這里我們知道可以用Person類的對象來接收Student類創(chuàng)建的對象吩坝,從代碼看來這個對象時一個Person對象毒姨,但是在代碼運行的時候,這個對象體現(xiàn)出來的卻是一個Student對象钉寝。
Runtime API
獲取對象的類
Class object_getClass(id obj)
設置對象的類
Class object_setClass(id obj, Class cls)
獲取對象的類名
const char *object_getClassName(id obj)
獲取實例變量的值
id object_getIvar(id obj, Ivar ivar)
設置實例變量的值 這個方法比下面這個設置實例變量要快
void object_setIvar(id obj, Ivar ivar, id value)
設置實例變量的值
Ivar object_setInstanceVariable(id obj, const char *name, void *value)
獲取實例變量變量和值
Ivar object_getInstanceVariable(id obj, const char *name, void **outValue)
根據(jù)名稱獲取類
Class objc_getClass(const char *name)
獲取對應類和實例變量名的Ivar指針
Ivar class_getInstanceVariable(Class cls, const char *name)
獲取類對應的實例變量的Ivar指針數(shù)組
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
根據(jù)類名和SEL變量獲取實例方法對象
Method class_getInstanceMethod(Class cls, SEL name)
根據(jù)類名和SEL變量獲取類方法對象
Method class_getClassMethod(Class cls, SEL name)
根據(jù)類和方法名獲取IMP指針 這個方法比IMP method_getImplementation(Method m) 可能要快一點
IMP類型是(void(*)(id instance, SEL _cmd, id parameter1,...))
IMP class_getMethodImplementation(Class cls, SEL name)
類實例能否響應這個SEL
BOOL class_respondsToSelector(Class cls, SEL sel)
根據(jù)name獲取屬性
objc_property_t class_getProperty(Class cls, const char *name)
獲取屬性列表
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
給指定類添加新方法
class_addMethod(Class cls, SEL name, IMP imp,
const char *types)
取代一個方法的實現(xiàn)
IMP class_replaceMethod(Class cls, SEL name, IMP imp,
const char *types)
添加一個實例變量
這里舉一個例子
class_addIvar(newClass, "name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
BOOL class_addIvar(Class cls, const char *name, size_t size,
uint8_t alignment, const char *types)
添加一個屬性
attributes這個參數(shù)的順序要保持T(Type)在第一位弧呐,V(Ivar)在最后一位
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
替換屬性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
創(chuàng)建一個新類
Class objc_allocateClassPair(Class superclass, const char *name,
size_t extraBytes)
注冊這個新類 給創(chuàng)建的新類添加屬性 方法 協(xié)議 成員變量等都需要在注冊之前完成
void objc_registerClassPair(Class cls)
Method轉SEL變量
SEL method_getName(Method m)
Method轉IMP指針
IMP method_getImplementation(Method m)
設置一個Method的IMP指針 返回之前的IMP指針
IMP method_setImplementation(Method m, IMP imp)
交換兩個Method
void method_exchangeImplementations(Method m1, Method m2)
獲取變量名
const char *ivar_getName(Ivar v)
獲取變量的類型編碼
const char *ivar_getTypeEncoding(Ivar v)
獲取屬性名
const char *property_getName(objc_property_t property)
獲取屬性的特性
const char *property_getAttributes(objc_property_t property)
獲取屬性特性數(shù)組
objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
根據(jù)屬性的特性name獲取value
char *property_copyAttributeValue(objc_property_t property, const char *attributeName)
SEL轉c字符串
const char *sel_getName(SEL sel)
根據(jù)str返回一個SEL變量
SEL sel_getUid(const char *str)
根據(jù)str注冊一個SEL變量
SEL sel_registerName(const char *str)
判斷兩個SEL變量是否相等
BOOL sel_isEqual(SEL lhs, SEL rhs)
block轉IMP
block舉例 由于是block所以沒有SEL參數(shù)
id block = ^(id instance, id parameter1, id parameter2) {
};
IMP imp_implementationWithBlock(id block)
IMP轉block
id imp_getBlock(IMP anImp)
給一個對象關聯(lián)一個指定的key和關聯(lián)方式
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
獲取關聯(lián)值
id objc_getAssociatedObject(id object, const void *key)
移除關聯(lián)值
void objc_removeAssociatedObjects(id object)
IMP SEL Method是可以相互轉化的,下面上一張圖嵌纲,做的不怎么好的俘枫,湊合著看吧
Type Encoding
關于屬性的特性我這里放一個鏈接
objc_property_attribute_t
實際應用
給Category添加屬性
我就隨便給一個Category添加一個莫名其妙的屬性
static const void *countKey = &countKey;
@implementation NSObject (Count)
- (void)setCount:(NSInteger)count {
objc_setAssociatedObject(self, countKey, @(count), OBJC_ASSOCIATION_ASSIGN);
}
- (NSInteger)count {
return [objc_getAssociatedObject(self, countKey) integerValue];
}
@end
攔截系統(tǒng)的方法(swizzling)
我這里根據(jù)IMP SEL Method的關系,我想到三種實現(xiàn)的方法
static IMP _setColor;
static const void *pageKey = &pageKey;
static const void *nameKey = &nameKey;
@implementation UIView (Border)
+ (void)load {
/***********************************交換方法************************/
//方法1
// Method method = class_getInstanceMethod(self, @selector(setBackgroundColor:));
// _setColor = method_setImplementation(method, (IMP)colorOfBackground);
//方法2
_setColor = class_replaceMethod(self, @selector(setBackgroundColor:), (IMP)colorOfBackground, "v@:@");
//方法3
// Method method1 = class_getInstanceMethod(self, @selector(setBackgroundColor:));
// Method method2 = class_getInstanceMethod(self, @selector(colorForBackgroundColor:));
// method_exchangeImplementations(method1, method2);
/***********************************交換方法************************/
}
- (void)colorForBackgroundColor:(UIColor *)color {
// 這里不會循環(huán)逮走,因為已經(jīng)交換方法鸠蚪,調(diào)用本身相當于調(diào)用系統(tǒng)setBackgroundColor:方法
[self colorForBackgroundColor:color];
NSLog(@"設置顏色成功, 并替換了方法 ---colorForBackgroundColor");
}
void colorOfBackground(UIView *view, SEL sel, UIColor *color) {
((void (*)(UIView *, SEL, UIColor *))_setColor)(view, sel, color);
NSLog(@"設置顏色成功师溅, 并替換了方法 ---colorOfBackground");
}
快速序列化茅信,實現(xiàn)歸檔和反歸檔
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
實現(xiàn)自己的KVO
如果要實現(xiàn)自己的KVO就要先了解蘋果自帶的KVO的實現(xiàn)原理,相信大家都會用KVO墓臭,在調(diào)用- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;這個方法的時候蘸鲸,會發(fā)現(xiàn)當前這個調(diào)用者的isa指針發(fā)生變化,這里運用了Runtime的東西窿锉。實現(xiàn)自己的KVO重點在于重寫setter方法和消息的轉發(fā)酌摇。調(diào)用msg_Send()是出現(xiàn)錯誤,在BuildSetting嗡载,搜索msg窑多,將YES改為NO
這里為NSObject添加了一個分類,其實系統(tǒng)的KVO也是NSObject的一個分類實現(xiàn)
//
// NSObject+FSKVO.m
// Runtime
//
// Created by vcyber on 17/8/23.
// Copyright ? 2017年 vcyber. All rights reserved.
//
#import "NSObject+FSKVO.h"
#import <objc/message.h>
static const void *observerKey = &observerKey;
static const void *keyPathKey = &keyPathKey;
static const void *optionsKey = &optionsKey;
static const void *setterKey = &setterKey;
static const void *oldValueKey = &oldValueKey;
@implementation NSObject (FSKVO)
- (void)fs_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
//1.動態(tài)生成一個類
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [@"FS_" stringByAppendingString:oldClassName];
Class newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
//2.為新生成的類添加setter方法
NSString *setter = [@"set" stringByAppendingString:[[keyPath capitalizedString] stringByAppendingString:@":"]];
class_addMethod(newClass, NSSelectorFromString(setter), (IMP)setKeyPath, "v@:@");
objc_registerClassPair(newClass);
//修改被觀察者的isa指針!!讓它指向自定義的類!!
object_setClass(self, newClass);
//3.獲取舊值
id oldValue = [self valueForKeyPath:keyPath];
//3.保存observer keyPath options setter 舊值 用于消息發(fā)送
objc_setAssociatedObject(self, observerKey, observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, keyPathKey, keyPath, OBJC_ASSOCIATION_COPY);
objc_setAssociatedObject(self, optionsKey, @(options), OBJC_ASSOCIATION_ASSIGN);
objc_setAssociatedObject(self, setterKey, setter, OBJC_ASSOCIATION_COPY);
objc_setAssociatedObject(self, oldValueKey, oldValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
void setKeyPath(id self, SEL _cmd, id newValue) {
id class = [self class];
//改變當前對象指向父類!!
object_setClass(self, class_getSuperclass(class));
//調(diào)用父類的setter方法
NSString *setter = objc_getAssociatedObject(self, setterKey);
objc_msgSend(self, NSSelectorFromString(setter), newValue);
//取出觀察者 轉發(fā)消息
id observer = objc_getAssociatedObject(self, observerKey);
NSString *keyPath = objc_getAssociatedObject(self, keyPathKey);
NSUInteger options = [objc_getAssociatedObject(self, optionsKey) unsignedIntegerValue];
NSMutableDictionary<NSKeyValueChangeKey, id> *change = [NSMutableDictionary dictionary];
if (options & NSKeyValueObservingOptionNew) {
change[NSKeyValueChangeNewKey] = newValue;
}
if (options & NSKeyValueObservingOptionOld) {
change[NSKeyValueChangeOldKey] = objc_getAssociatedObject(self, oldValueKey);
}
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), keyPath, self, change, NULL);
object_setClass(self, class);
objc_setAssociatedObject(self, oldValueKey, newValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
用法和系統(tǒng)的用法一樣
MyClass1 *class1 = [[MyClass1 alloc] init];
_cls = class1;
class1.string = @"test";
[class1 fs_addObserver:self forKeyPath:@"string" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@-%@", keyPath, change);
}
JSON轉模型
這里我就不做簡單的演示了(因為難的我也不會)洼滚,大家可以看看別人的三方庫怯伊,里面用到了runtime的東西。
總結
大家還有什么好的runtime使用情況判沟,可以留言或者私信給我耿芹,我就添加在文章上面,供給大家看挪哄。歡迎吐槽0娠酢!迹炼!