關于iOS Runtime讓你了解更多

關于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是可以相互轉化的,下面上一張圖嵌纲,做的不怎么好的俘枫,湊合著看吧


IMP&SEL&Method

Type Encoding

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娠酢!迹炼!

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末砸彬,一起剝皮案震驚了整個濱河市颠毙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌砂碉,老刑警劉巖蛀蜜,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異增蹭,居然都是意外死亡滴某,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門滋迈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霎奢,“玉大人,你說我怎么就攤上這事饼灿∧幌溃” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵碍彭,是天一觀的道長晤硕。 經(jīng)常有香客問我,道長庇忌,這世上最難降的妖魔是什么舞箍? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮漆枚,結果婚禮上创译,老公的妹妹穿的比我還像新娘。我一直安慰自己墙基,他們只是感情好软族,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著残制,像睡著了一般立砸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上初茶,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天颗祝,我揣著相機與錄音,去河邊找鬼恼布。 笑死螺戳,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的折汞。 我是一名探鬼主播倔幼,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼爽待!你這毒婦竟也來了损同?” 一聲冷哼從身側響起翩腐,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎膏燃,沒想到半個月后茂卦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡组哩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年等龙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片禁炒。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡而咆,死狀恐怖霍比,靈堂內(nèi)的尸體忽然破棺而出幕袱,到底是詐尸還是另有隱情,我是刑警寧澤悠瞬,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布们豌,位于F島的核電站,受9級特大地震影響浅妆,放射性物質發(fā)生泄漏望迎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一凌外、第九天 我趴在偏房一處隱蔽的房頂上張望辩尊。 院中可真熱鬧,春花似錦康辑、人聲如沸摄欲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胸墙。三九已至,卻和暖如春按咒,著一層夾襖步出監(jiān)牢的瞬間迟隅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工励七, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留智袭,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓掠抬,卻偏偏與公主長得像吼野,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子剿另,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容