你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS編程技術 >> runtime之玩轉成員變量

runtime之玩轉成員變量

編輯:IOS編程技術

前言:

  不鋪墊那麼多,單刀直入吧:runtime是一個C和匯編寫的動態庫,就像是一個小小的系統,將OC和C緊密關聯在一次,這個系統主要做兩件事情。

1,封裝C語言的結構體和函數,讓開發者在運行時創建,檢查或者修改類,對象和方法等
2,傳遞消息,找出方法的最終執行代碼

也就是說我們寫的OC代碼在運行的時候都會轉為運行時代碼

通過runtime的學習能夠更好理解OC的這種消息發送機制,並且我也認為對runtime的學習是對深入學習iOS必不可少的坎,比如你有可能通過閱讀一些第三方框架來提高自己的編程技巧,在這些第三方框架中就會有大量運行時代碼。掌握了runtime我們能夠簡單做些什麼事情呢?

  1,遍歷對象的所有屬性
  2,動態添加/修改屬性,動態添加/修改/替換方法
  3,動態創建類/對象
  4,方法攔截使用(給方法添加一個動態實現,甚至可以講該方法重定向或者打包給lisi)

 聽起來跟黑魔法一樣。其實runtime就素有黑魔法之稱!我們就從成員變量開始我們對runtime的學習吧。

正文


 

成員變量:

  成員變量是我們在定義一個類中其中重要的成分,主要是想描述這個類實例化後具備了什麼屬性,特點,等等。就像定義了一個Person類,Person類具備了name,age,gender等各種屬性來描述這個類。舉了這個稍微符合的例子來輔助說明成員變量是干嘛用的,但是卻還是不能說明成員變量到底本質是什麼?在runtime.h文件中的成員變量是一個指向objc_ivar類型的結構體指針:

 /// An opaque type that represents an instance variable.
 typedef struct objc_ivar *Ivar;

在這個objc_ivar這個結構體定義如下:

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;//成員變量的名字
    char *ivar_type                                          OBJC2_UNAVAILABLE;//成員變量的類型
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}      

對成員變量進行操作的主要有以下幾種方式:

Ivar *class_copyIvarList(Class cls, unsigned int *outCount)      //獲取所有成員變量
const char *ivar_getName(Ivar v)            //獲取某個成員變量的名字
const char *ivar_getTypeEncoding(Ivar v)   //獲取某個成員變量的類型編碼
Ivar class_getInstanceVariable(Class cls, const char *name)    //獲取某個類中指定名稱的成員變量
id object_getIvar(id obj, Ivar ivar)    //獲取某個對象中的某個成員變量的值
void object_setIvar(id obj, Ivar ivar, id value)    //設置某個對象的某個成員變量的值

下面通過建立一個Person類來理解runtime中提供的這些函數,首先我們定義一個Person類,並且重寫它的description方法:

Person.h中:

@interface Person : NSObject
{
    NSString *clan;//族名
}
@property(nonatomic,copy)NSString *name;
@property(nonatomic,copy)NSString *gender;
@property(nonatomic,strong)NSNumber *age;
@property(nonatomic,assign)NSInteger height;
@property(nonatomic,assign)double weight;

Person.m

-(NSString *)description
{
    unsigned int outCount;
    Ivar *IvarArray = class_copyIvarList([Person class], &outCount);//獲取到Person中的所有成員變量
    for (unsigned int i = 0; i < outCount; i ++) {
        Ivar *ivar = &IvarArray[i];
        NSLog(@"第%d個成員變量:%s,類型是:%s",i,ivar_getName(*ivar),ivar_getTypeEncoding(*ivar));// 依次獲取每個成員變量並且打印成員變量名字和類型
    }
    return nil;
}

在程序入口創建Person實例類並且調用description方法可以看到打印台打印:


