你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS runtime的應用實例

iOS runtime的應用實例

編輯:IOS開發綜合

此篇文章將要介紹IOS runtime的應用實例的文章,具體方法請看介紹

  一直想弄明白runtime是怎麼回事,因為面試的時候這是一道必備問題,但是平時用的機會真的少之又少,我一度以為runtime只是用來裝13的利器,沒什麼卵用。但是隨著學習的增多,發現runtime真的很有用,但也沒那麼神秘。我相信看了我這篇博客,您對runtime肯定會有自己的理解。   先說說OC與C的對比: 1.OC是對OC的面向對象的封裝,OC中的對象只是C中指向結構體的指針。 2.OC的方法,本質上就是C語言中的函數,OC中的任意一個方法,在runtime中都會有一個與之對應的函數。eg:[objc sendMessage:@"I am back"]; -> objc_msg(self,@selector(sendMessage),"I am back");所以說在OC中對象調用方法,到運行的時候,都會變成向對象發送消息,這就是runtime中最著名的消息機制。 3.既然本質都是函數,那是不是和C語言的函數沒有區別呢?絕對不是。 (1)C語言只能調用實現過的函數,只聲明了是不行的,編譯是不能通過的。 (2)OC無所謂,只要聲明了就能調用,即時你沒聲明都能調用,編譯階段都不會報錯,只會報警告。

- (id)performSelector:(SEL)aSelector;

這樣據說是保證了編程的靈活性,反正大家都這麼說,但是我覺得這就是不夠嚴謹,因為真要是需要這個方法執行了,程序就得崩潰,在編譯的時候就能解決的問題,為什麼要等到程序崩潰再修改代碼呢,有點浪費時間啊。   下面列舉了幾個runtime的應用實例,先用起來,用的多了,自然就理解了。

1.runtime的常用方法

  runtime可以動態獲取一個對象的成員變量、屬性、方法、遵守的協議。

#import <Foundation/Foundation.h>
#import "ProtocolTest1.h"
#import "ProtocolTest2.h"

@interface Person : NSObject <ProtocolTest1,ProtocolTest2>

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *age;

- (NSString *)getName;
- (NSString *)getAge;

+ (void)classMethodTest;
@end
#import "Person.h"

@implementation Person

- (NSString *)getName {
    
    return @"I am Tomcat";
}
- (NSString *)getAge {
    
    return @"I will be 18 years old forever";
}

+ (void)classMethodTest {
    
    NSLog(@"This is a class method");
}
- (NSString *)description {
    
    return [NSString stringWithFormat:@"name=%@,age=%@",_name,_age];
}

@end

  先看看我們的小白鼠Person類,我們就拿他做實驗,動態獲取他的成員變量、屬性、方法、遵守的協議。

#import "FirstViewController.h"
#import <objc/runtime.h>
#import "Person.h"

@interface FirstViewController ()
@end

@implementation FirstViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    Person *tomcat = [Person new];
    unsigned int count;
    NSLog(@"\n1.獲得屬性列表");
    //  1.get describes of all properties (獲得屬性列表)
    objc_property_t *propertyList = class_copyPropertyList([tomcat class], &count);
    for (unsigned int i = 0; i < count; i++) {
        
        const char *propertyName = property_getName(propertyList[i]);
        printf("property = %s\n",propertyName);
    }
    
    // 2.get describes of all methods (獲得方法列表)
    NSLog(@"\n\n2.獲得方法列表");
    Method *methodList = class_copyMethodList([tomcat class], &count);
    for (unsigned int i = 0; i < count; i++) {
        
        SEL methodName = method_getName(methodList[i]);
        NSLog(@"methodName = %@",NSStringFromSelector(methodName));
    }
    
    // 3.get describes of all variables (獲得成員變量列表)
    NSLog(@"\n\n3.獲得成員變量列表");
    Ivar *ivarList = class_copyIvarList([tomcat class], &count);
    for (unsigned int i = 0; i < count; i++) {
        
        const char *ivarNmae = ivar_getName(ivarList[i]);
        printf("ivarNmae = %s\n",ivarNmae);
        // 動態變量控制
        object_setIvar(tomcat, ivarList[i], @"哈哈,你被我改了");
    }
    NSLog(@"動態變量控制: name = %@",tomcat.name);
    
    //4.get describes of all protocols adopted by a class (獲得當前對象遵守的協議列表)
    NSLog(@"\n\n4.獲得協議列表");
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([tomcat class], &count);
    for (unsigned int i = 0; i < count; i++) {
        
        const char *protocolNmae = protocol_getName(protocolList[i]);
        printf("protocolNmae = %s\n",protocolNmae);
    }

  注意:我們要使用runtime庫,首先要 #import <objc/runtime.h>。

2.攔截並替換方法

  想要攔截和替換方法,首先要找到方法,根據什麼找呢?方法名。

  //5. 通過方法名獲得類方法
    Class personClass = object_getClass([Person class]);
    SEL classSel = @selector(classMethodTest);
    Method classMethod = class_getInstanceMethod(personClass, classSel);
    
    //6. 通過方法名獲得實例方法
    SEL objSel1 = @selector(getName);
    Method objMethod1 = class_getInstanceMethod([tomcat class], objSel1);
    SEL objSe2 = @selector(getAge);
    Method objMethod2 = class_getInstanceMethod([tomcat class], objSe2);

  我們還可以交換這兩個方法的實現

 //7. 交換兩個方法的實現
    NSLog(@"\n\n交換兩個方法的實現");
    NSLog(@"交換之前 --- getName = %@,getAge = %@", [tomcat getName],[tomcat getAge]);
    method_exchangeImplementations(objMethod1, objMethod2);
    NSLog(@"交換之後 --- getName = %@,getAge = %@", [tomcat getName],[tomcat getAge]);

