runtime之玩轉成員變數,runtime成員變數

來源:互聯網
上載者:User

runtime之玩轉成員變數,runtime成員變數
前言:

  不鋪墊那麼多,單刀直入吧: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類型的結構體指標:

1 /// An opaque type that represents an instance variable.2 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對象賦值和取值方法:

 1 + (Person *)personWithName:(NSString *)name age:(NSNumber *)age gender:(NSString *)gender clan:(NSString *)clan 2 { 3     Person *p = [Person new]; 4     unsigned int outCount; 5     Ivar *IvarArray = class_copyIvarList([Person class], &outCount); 6     object_setIvar(p, IvarArray[0], clan); 7     object_setIvar(p, IvarArray[1], name); 8     object_setIvar(p, IvarArray[2], gender); 9     object_setIvar(p, IvarArray[3], age);10     return p;11 }12 13 - (void)personGetPersonMessage14 {15     unsigned int outCount;16     Ivar *IvarArray = class_copyIvarList([Person class], &outCount);17     for (NSInteger i = 0; i < 4; i ++) {18         NSLog(@"%s = %@",ivar_getName(IvarArray[i]),object_getIvar(self,IvarArray[i]));19     }20 }

在viewDidLoad中:

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

可以看到列印台列印:

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

 

屬性:

屬性在runtime中定義如下:

1 /// An opaque type that represents an Objective-C declared property.2 typedef struct objc_property *objc_property_t;3 /// Defines a property attribute4 typedef struct {5     const char *name;           /**< The name of the attribute */6     const char *value;          /**< The value of the attribute (usually empty) */7 } 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中:

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

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

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

1 屬性類型  name值:T  value:變化2 編碼類別型  name值:C(copy) &(strong) W(weak) 空(assign) 等 value:無3 非/原子性 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下提供了三個函數給我們能夠進行關聯對象的操作:

 1 void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)     //為某個類關聯某個對象 2 id objc_getAssociatedObject(id object, const void *key)    //擷取到某個類的某個關聯對象 3 void objc_removeAssociatedObjects(id object)          //移除已經關聯的對象 4 參數說明: 5 /** 6  *  參數說明: 7     object:要新增成員變數的對象 8     key:新增成員變數對應的key值 9     value:要添加的成員變數10     policy:添加的成員變數的修飾符11  */

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

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

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

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

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

 1 -(void)setFunnyName:(NSString *)funnyName 2 { 3     objc_setAssociatedObject(self, @selector(funnyName), funnyName, OBJC_ASSOCIATION_COPY_NONATOMIC); 4 } 5  6 -(NSString *)funnyName 7 { 8     return objc_getAssociatedObject(self, @selector(funnyName)); 9 }10 11 -(void)setDictAction:(void (^)(NSString *))dictAction12 {13     objc_setAssociatedObject(self, @selector(dictAction), dictAction, OBJC_ASSOCIATION_COPY_NONATOMIC);14 }15 16 -(void (^)(NSString *))dictAction17 {18     return objc_getAssociatedObject(self, @selector(dictAction));19 }

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

 1 NSDictionary *dict = [NSDictionary new]; 2     dict.funnyName = @"SoFunny"; 3     NSLog(@"dict.funnyName = %@",dict.funnyName); 4     void(^action)(NSString *str)  = ^(NSString *str){ 5         NSLog(@"列印了這個字串:%@",str); 6     }; 7     //設定block 8     dict.dictAction = action; 9     10     //調用dict的action11     dict.dictAction(@"新增加變數dicAction");

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

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

戳我看代碼

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.