OC-成員變量和屬性

前言

相信大家對這兩個詞都不陌生,但是大家會很容易將這兩個詞混淆,所以在探究之前,先來說下什么是成員變量,什么是屬性。

成員變量就是我們在開發中,類似下面這樣定義的變量,例如:

@interface Person : NSObject
{
    @public
    NSString *_name;
    CGFloat _age;
}
@end

則_name,_age便是成員變量。

屬性就是在開發中,我們用 @property 關鍵字聲明的變量,如:

@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) CGFloat *age;

該方法會自動生成_name和_age成員變量,name,age便是我們聲明的屬性
Student.m
#import "Student.h"

@interface Student()
{
  NSString *_address;
}
@property (nonatomic,copy) NSString *name;
@end
@implementation Student
@end

將Student.m文件用clang -rewrite-objc Student.m重新編譯下得到Student.cpp,從該文件中,我們可以得到如下信息:

struct Student_IMPL {
  struct NSObject_IMPL NSObject_IVARS;
  NSString *_address;
  NSString *_name;
};

static NSString * _I_Student_name(Student * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Student$_name)); }

static void _I_Student_setName_(Student * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Student, _name), (id)name, 0, 1); }

編譯器將屬性自動轉換成了成員變量,并且自動生成了getter和setter方法。因此兩者最直觀的區別是屬性會有相應的getter方法和setter方法,而成員變量沒有,另外,外部訪問屬性可以用"."來訪問,訪問成員變量需要用"->"來訪問

成員變量(Ivar)
定義

runtime.h文件中對Ivar的定義為:

 typedef struct objc_ivar *Ivar;

其為指向結構體objc_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
} OBJC2_UNAVAILABLE;
  • ivar_name
    成員變量名稱,可以用const char * ivar_getName(Ivar ivar)來獲得
  • ivar_type
    成員變量類型,可以用const char * ivar_getTypeEncoding(Ivar ivar) 來獲得,這里得到的類型,并不是變量真正的成員變量類型,而是經過類型編碼的c字符串。
  • ivar_offset
    基地址偏移量。其實在訪問變量的時候,是先找到類所在的地址,然后根據地址偏移量,去找到我們要訪問的變量的。我們可以用ptrdiff_t ivar_getOffset(Ivar ivar)來得到某個變量的偏移量。通過這個偏移量,我們也可以訪問到類的私有變量。

那么變量在類中是怎么存儲的呢?繼續來看的類的定義:

struct objc_class {
  Class isa  OBJC_ISA_AVAILABILITY;
  #if !__OBJC2__
  Class super_class         OBJC2_UNAVAILABLE;  // 父類
  const char *name          OBJC2_UNAVAILABLE;  // 類名
  long version              OBJC2_UNAVAILABLE;  // 類的版本號
  long info                 OBJC2_UNAVAILABLE;  // 類信息
  long instance_size        OBJC2_UNAVAILABLE;  // 類的實例大小
  struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 成員變量列表
  struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法列表
  struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法緩存
  struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 協議列表
  #endif
} OBJC2_UNAVAILABLE;

來看結構體objc_ivar_list定義:

struct objc_ivar_list {
  int ivar_count                                    OBJC2_UNAVAILABLE;
  #ifdef __LP64__
  int space                                         OBJC2_UNAVAILABLE;
  #endif
  /* variable length structure */
  struct objc_ivar ivar_list[1]                     OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

在類的定義中,用objc_ivar_list類型的結構體指針變量來記錄類的所有成員變量的相關信息。objc_ivar_list中存放著一個objc_ivar結構體數組,objc_ivar結構體中存放著類的單個成員變量的所有信息。

對變量的操作函數
  • BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types)
    向類中添加成員變量,該方法只能在動態創建類的時候使用,不能向已存在的類中添加成員變量。
  • Ivar * class_copyIvarList(Class cls, unsigned int *outCount)
    獲得成員變量列表,outCount如果有返回值,則返回的是類中成員變量的個數,如果NULL,則沒有返回成員變量的個數
  • const char * ivar_getName( Ivar ivar)
    返回成員變量的name
  • const char * ivar_getTypeEncoding( Ivar ivar)
    返回成員變量的類型編碼
  • ptrdiff_t ivar_getOffset( Ivar ivar)
    返回成員變量的基地址偏移量
  • id object_getIvar(id object, Ivar ivar)
    可以用這種便捷方式來獲得成員變量的值
  • void object_setIvar(id object, Ivar ivar, id value)
    設置成員變量的值
代碼示例

Person.h

@interface Person : NSObject
{
  @public
  NSString *_name;
  CGFloat _age;

  @private
  int _temp;
}
@property (nonatomic,assign) CGFloat height;
@end

Person.m

@implementation Person
- (NSString *)description{
   return [NSString stringWithFormat:@"私有變量_temp的值為%d",_temp];
}  
@end