IOS runtime的應用實例

  攔截並替換方法,多用於給系統方法添加新的功能和修改第三方庫。我們現在實現一個功能,就是給計算按鈕的點擊計數。

#import "UIButton+Count.h"
#import <objc/runtime.h>
#import "Tool.h"

@implementation UIButton (Count)

+ (void)load {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        Class selfClass = [self class];
        // 1.原來的方法
        SEL oriSEL = @selector(sendAction:to:forEvent:);
        Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
        // 2.現在的方法
        SEL cusSel = @selector(mySendAction:to:forEvent:);
        Method cusMethod = class_getInstanceMethod(selfClass, cusSel);
        // 3.給原來的方法添加實現,防止原來的方法沒有實現,只有聲明崩潰
        BOOL addSuc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
        if (addSuc) {
            // 添加成功,用現在的方法的實現替換原來方法的實現
            class_replaceMethod(selfClass, cusSel, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else {
            // 沒添加成功,證明原來的方法有實現,直接交換兩個方法
            method_exchangeImplementations(oriMethod, cusMethod);
        }
    });
}
// 現在的方法
- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    
    [[Tool shareInstance] countClicks];
    [super sendAction:action to:target forEvent:event];
}
@end

Tool.h

#import <Foundation/Foundation.h>

@interface Tool : NSObject

@property (nonatomic, assign) NSInteger count;

+ (instancetype)shareInstance;
- (void)countClicks;
@end

Tool.m

#import "Tool.m"

@implementation Tool

static id _instance;
+ (instancetype)shareInstance {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        _instance = [[Tool alloc] init];
    });
    
    return _instance;
}

- (void)countClicks {
    
    _count += 1;
    NSLog(@"您點擊了%ld次",_count);
}
@end
3.實現NSCoding自動歸檔解檔

  NSCoding用於存儲模型對象,必須實現代理NSCoding。

#import "Student.h"
#import <objc/runtime.h>

@implementation Student

- (void)encodeWithCoder:(NSCoder *)aCoder {
    
    // 如果不用runtime
//    [aCoder encodeObject:self.name forKey: @"name"];
//    [aCoder encodeObject:self.stuID forKey: @"stuID"];
//    [aCoder encodeObject:self.score forKey: @"score"];
//    [aCoder encodeObject:self.name forKey: @"myFriend"];
    
    unsigned int count = 0;
    // 獲取變量列表
    Ivar *ivars = class_copyIvarList([self class], &count);
    
    for (int i = 0; i < count; i++) {
        // 獲取變量名
        const char *name = ivar_getName(ivars[i]);
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        // 用變量名歸檔變量
        [aCoder encodeObject:value forKey:key];
    }
    free(ivars);
    
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    
    if (self = [super init]) {
        // 如果不用runtime
        //    self.name = [aDecoder decodeObjectForKey:@"name"];
        //    self.stuID = [aDecoder decodeObjectForKey:@"stuID"];
        //    self.score = [aDecoder decodeObjectForKey:@"score"];
        //    self.myFriend = [aDecoder decodeObjectForKey:@"myFriend"];
        
        unsigned int count = 0;
        // 獲取變量列表
        Ivar *ivars = class_copyIvarList([self class], &count);
        for (int i = 0; i < count; i++) {
            // 獲取變量名
            const char *name = ivar_getName(ivars[i]);
            NSString *key = [NSString stringWithUTF8String:name];
            // 用變量名解檔變量
            id value = [aDecoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
        free(ivars);
    }
    
    return self;
}

- (NSString *)description {
    
    return [NSString stringWithFormat:@"name=%@\nstuID=%@\nscore=%@\nmyFriend:%@",_name,_stuID,_score,_myFriend];
}
@end

  如果模型屬性少無所謂,如果多的話,最好用runtime。有100個屬性,你不可能寫100次解檔歸檔啊。

4.實現字典轉模型的自動轉換

  其實這個用系統方法就很好了,這次我用runtime實現一下,估計系統方法也是這個邏輯。

#import "NSObject+Model.h"
#import <objc/runtime.h>

@implementation NSObject (Model)

+ (instancetype)allocWithDic: (NSDictionary *)dic {
  
    id objc = [[self alloc] init];
    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList(self, &count);
    
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivarList[i];
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        NSString *key = [ivarName substringFromIndex:1];
        id value = dic[key];
        NSLog(@"type = %@",ivarType);
        
        // 把模型轉化的字典再轉換成模型
        // 先判斷value類型,value類型是自字典,但是ivarType又不是NSDictionary,說明value實際上是一個模型轉化的字典
        if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) {
            // @"Person" -> "Person"
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
            // "Person" -> Person
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
            // 生成模型
            Class modelClass = NSClassFromString(ivarType);
            if (modelClass) {
                // 給模型裡的屬性賦值
                value = [modelClass allocWithDic:value];
            }
        }
        
        if (value) {
            [objc setValue:value forKey:key];
        }else {
            NSLog(@"沒找到value");
        }
    }
    
    return objc;
}

@end

  rumtime庫是一個非常強大的,我列舉的這幾個用法是比較常用和基礎的。例外runtime是開源的,不過看起來應該很難,基本上是用c語言和匯編寫的,現在懂匯編的應該很少。本文完整案例已經上傳到Github,歡迎下載。 

通過本文的學習希望對您了解和學習IOS開發的相關知識有一些好的幫助.感謝關注本站.我們將為您收集更多更好的ios開發教程.

【iOS runtime的應用實例】的相關資料介紹到這裡,希望對您有所幫助! 提示:不會對讀者因本文所帶來的任何損失負責。如果您支持就請把本站添加至收藏夾哦!

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved