使用Runtime的API

runtime 的運(yùn)行時(shí)機(jī)制

runtime 又叫運(yùn)行時(shí)歧蒋,是一套底層的 C 語言 API醉锄,其為 iOS 內(nèi)部的核心之一,我們平時(shí)編寫的 OC 代碼龙亲,底層都是基于它來實(shí)現(xiàn)的虑椎。Objective-C語言是一門動態(tài)語言。

runtime 的作用

  • 獲取某個(gè)類的所有成員變量俱笛、屬性捆姜、方法、協(xié)議
  • 添加一個(gè)成員變量迎膜、屬性泥技、方法
  • 過濾
  • 重定向
  • 交換方法
  • 轉(zhuǎn)發(fā)
  • 字典轉(zhuǎn)模型(重難點(diǎn))

demo演示

1.runtime獲取變量、屬性磕仅、方法珊豹、協(xié)議

- (void)testRunTimeGetValue
{
    unsigned int count;
    
    //獲取成員變量列表
    Ivar *ivarList = class_copyIvarList([self class], &count);
    for (unsigned int i=0; i<count; i++) {
        Ivar myivar = ivarList[i];
        const char *ivarname = ivar_getName(myivar);
        const char *ivarType = ivar_getTypeEncoding(myivar); // 獲取變量編碼類型
        
        NSString *ivarNameStr = [NSString stringWithUTF8String:ivarname];
        NSLog(@"ivar----="">%@  %@", ivarNameStr, [NSString stringWithUTF8String:ivarType]);
        if ([ivarNameStr isEqualToString:@"_property2"]) {
            [self setValue:@"這是屬性值" forKey:@"property2"];
            NSLog(@"self.property2:%@", self.property2);
        }
    }
    
    //獲取屬性列表
    objc_property_t * propertyList = class_copyPropertyList([self class], &count);
    for (unsigned int i=0; i<count; i++) {
        const char * propertyname = property_getName(propertyList[i]);
        NSLog(@"property----="">%@", [NSString stringWithUTF8String:propertyname]);
    }
    
    //獲取方法列表
    Method *methodList = class_copyMethodList([self class], &count);
    for (unsigned int i=0; i<count; i++) {
        Method method = methodList[i];
        NSLog(@"method----=>%@", NSStringFromSelector(method_getName(method)));
    }
    
    //獲取協(xié)議列表
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
    for (unsigned int i=0; i<count; i++) {
        Protocol *myprotocal = protocolList[i];
        const char *protocolname = protocol_getName(myprotocal);         NSLog(@"protocol----="">%@", [NSString stringWithUTF8String:protocolname]);
    }
}

2.runtime添加屬性簸呈,還可以添加任何對象

- (void)testRunTimeAddValue
{
    //添加屬性
    objc_setAssociatedObject(self, &associatedObjectKey, @"addProperty", OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    //添加對象
    UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(10, 100, 300, 60)];
    label.numberOfLines = 2;
    label.textColor = [UIColor blueColor];
    label.backgroundColor = [UIColor redColor];
    objc_setAssociatedObject(self, &associatedObjectKey2,label, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    [self getValueOfRunTime];
}

- (void)getValueOfRunTime
{
    //獲取關(guān)聯(lián)對象string與label

    NSString *string = objc_getAssociatedObject(self, &associatedObjectKey);
    
    UILabel *label = objc_getAssociatedObject(self, &associatedObjectKey2);
    label.text = [NSString stringWithFormat:@"動態(tài)添加的屬性名稱為:%@\\\\n此label也是動態(tài)添加的對象", string];
    [self.view addSubview:label];
}

3. runtime過濾

調(diào)用一個(gè)不存在的實(shí)例方法的時(shí)候,會調(diào)用resolveInstanceMethod:方法店茶;調(diào)用一個(gè)不存在的類方法的時(shí)候蜕便,會調(diào)用resolveClassMethod:方法;
如果resolveInstanceMethod:返回NO贩幻,則會再掉用forwardingTargetForSelector方法實(shí)現(xiàn)轉(zhuǎn)發(fā)轿腺。如果返回Yes,反之不掉用丛楚。從而實(shí)現(xiàn)過濾的作用族壳。

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
#if 1
    return NO;
#else
    if (sel == @selector(testCategory)) {
        class_addMethod([self class], sel, (IMP)defualtFunc, "v@:");
        return YES;
    }
    return [super resolveClassMethod:sel];
#endif
}

4. runtime重定向

將你調(diào)用的不存在的方法重定向到一個(gè)其他聲明了這個(gè)方法的類,只需要返回一個(gè)有這個(gè)方法的target趣些。

- (id)forwardingTargetForSelector:(SEL)sel
{
    if(sel == @selector(testCategory)) {
        return appDelegate;
    }
    return [super forwardingTargetForSelector:sel];
}