main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    // 添加成員變量
    Class cls =  objc_allocateClassPair([NSObject class],"myClass", 0);
    BOOL res = class_addIvar(cls, "sex", sizeof(NSString *), log2(sizeof(NSString *)), "@");
    if(res){
        NSLog(@"添加成功");
    }else{
        NSLog(@"添加失敗");
    }
    
    Person *p = [[Person alloc] init];
    unsigned int outCount = 0;
    NSLog(@"=============獲取成員變量列表============");
    Ivar *ivars = class_copyIvarList([p class], &outCount);
    NSLog(@"成員變量個數: %d",outCount);
    for (int i = 0; i<outCount; i++) {
        Ivar ivar = ivars[i];
        NSLog(@"變量名稱: %s,類型: %s,偏移量: %td",ivar_getName(ivar),ivar_getTypeEncoding(ivar),ivar_getOffset(ivar));
    }
    free(ivars);
    NSLog(@"=============訪問私有變量============");
    NSLog(@"實例變量p地址:%p",p);
    Ivar tempIvar = class_getInstanceVariable([p class], "_temp");
    NSLog(@"私有變量_temp的偏移量:%td",ivar_getOffset(tempIvar));
    int *temp = (int *)((__bridge void *)(p) + ivar_getOffset(tempIvar));
    NSLog(@"私有變量_temp的地址:%p",temp);
    *temp = 10;
    NSLog(@"%@",p);
  }
  return 0;
}

輸出結果為:

添加成功
=============獲取成員變量列表============
成員變量個數: 4
變量名稱: _name,類型: @"NSString",偏移量: 8
變量名稱: _age,類型: d,偏移量: 16
變量名稱: _temp,類型: i,偏移量: 24
變量名稱: _height,類型: d,偏移量: 32
=============訪問私有變量============
實例變量p地址:0x100200000
私有變量_temp的偏移量:24
私有變量_temp的地址:0x100200018
私有變量_temp的值為10
屬性(Property)

在類的定義中,我們沒有發現存儲屬性的變量,那么屬性是怎么存儲的呢?從上面重新編譯Student.m生成的Student.cpp中,我們可以看到編譯器將屬性轉換成了成員變量,但是仍然找不到屬性是用什么存儲的。怎么辦呢?我們可以從添加屬性的方法入手,添加屬性的方法:

BOOL class_addProperty(Class cls, const char *name,const objc_property_attribute_t *attrs, unsigned int n)

其方法實現如下:

static bool _class_addProperty(Class cls, const char *name, 
               const objc_property_attribute_t *attrs, unsigned int count, 
               bool replace){
  if (!cls) return NO;
  if (!name) return NO;

  property_t *prop = class_getProperty(cls, name);
  if (prop  &&  !replace) {
    // already exists, refuse to replace
    return NO;
  } 
  else if (prop) {
    // replace existing
    rwlock_writer_t lock(runtimeLock);
    try_free(prop->attributes);
    prop->attributes = copyPropertyAttributeString(attrs, count);
    return YES;
  }
  else {
    rwlock_writer_t lock(runtimeLock);
    
    assert(cls->isRealized());
    
    property_list_t *proplist = (property_list_t *)
        malloc(sizeof(*proplist));
    proplist->count = 1;
    proplist->entsizeAndFlags = sizeof(proplist->first);
    proplist->first.name = strdup(name);
    proplist->first.attributes = copyPropertyAttributeString(attrs, count);
    
    cls->data()->properties.attachLists(&proplist, 1);
    
    return YES;
  }
}

從中我們可以看到:其最終是用property_list_t來存儲單個屬性信息的。

對屬性操作的函數
  • objc_property_t class_getProperty(Class cls, const char *name)
    獲得類的某個屬性的信息
  • objc_property_t * class_copyPropertyList(Class cls, unsigned int *outCount)
    獲得類的屬性列表,不包含父類的屬性,outCount中返回類的屬性個數。
  • const char *property_getName(objc_property_t property)
    獲得屬性名稱
  • const char *property_getAttributes(objc_property_t property)
    獲得屬性的屬性信息,即屬性的描述信息。
  • char *property_copyAttributeValue(objc_property_t property, const char *attributeName)
    獲得屬性的某個描述信息的值
  • objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
    屬性的描述信息列表