ivar_getTypeEncoding函數獲取到的是成員變量的類型編碼。類型編碼是蘋果對數據類型對象類型規定的另一個表現形式,比如"@"代表的是對象,":"表示的是SEL指針,"v"表示的是void。具體可以看蘋果官方文檔對類型編碼的具體規定:戳我!!!

通過runtime來給對象賦值和獲取對象的值:

Person.m中實現了兩個分別對實例化person對象賦值和取值方法:

 + (Person *)personWithName:(NSString *)name age:(NSNumber *)age gender:(NSString *)gender clan:(NSString *)clan
 {
     Person *p = [Person new];
     unsigned int outCount;
     Ivar *IvarArray = class_copyIvarList([Person class], &outCount);
     object_setIvar(p, IvarArray[0], clan);
     object_setIvar(p, IvarArray[1], name);
     object_setIvar(p, IvarArray[2], gender);
     object_setIvar(p, IvarArray[3], age);
     return p;
 }
 
 - (void)personGetPersonMessage
 {
     unsigned int outCount;
     Ivar *IvarArray = class_copyIvarList([Person class], &outCount);
     for (NSInteger i = 0; i < 4; i ++) {
         NSLog(@"%s = %@",ivar_getName(IvarArray[i]),object_getIvar(self,IvarArray[i]));
     }
 }

在viewDidLoad中:

 Person *person = [Person personWithName:@"張三" age:@26 gender:@"man" clan:@"漢"];
     [person personGetPersonMessage];

可以看到打印台打印:

成功的對Person對象進行設置值和取值操作。


 

屬性:

屬性在runtime中定義如下:

 /// An opaque type that represents an Objective-C declared property.
 typedef struct objc_property *objc_property_t;
 /// Defines a property attribute
 typedef struct {
     const char *name;           /**< The name of the attribute */
     const char *value;          /**< The value of the attribute (usually empty) */
 } objc_property_attribute_t;

屬性的本質是一個指向objc_property的結構體指針。跟成員變量一樣,runtime中一樣為屬性定義了一系列對屬性的操作函數:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)   //獲取所有屬性的列表
const char *property_getName(objc_property_t property) //獲取某個屬性的名字
const char *property_getAttributes(objc_property_t property)   //獲取屬性的特性描述
objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)   //獲取所有屬性的特性

獲取person實例對象中所有屬性的特性描述:

Person.m中:

 - (void)getAttributeOfproperty
 {
     unsigned int outCount;
     objc_property_t *propertyList = class_copyPropertyList([Person class], &outCount);
     for (NSInteger i = 0; i < outCount; i ++) {
         NSLog(@"屬性:%s,它的特性描述:%s",property_getName(propertyList[i]),property_getAttributes(propertyList[i]));
     }
 }

獲取屬性列表只會獲取有property屬性聲明的變量,所有當調用getAttributeOfproperty的時候打印台打印:

特性描述主要說明的是該屬性的修飾符,具體的代表意義如下:

 屬性類型  name值:T  value:變化
 編碼類型  name值:C(copy) &(strong) W(weak) 空(assign) 等 value:無
 非/原子性 name值:空(atomic) N(Nonatomic)  value:無

在運行時runtime下我們可以獲取到所有的成員變量,以及類的私有變量。所有runtime的重要應用就是字典轉模型,復雜歸檔

應用1:復雜對象歸檔

復雜對象歸檔平常我們需要類遵循<NSCoding>協議,重寫協議中編碼和解碼的兩個方法,創建NSKeyarchive對象將類中的成員變量進行逐一編碼和解碼。

runtime下基本是同樣的操作,但是我們可以利用runtime提供的函數獲取變量的名字和所對應的成員變量,開啟循環進行快速歸檔(要記得普通情況下我們可以要逐一的寫),同樣是以Person類為例;

Person.m中:

-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        unsigned int outCount;
        Ivar *ivarList = class_copyIvarList([Person class], &outCount);
        for (NSInteger i = 0; i < outCount; i ++) {
            Ivar ivar = ivarList[i];
            NSString *ivarName = [NSString
                                  stringWithUTF8String:ivar_getName(ivar)];
            [self setValue:[aDecoder decodeObjectForKey:ivarName] forKey:ivarName];
        }
    }
    return self;
}