5. runtime交換方法

交換實(shí)例方法使用class_getInstanceMethod仿荆。交換類方法使用class_getClassMethod。

- (void)testRunTimeChangeFunction
{
    // 獲取 testFunction1方法
    Method testFunction1 = class_getInstanceMethod([self class], @selector(testFunction1));
    
    // 獲取 testFunction2方法
    Method testFunction2 = class_getInstanceMethod([self class], @selector(testFunction2));
    
    // 交換方法地址, 相當(dāng)于交換實(shí)現(xiàn)
    method_exchangeImplementations(testFunction1, testFunction2);
    
    [self testFunction1];
    //實(shí)際上會掉用testFunction2方法
}

- (void)testFunction1
{
    NSLog(@"runtime交換方法-testFunction1");
}

- (void)testFunction2
{
    NSLog(@"runtime交換方法-testFunction2");
}

6. runtime轉(zhuǎn)發(fā)

是將調(diào)用不存在的方法打包成了NSInvocation傳遞來坏平。做完你自己的處理后拢操,調(diào)用invokeWithTarget:方法讓某個(gè)target觸發(fā)這個(gè)方法

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([appDelegate respondsToSelector:
         [anInvocation selector]]) {
        [anInvocation invokeWithTarget:appDelegate];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

7. runtime字典轉(zhuǎn)模型(第三方MJExtension庫也是按照如下方法實(shí)現(xiàn)的字典轉(zhuǎn)模型)

  • 用法:通過方法modelWithDictionary:傳入了一個(gè)字典,返回一個(gè)對應(yīng)的model舶替,用法簡單令境。
- (void)testRunTimeDictionaryToModel
{
    //獲取字典數(shù)據(jù)
    NSDictionary *dictionary = [self getMainBundleResource:@"jsonData.json"];
    
    LXResultModel *model = [LXResultModel modelWithDictionary:dictionary];
    NSLog(@"model.foods[0].food: %@", model.foods[0].food);
}
  • 必要條件:根據(jù)字典結(jié)構(gòu)(在jsonData.json文件中)創(chuàng)建對應(yīng)的model層級結(jié)構(gòu)(在RuntimeDicToModel文件夾中),并將字典的key聲明為model的屬性名稱坎穿。字典結(jié)構(gòu)如下,因此對應(yīng)的model可以這樣取到值返劲,正如上面的NSLog輸出為:model.foods[0].food: rice玲昧。
{
    "address": {
        "city": "遵義市",
        "likePlaces": {
            "place": "HongKong"
        },
        "province": "貴州省"
    },
    "age": 25,
    "foods" :[
    {
        "food": "rice",
        "fruit": "apple",
    },
    {
        "food": "noodle",
        "fruit": "watermelon",
    }
    ],
    "name": "lixu"
}
  • 核心代碼:使用到runtime的邏輯處理,可以將字典里的arr子元素?cái)?shù)據(jù)轉(zhuǎn)化為model篮绿,也可以將字典里包含的字典轉(zhuǎn)化為model(用遞歸算法)孵延。
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary
{
    id objc = [[self alloc] init];
    unsigned int count;

    // 獲取類中的所有成員屬性
    Ivar *ivarList = class_copyIvarList(self, &count);
    
    for (int i = 0; i < count; i++) {
        // 根據(jù)角標(biāo),從數(shù)組取出對應(yīng)的成員屬性
        Ivar ivar = ivarList[i];
        
        // 獲取成員屬性名
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
        
        // 從第一個(gè)角標(biāo)開始截取亲配,因?yàn)閷傩宰兞康牡谝粋€(gè)字符為“_”,
        if (![[name substringToIndex:1] isEqualToString:@"_"]) {
            continue;
        }
        NSString *key = [name substringFromIndex:1];
        // 根據(jù)成員屬性名去字典中查找對應(yīng)的value
        id value = dictionary[key];
        
        // 二級轉(zhuǎn)換:如果字典中還有字典尘应,也需要把對應(yīng)的字典轉(zhuǎn)換成模型
        // 判斷下value是否是字典
        if ([value isKindOfClass:[NSDictionary class]]) {
            // 獲取成員屬性類型
            NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            
            // 根據(jù)字符串類名生成類對象
            Class modelClass = NSClassFromString([self cutClassString:type]);
            
            if (modelClass) { // 有對應(yīng)的模型才需要轉(zhuǎn)
                // 把字典轉(zhuǎn)模型,采用遞歸
                value = [modelClass modelWithDictionary:value];
            }
        }
        
        // 三級轉(zhuǎn)換:NSArray中也是字典吼虎,把數(shù)組中的字典轉(zhuǎn)換成模型.
        // 判斷值是否是數(shù)組
        if ([value isKindOfClass:[NSArray class]]) {
            // 判斷對應(yīng)類有沒有實(shí)現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {

                // 獲取數(shù)組中字典對應(yīng)的模型
                Class classModel = [self arrayContainModelClass][key];
                NSMutableArray *arrM = [NSMutableArray array];
                // 遍歷字典數(shù)組犬钢,生成模型數(shù)組
                for (NSDictionary *dict in value) {
                    // 字典轉(zhuǎn)模型
                    id model =  [classModel modelWithDictionary:dict];
                    [arrM addObject:model];
                }

                // 把模型數(shù)組賦值給value
                value = arrM;
            }
        }
        if (value) { // 有值,才需要給模型的屬性賦值
            // 利用KVC給模型中的屬性賦值
            [objc setValue:value forKey:key];
        }
    }
    
    return objc;
}