代碼演練
  int main(int argc, const char * argv[]) {
    @autoreleasepool {
      Person *p = [[Person alloc] init];
      NSLog(@"=========動態添加屬性==========");
      objc_property_attribute_t type= {"T","@\"NSString\""}; // type
      objc_property_attribute_t refType = {"C",""}; // copy
      objc_property_attribute_t backValue = {"V","_sex"}; // 返回值
      objc_property_attribute_t attrs[] = {type, refType, backValue}; 
      BOOL flag = class_addProperty([p class], "sex",attrs, 3);
      if(flag){
          NSLog(@"屬性添加成功");
      }else{
          NSLog(@"屬性添加失敗");
      }
      NSLog(@"=========獲得屬性列表==========");
      unsigned int outCount = 0;
      objc_property_t *props = class_copyPropertyList([p class], &outCount);
      for(int i=0; i<outCount; i++){
        objc_property_t p = props[i];
        NSLog(@"屬性: %s,描述信息:%s",property_getName(p),property_getAttributes(p));
      }
      free(props);
    
      NSLog(@"=============獲取成員變量列表============");
      unsigned int outIvarCount = 0;
      Ivar *ivars = class_copyIvarList([p class], &outIvarCount);
      NSLog(@"成員變量個數: %d",outIvarCount);
      for (int i = 0; i<outIvarCount; i++) {
        Ivar ivar = ivars[i];
        NSLog(@"變量名稱: %s",ivar_getName(ivar));
      }
      free(ivars);
    }
     return 0;
  }

輸出結果:

  =========動態添加屬性==========
  屬性添加成功
  =========獲得屬性列表==========
  屬性: sex,描述信息:T@"NSString",C,V_sex
  屬性: height,描述信息:Td,N,V_height
  =============獲取成員變量列表============
  成員變量個數: 4
  變量名稱: _name
  變量名稱: _age
  變量名稱: _temp
  變量名稱: _height

由代碼輸出結果,我們可以看到類的屬性的一些信息,同時我們也可以看到,我們動態添加的屬性,是不會自動生成對應的成員變量的。因此我們在給動態添加的屬性賦值的時候,是不能直接用_屬性名稱去賦值的。那怎么辦呢?其實,我們用@property聲明的屬性,系統會自動生成getter和setter方法,我們也可以仿造系統的做法,同樣的給我們新添加的屬性,增加getter和setter方法。給類增加這兩個方法,由多種實現方式,但是在不改變原有類的代碼的基礎上,我們需要用到對象關聯

對象關聯(Associative References)

對象關聯是動態添加屬性的常用方法,相關操作函數如下:

  • void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
    給對象設置一個關聯的值, objc_AssociationPolicy:關聯策略,其實就是值的引用類型,是retain,copy,weak或assign
  • id objc_getAssociatedObject(id object, void *key)
    得到對象關聯的值
  • void objc_removeAssociatedObjects(id object)
    移除所有對象的關聯值

這里演示下,將上面的屬性sex添加完善一下。
代碼演練:

static const void *sexTag = &sexTag;
NSString *sex(id self, SEL _cmd) {
   return objc_getAssociatedObject(self, sexTag);
}  
void setSex(id self, SEL _cmd, NSString *sex) {
  objc_setAssociatedObject(self, sexTag, sex, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

int main(int argc, const char * argv[]) {
@autoreleasepool {
    Person *p = [[Person alloc] init];
    NSLog(@"=========動態添加屬性==========");
    objc_property_attribute_t type= {"T","@\"NSString\""};
    objc_property_attribute_t refType = {"C",""};
    objc_property_attribute_t backValue = {"V","_sex"};
    objc_property_attribute_t attrs[] = {type, refType, backValue};
    BOOL flag = class_addProperty([p class], "sex",attrs, 3);
    if(flag){
        NSLog(@"屬性添加成功");
        class_addMethod([p class], @selector(sex), (IMP)sex, "@@:");
        class_addMethod([p class], @selector(setSex:), (IMP)setSex, "v@:@");
    }else{
        NSLog(@"屬性添加失敗");
    }  
    NSLog(@"=============屬性賦值及獲取============");
    [p performSelector:@selector(setSex:) withObject:@"男"];
    NSLog(@"屬性sex的值為:%@",[p performSelector:@selector(sex)]);

輸出結果為:

=========動態添加屬性==========
屬性添加成功
=============屬性賦值及獲取============
屬性sex的值為:男
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,751評論 0 9
  • Objective-C語言是一門動態語言,它將很多靜態語言在編譯和鏈接時期做的事放到了運行時來處理。這種動態語言的...
    有一種再見叫青春閱讀 602評論 0 3
  • 我們常常會聽說 Objective-C 是一門動態語言,那么這個「動態」表現在哪呢?我想最主要的表現就是 Obje...
    Ethan_Struggle閱讀 2,225評論 0 7
  • Objective-C語言是一門動態語言,他將很多靜態語言在編譯和鏈接時期做的事情放到了運行時來處理。這種動態語言...
    tigger丨閱讀 1,417評論 0 8
  • 溽暑襲津城 艷陽曝萬物 熱浪蔫垂柳 蜃氣浮蒼穹 棠香溢滿苑 海風熏汗軀 何消心坎炙 敞懷納微涼
    Ocaptain_lyl閱讀 143評論 0 0