-(void)encodeWithCoder:(NSCoder *)aCoder
{
    unsigned int outCount;
    Ivar *ivarlist = class_copyIvarList([self class], &outCount);
    for (NSInteger i = 0; i < outCount; i ++) {
        Ivar ivar = ivarlist[i];
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        [aCoder encodeObject:[self valueForKey:ivarName] forKey:ivarName];
    }
}

應用2字典轉模型:

另一個重要的應用便是字典轉模型,將字典中的數據賦值給模型中對應的屬性。大概思路是先通過class_copyPropertyList獲取到所有的屬性,再通過property_getName獲取到變量對應的名字作為key值,通過key值查看字典中是否有對應的value,若是有的話則給屬性賦值。

以上的操作都是基於對象具有的屬性通過runtime獲取屬性的一些信息,比如名字,屬性的值,屬性的特性描述等。通過runtime還可以給對象動態添加變量,也就是添加關聯。還記得分類和延展的區別嗎?延展可以為類添加屬性和方法,而分類只能為類添加方法。有個面試題:不使用繼承的方式如何給系統類添加一個公共變量?我們知道在延展裡面為類添加的變量是私有變量,外界無法訪問的。如果對runtime有了解的人也許就知道這是想考驗應聘人對runtime的了解。

runtime下提供了三個函數給我們能夠進行關聯對象的操作:

 void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)     //為某個類關聯某個對象
 id objc_getAssociatedObject(id object, const void *key)    //獲取到某個類的某個關聯對象
 void objc_removeAssociatedObjects(id object)          //移除已經關聯的對象
 參數說明:
 /**
  *  參數說明:
     object:要添加成員變量的對象
     key:添加成員變量對應的key值
     value:要添加的成員變量
     policy:添加的成員變量的修飾符
  */

我們以給NSDictionary添加一個NSString類型的公共變量funnyName為例:

在NSDictionary分類MyDict.h新增加兩個屬性其中一個字符串一個block中:

 @property(nonatomic,copy)NSString *funnyName;
 @property(nonatomic,copy)void(^dictAction)(NSString *str);

一般情況下如果我們只聲明了這些變量在外面使用的時候就會報錯,所有需要我們手動實現他們的set和get方法(可別以為是因為我們沒有實現它們的set和方法才報錯了哦,@property修飾的屬性可是會自動生成get和set方法)

那應該如何實現它們的set和get方法呢:

 -(void)setFunnyName:(NSString *)funnyName
 {
     objc_setAssociatedObject(self, @selector(funnyName), funnyName, OBJC_ASSOCIATION_COPY_NONATOMIC);
 }
 
 -(NSString *)funnyName
 {
     return objc_getAssociatedObject(self, @selector(funnyName));
 }
 
 -(void)setDictAction:(void (^)(NSString *))dictAction
 {
     objc_setAssociatedObject(self, @selector(dictAction), dictAction, OBJC_ASSOCIATION_COPY_NONATOMIC);
 }
 
 -(void (^)(NSString *))dictAction
 {
     return objc_getAssociatedObject(self, @selector(dictAction));
 }

在我們程序中就可以使用字典新增加的兩個屬性了:

 NSDictionary *dict = [NSDictionary new];
     dict.funnyName = @"SoFunny";
     NSLog(@"dict.funnyName = %@",dict.funnyName);
     void(^action)(NSString *str)  = ^(NSString *str){
         NSLog(@"打印了這個字符串:%@",str);
     };
     //設置block
     dict.dictAction = action;
     
     //調用dict的action
     dict.dictAction(@"新增加變量dicAction");

在打印台可以看見打印成功打印到我們想要的東西:


初嘗runtime,若是有什麼表述不當的地方還請指出。後續將繼續更新runtime的學習。

戳我看代碼

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