討論

為了瘦身ViewController或者AppDelegate代碼思灰,常常用到繼承玷犹、封裝、分類等方案洒疚,這里為了結(jié)合runtime只討論分類歹颓。

現(xiàn)在的需求是將AppDelegate里的代碼部分隔離到AppDelegate+RunTime中坯屿,其中需要引用AppDelegate的一個(gè)屬性testStr。

好巍扛,重點(diǎn)來了领跛,下面我采用了三種方案可以在AppDelegate+RunTime中引用屬性testStr。分別用testStr1撤奸、testStr2 吠昭、testStr3表示。

方案一:在本類AppDelegate的.h聲明屬性寂呛,即可在分類中導(dǎo)入本類頭文件怎诫,引用其屬性了

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (nonatomic, copy)   NSString *testStr1;

- (void)testRedirect;

@end

將testStr2與testStr3都聲明在了分類中

@interface AppDelegate (RunTime)

@property (nonatomic, copy)   NSString *testStr2;
@property (nonatomic, copy)   NSString *testStr3;

- (void)testCategory;

@end

方案二:在AppDelegate+RunTime.m中重寫testStr2 的set和get方法,使用runtime實(shí)現(xiàn)給分類添加屬性贷痪。

-(NSString *)testStr2
{
    return objc_getAssociatedObject(self, _cmd);//_cmd當(dāng)前方法的一個(gè)SEL指針幻妓,與@selector(str)對應(yīng),這樣可以避免定義一個(gè)靜態(tài)全局變量
}

-(void)setTestStr2:(NSString *)testStr2
{
    objc_setAssociatedObject(self, @selector(testStr2), testStr2, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

方案三:在AppDelegate+RunTime.m中重寫testStr3 的set和get方法劫拢,使用全局靜態(tài)變量switchStr去引用testStr3的值實(shí)現(xiàn)給分類添加屬性肉津。

static NSString *switchStr;

-(NSString *)testStr3
{
    return switchStr;
}

-(void)setTestStr3:(NSString *)testStr3
{
    switchStr = [testStr3 copy];
}

通過調(diào)用testCategory方法可以看到三種方案設(shè)置的屬性都可以正常獲取值,那么平時(shí)你使用的是哪種方案呢舱沧?為什么呢妹沙?



源碼請點(diǎn)擊github地址下載。


QQ:2239344645 我的github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末熟吏,一起剝皮案震驚了整個(gè)濱河市距糖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌牵寺,老刑警劉巖悍引,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異帽氓,居然都是意外死亡趣斤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門黎休,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浓领,“玉大人,你說我怎么就攤上這事势腮×罚” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵捎拯,是天一觀的道長撑蒜。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么座菠? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任狸眼,我火速辦了婚禮,結(jié)果婚禮上浴滴,老公的妹妹穿的比我還像新娘拓萌。我一直安慰自己,他們只是感情好升略,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布微王。 她就那樣靜靜地躺著,像睡著了一般品嚣。 火紅的嫁衣襯著肌膚如雪炕倘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天翰撑,我揣著相機(jī)與錄音罩旋,去河邊找鬼。 笑死眶诈,一個(gè)胖子當(dāng)著我的面吹牛涨醋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逝撬,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼浴骂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了宪潮?” 一聲冷哼從身側(cè)響起溯警,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎狡相,沒想到半個(gè)月后梯轻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谣光,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年檩淋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了芬为。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片萄金。...
    茶點(diǎn)故事閱讀 40,498評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖媚朦,靈堂內(nèi)的尸體忽然破棺而出氧敢,到底是詐尸還是另有隱情,我是刑警寧澤询张,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布孙乖,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏唯袄。R本人自食惡果不足惜弯屈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望恋拷。 院中可真熱鬧资厉,春花似錦、人聲如沸蔬顾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诀豁。三九已至窄刘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舷胜,已是汗流浹背娩践。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留逞带,地道東北人欺矫。 一個(gè)月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像展氓,于是被迫代替她去往敵國和親穆趴。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評論 2 359

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