Objective c 知識總結 @property

半紙淵發表於2017-12-14

目錄

一、屬性宣告的概念、構成、訪問

1. 屬性宣告的初現版本
2. 屬性宣告的概念
3. 屬性宣告的構成
4. 屬性訪問方式
複製程式碼

二、屬性宣告的自動合成

1. @synthesize 
2. @dynamic
3. 自動合成?
複製程式碼

三、屬性宣告的可選選項 ( 關鍵字 )

1. 方法名類關鍵字解析
2. 讀寫許可權類關鍵字解析
3. 賦值操作類關鍵字解析 【重點】
4. 原子性操作類關鍵字解析
5. 類屬性關鍵字解析【 OC 新增】
複製程式碼

四、屬性的 Runtime 實現

1. 屬性在Runtime 的內部實現
2. protocol & category 的應用(關聯物件)
複製程式碼

五、參考書籍、文章


一、屬性宣告的概念、構成、訪問

1. 屬性宣告的初現版本
  • 屬性宣告是 Objective-C 2.0 的新增功能;
  • @property 是編譯器指令,@property 完成的工作就是屬性宣告;
2. 屬性宣告的概念
  • 屬性? 屬性是指物件的特性。
  • 屬性宣告? 屬性宣告是一種宣告變數為屬性的語法。
  • 屬性的實現? 宣告瞭例項變數或定義了相應的訪問方法(存取方法)即為實現了屬性。
  • Objective-C 2.0 屬性的概念
    OC 的屬性概念
3. 屬性宣告的構成

Property 的書寫格式

  • 第一部分:@property @property 只是一個編譯器指令,意思是告訴編譯器要幹嘛,當然它的意思就是要求 Xcode 做屬性宣告瞭。
  • 第二部分:選項列表 這些也叫屬性關鍵字,它們分別有,如表:
種類 關鍵字 描述
修改方法名類 setter = 新的 OC 方法名 修改預設生成的方法名( selector )
—— getter = 新的 OC 方法名 ——
讀寫許可權類 readonly 表明變數只讀,只生成 getter 方法
—— readwrite 表明變數可讀寫【預設值】
賦值操作類 assign 直接賦值 ( MRC / ARC 均可用 )【預設值】
—— retain 進行保持操作,持有物件 ( 僅 MRC 可用 )
—— unsafe_unretained 直接賦值 ( 僅 ARC )
—— strong 強引用,持有物件 ( 僅 ARC 可用 )【預設值】
—— weak 弱引用,不持有物件 ( 僅 ARC 可用 )
—— copy 拷貝副本 (深度拷貝)
原子性操作類 nonatomic 非原子性操作,執行緒不安全
—— atomic 原子性操作,執行緒安全【預設值】
類屬性 class 永遠不自動合成存取方法,需手動實現;不宣告例項變數,因為它是類變數;【iOS 10, Xcode 8】
空類 nonnull 不能為空【iOS 9, Xcode 7】
—— nullable 可以為空【iOS 9, Xcode 7】
—— null_resettable setter 方法可以是 nil,getter 方法不能返回 nil,要重寫 getter 方法【iOS 9, Xcode 7】
—— null_unspecified(_Null_unspecified) 不確定是否為空【iOS 10, Xcode 8】(【iOS 9, Xcode 7】)

詳細描述請移步至,本文 第三章: 屬性宣告的可選選項 ( 關鍵字 );

  • 第三部分:變數型別 + 變數名+ ; 這一部分和宣告例項變數的情況是一樣的;
4. 屬性訪問方式
  • 訪問的方式有:
    • 通過直接使用例項變數
    • 使用編譯器提供的點運算子,實現屬性存取方法的呼叫,從而間接使用例項變數;

注意:id 型別的變數不能使用點操作符進行訪問,原因是 Xcode 不知道是否存在對應的存取方法;


二、屬性宣告的自動合成

Property 的組成

1. @synthesize :自動編寫存取方法
@interface ......
@property (nonatomic, strong) int age;
@end
@implementaion XXXClass
@synthesize age = _age;
@end
複製程式碼
  • 修改屬性宣告的變數名,上面的例子就是修改屬性宣告的 age 變數名改為 _age 變數名;
  • 告訴編譯器要自動合成 setter、getter 方法(readwrite、readonly)
// 情況 1 readwrite,同時生成 setter、getter 方法
@property (nonatomic, strong) int age;
//////
  - (void)setAge:(int)age {
  // do something
}
  - (int)age {
  // do something
  return _age;
}
//////
複製程式碼
// 情況 2 readonly,只生成 getter 方法
@property (nonatomic, strong, readonly) int age;
//////
  - (int)age {
  // do something
  return _age;
}
//////
複製程式碼

如果宣告瞭屬性 @property,同時又手動實現了相應的存取方法,就一定要寫 @synthesize 不然照樣報警告;

2. @dynamic : 手動編寫存取方法
@interface ......
@property (nonatomic, strong) int age;
@end
@implementaion XXXClass
@synthesize age = _age;
@end
複製程式碼

告訴編譯器要手動編寫 setter、getter 方法(readwrite、readonly)

// 情況 1 readwrite,必須要同時編寫 setter、getter 方法
@property (nonatomic, strong) int age;
//////
  - (void)setAge:(int)age {
  // do something
}
  - (int)age {
  // do something
  return _age;
}
//////
複製程式碼
// 情況 2 readonly,只需編寫 getter 方法
@property (nonatomic, strong, readonly) int age;
//////
  - (int)age {
  // do something
  return _age;
}
//////
複製程式碼

此處程式設計師手動編寫的 setter 、getter 方法必須要嚴格按照 存取方法的命名要求進行編寫:

setter --> setValueName:
getter --> valueName
複製程式碼

不然在呼叫屬性存取方法的時候,會出現訪問出錯的;

3. 自動合成?

從 Xcode 4.4 開始,當我們用 @property 進行屬性宣告的時候,編譯器就會自動幫我們生成相應的 例項變數 + 存取方法宣告 + 存取方法實現;
那什麼情況下會破壞這種自動合成的過程呢?
正常的使用情況:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic) NSUInteger age;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    _age = 18;
    self.age = 20;
    NSLog(@"self.age = %lu", (unsigned long)self.age);
    
}

@end
複製程式碼

例項變數 + 存取方法宣告 + 存取方法實現 都正常地擁有了;

  • 例項變數的情況
    Objective c 知識總結    @property
    Objective c 知識總結    @property

這裡直接證明了以下幾點:

  1. Xcode 幫我們生成(把原來的變數名改成)了,帶下劃線的例項變數;
  2. 宣告並生成了變數名對應的存取方法;

** 讓警告消失 **

Objective c 知識總結    @property
Objective c 知識總結    @property


#import "ViewController.h"

@interface ViewController (){
    NSUInteger __age;
}
@property (nonatomic) NSUInteger _age;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    _age = 18;
    __age = 19;
    self.age = 20;
    self._age = 23;
    NSLog(@"self.age = %lu", (unsigned long)self.age);
    NSLog(@"self.age = %lu", (unsigned long)self._age);
    
}

@end
複製程式碼

那個警告明顯是說,我自動合成的例項變數是__age,而不是 _age,所以你應該定義一個 __age 的例項變數才對,不然我就警告你;

其實這裡是間接地證明了,如果你自己定義了相應的帶下劃線的例項變數,那麼 Xcode 就不會自己合成屬性相應的例項變數了;

簡而言之,寫了 NSUInteger __age;@property (nonatomic) NSUInteger _age; Xcode 只會合成相應的 存取方法宣告 + 存取方法實現;

  • 存取方法情況
    Objective c 知識總結    @property
    很明顯地,如果存取方法都手動實現了,那麼自然就把自動合成的機制打破了,連 _age 例項變數都不會幫你生成,當然連 age 例項變數也不會有;

讓錯誤消失

Objective c 知識總結    @property

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic) NSUInteger age;
@end

@implementation ViewController

@synthesize age = _age;

- (void)setAge:(NSUInteger)age {
    
    _age = age;
    
}

- (NSUInteger)age {
    
    return _age;
    
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    age = 19;
    
    _age = 18;
    self.age = 20;
    NSLog(@"self.age = %lu", (unsigned long)self.age);
    
}

@end
複製程式碼

新增 @synthesize age = _age; 程式碼就是告訴 Xcode 幫我把 age 改成 _age 並生成相應的例項變數,屬性的錯誤就可以修復了;

當然 age 那個錯誤可以直接忽略,因為壓根就不會有它的出現;

當然如果隻手動做一個方法的實現:

Objective c 知識總結    @property
因為這裡 age 預設是 readwrite 的,所以肯定還有兩個方法(存取),如果隻手動實現其中一個,就相當於告訴 Xcode 我還有一個方法你幫我實現了吧;

那麼如果屬性是 readonly 的呢?

Objective c 知識總結    @property
如果是 readonly 的屬性宣告,只可以有讀取方法(getter),所以你手動實現了它的 getter 方法,其實和 readwrite 情況下手動實現 setter 和 getter 的情況是一樣一樣的;

讓錯誤消失

Objective c 知識總結    @property
同樣地,新增 @synthesize age = _age; 即可;

當然,它是沒有 setter 方法的,你也想要有,也可以任性地自己寫一個,但是 readonly 為什麼不改成 readwrite 呢?

  • 例項變數和存取方法都寫了的情況
    Objective c 知識總結    @property
    我覺得這個很明顯了, Xcode 不會幫你生成 例項變數 + 存取方法(宣告加實現);

如果加個 @dynamic age; 呢?執行時掛 了:

Objective c 知識總結    @property

不可以掛

Objective c 知識總結    @property

前者 setter Xcode 自動合成了,而後者是沒有合成,現在應該知道 @dynamic 的用意了吧。


三、屬性宣告的可選選項 ( 關鍵字 )

1. 方法名類關鍵字解析

@property ( setter = Age: ) int age;

其中**Age:就是新的方法名,它其實是一個 selector ;
它會替換預設生成的
setAge:**方法;
當然 getter 也是這樣使用;

2. 讀寫許可權類關鍵字解析
  • readonly,只讀只生成相應的 getter 方法,以及帶下劃線的例項變數; @property ( readonly ) int age;

  • readwrite,生成 setter 、getter 方法,以及帶下劃線的例項變數; @property ( readwrite ) int age; -- a @property int age; -- b a、b 結果是一樣的,原因是 readwrite 是預設的讀寫許可權; 它們都生成了,setAge: 、age 存取方法的宣告和實現,_age 例項變數;

3. 賦值操作類關鍵字解析 【重點】
  1. assign、unsafe_unretained,僅用於基礎資料型別( C 型別)
  2. retain、strong、weak、copy,僅用於 OC 物件
setter 、getter 方法的區別:
  • assign 與 unsafe_unretained
    • 變數直接賦值
    • assign 可用於 MRC/ARC ,而 unsafe_unretained 只能用於 ARC ;
    • setter、getter 方法:
// 屬性宣告
@property ( nonatomic, assign) int age;
//  MRC 環境下,它們相同 @property ( nonatomic ) int age;
// setter 的自動實現
    - (void)setAge:(int)age {
      _age = age;
  }
// getter 的自動實現
    - (int)age {
      return _age;
}
複製程式碼
// 屬性宣告
@property ( nonatomic, unsafe_unretained ) int age;
// setter 的自動實現
    - (void)setAge:(int)age {
      _age = age;
  }
// getter 的自動實現
    - (int)age {
      return _age;
}
複製程式碼
  • retain 與 strong
    • 變數被持有,前者對應物件的記憶體計數器加 1 ,後者對應物件會被強引用;
    • retain 只用於 MRC ,而 strong 只能用於 ARC ,且 ARC 預設的賦值關鍵字為 strong;
    • setter、getter 方法:
// 屬性宣告
@property ( nonatomic, retain) NSObject *obj;
// setter 的自動實現
    - (void)setObj:(NSObject *)obj{
      if ( _obj != obj ) {
        [_obj release];
        _obj = [obj retain];
      }
  }
// getter 的自動實現
    - (NSObject *)obj{
      NSObject *objTemp = [ [ _obj retain ] autorelease ];
      return objTemp;
}
複製程式碼
// 屬性宣告
@property ( nonatomic, strong) NSObject *obj;
// 相同, @property ( nonatomic ) NSObject *obj;
// setter 的自動實現
    - (void)setObj:(NSObject *)obj{
      _obj = obj;  // 這裡預設有 __strong 修飾
                   // __strong _obj = obj;
  }
// getter 的自動實現
    - (NSObject *)obj{
      return _obj ;
}
複製程式碼
  • weak
    • 變數不被持有,對應物件會被弱引用與 strong 相對;
    • weak 只能用於 ARC ,weak 修飾的物件在被銷燬的時候,對應的物件指標會自動置為 nil;
    • setter、getter 方法:
// 屬性宣告
@property ( nonatomic, weak) NSObject *obj;
// setter 的自動實現
    - (void)setObj:(NSObject *)obj{
      __weak _obj = obj;
  }
// getter 的自動實現
    - (NSObject *)obj{
      return _obj ;
}
複製程式碼
  • copy
    • 深度拷貝物件(相當於建立了新的例項物件),是不可變副本;
    • copy 只能用於遵守了 NSCopying 的物件 ,不然會出錯;MRC 、ARC 環境下均可用;
    • setter、getter 方法:
// MRC 下:
// 屬性宣告
@property ( nonatomic, copy ) NSObject *obj;
// setter 的自動實現
    - (void)setObj:(NSObject *)obj{
      if ( _obj != obj ) {
        [_obj release];
        _obj = [obj copy];
      }
  }
// getter 的自動實現
    - (NSObject *)obj{
      NSObject *objTemp = [ [ _obj retain ] autorelease ];
      return objTemp;
}
複製程式碼
// ARC 下:
// 屬性宣告
@property ( nonatomic, copy ) NSObject *obj;
// setter 的自動實現
    - (void)setObj:(NSObject *)obj{
      _obj = [obj copy];
  }
// getter 的自動實現
    - (NSObject *)obj{
      return _obj ;
}
複製程式碼

注意:@property ( nonatomic, copy ) NSMutableArray *mArray; 這種宣告真的是自己挖坑了;你要的是可變物件,但是一 copy 就是不可變物件了,執行時會出問題的;

4. 原子性操作類關鍵字解析
  • atomic
    • 原子性,存取方法均加鎖保護,保證原子性;
    • 執行緒安全,但低效,MRC 、ARC 環境下均可用;
    • setter、getter 方法:【copy 關鍵字作為例子,就是在原來的基礎上加鎖】
// MRC 下:
// 屬性宣告
@property ( nonatomic, copy ) NSObject *obj;
// setter 的自動實現
    - (void)setObj:(NSObject *)obj{
      [_ex lock];
      if ( _obj != obj ) {
        [_obj release];
        _obj = [obj copy];
      }
      [_ex unlock];
  }
// getter 的自動實現
    - (NSObject *)obj{
      [_ex lock];
      NSObject *objTemp = [ [ _obj retain ] autorelease ];
      [_ex unlock];
      return objTemp;
}
複製程式碼
// ARC 下:
// 屬性宣告
@property ( nonatomic, copy ) NSObject *obj;
// setter 的自動實現
    - (void)setObj:(NSObject *)obj{
      [_ex lock];
      _obj = [obj copy];
      [_ex unlock];
  }
// getter 的自動實現
    - (NSObject *)obj{
      return _obj ;
}
複製程式碼
  • nonatomic
    • 非原子性,存取方法不加鎖保護;
    • 執行緒不安全,但高效,MRC 、ARC 環境下均可用;
    • setter、getter 方法:【copy 關鍵字作為例子】
// 其實上面的例子已經很多了,所以這裡就不貼程式碼了
複製程式碼
5. 類屬性關鍵字解析【 OC 新增】
  • class 關鍵字是表示定義的變數是類變數,就是元類的變數;
  • 那麼相應地,它的存取方法當然就是類方法了;
  • 它永遠不會自動合成,所以類變數、類存取方法,都要自己手動實現;
  • setter、getter 方法:
@property ( nonatomic, class) int age;
@property ( nonatomic, class) NSObject *obj;
複製程式碼
  • Ep:
#import "ViewController.h"
@interface ViewController ()
@property (class, nonatomic) int number;
@end
複製程式碼
@implementation ViewController
@dynamic number;
static int __number = -1;
  - (void)setNumber:(int)n {
    __number = n;
}
  - (int)number {
    return __number;
}
 - (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.number = 77;
    NSLog(@"number %@", @(self.number)); 
}
@end
複製程式碼
  • EP2:
    Objective c 知識總結    @property
    未手動實現相應方法
  • 解決問題: 增加一句程式碼即可。
    Objective c 知識總結    @property
    但是是不會生成 _name 這個變數的,要自己手動新增,
    Objective c 知識總結    @property
    Objective c 知識總結    @property
    如果其它檔案要使用到這個變數怎麼調取【自身調取同理】:
    失敗【廢話】
    木有哦
    都說是類屬性了,肯定用類呼叫嘛,試試啊~~~
    類方法的提示證明有相關的方法宣告
    外部呼叫
    內部呼叫
    好興奮啊~~~
    掛了
    就是告訴自己寫 Get Set 方法吧,Xcode 只是宣告一下而已:
    增加相應的類方法
    再試一下,
    成功了
6. 空類關鍵字解析

它們只能用於指標變數,當然例項物件肯定是可以用的咯

Objective c 知識總結    @property
Objective c 知識總結    @property

Xcode7 iOS9 OC新增 nonnull/nullable/null_resettable/null_unspecified

  • nonnull
    • 指標變數不可以為空(nil/Nil/NULL);
    • setter、getter 方法不變;

補充:如果宣告的屬性有多個是需要 nonnull 修飾的話,可以使用一對巨集來簡化屬性程式碼:

// NS_ASSUME_NONNULL_BEGIN 
#define NS_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin")
// NS_ASSUME_NONNULL_END   
#define NS_ASSUME_NONNULL_END   _Pragma("clang assume_nonnull end")
複製程式碼

Ep:

#import "ViewController.h"
//
@interface ViewController ()
NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) NSUInteger *number;
@property (nonatomic) NSObject *obj;
@property (nonatomic) NSObject *obj1;
@property (nonatomic) NSObject *obj2;
@property (nonatomic) NSObject *obj3;
@property (nonatomic) NSObject *obj4;
@property (nonatomic) NSObject *obj5;
NS_ASSUME_NONNULL_END
@property (nonatomic) NSObject *obj6;
@end
//
@implementation ViewController
//
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //
    self.number = nil;
    self.obj = nil;
    self.obj6 = nil;
    //
}
//
@end
複製程式碼

Objective c 知識總結    @property
Objective c 知識總結    @property

  • nullable

    • 指標變數可以為空(nil/Nil/NULL);
  • setter、getter 方法不變;

  • null_resettable

    Objective c 知識總結    @property
    Objective c 知識總結    @property

  • setter 可以是 nil,但 getter 不能返回nil;

  • 重寫 setter 或 getter 方法,警告都會取消,但是正確的做法是重寫 getter 方法處理返回 nil 的情況;

Ep:

#import "ViewController.h"
//
@interface ViewController ()
@property (nonatomic, null_resettable) NSString *obj;
@end
//
@implementation ViewController
//
//- (void)setObj:(NSString *)obj {
//
//    if ([obj isEqualToString:@""]) {
//        _obj = @"Hello !";
//    }
//    
//}
//
- (NSString *)obj {
    //
    if (_obj == nil) {
        _obj = @"Hello !";
    }
    //
    return _obj;
    //
}
//
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //
    self.obj = nil;
    //
}
//
@end
複製程式碼
  • null_unspecified(_Null_unspecified)
    • 不確定是否為空;
    • _Null_unspecified 是 Xcode 6.3 開始使用的,null_unspecified Xcode 8 開始使用,並能寫進 @property 的選項列表中;

Ep【 Xcode 7, iOS 9】:

#import "ViewController.h"
//
@interface ViewController ()
@property (nonatomic)  NSString * _Null_unspecified obj;
@end
//
@implementation ViewController
//
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //
    self.obj = nil;
    //
}
//
@end
複製程式碼

Ep【 Xcode 8, iOS 10】:

#import "ViewController.h"
//
@interface ViewController ()
@property (nonatomic, null_unspecified)  NSString * obj;
@end
//
@implementation ViewController
//
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //
    self.obj = nil;
    //
}
//
@end
複製程式碼

補充:有一些關鍵字當然也可以用於變數宣告那裡咯,大體有那些,你可以試試;

#import "ViewController.h"
//
@interface ViewController ()
@property (nonatomic)  NSString * const obj;
@property (nonatomic)  __weak NSString * obj2;
@property (nonatomic)   NSString * _Nonnull  obj3;
@end
//
@implementation ViewController
//
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //
    self.obj = nil;
    //
}
//
@end
複製程式碼

四、屬性的 Runtime 實現

核心內容在 <objc/runtime.h> 中:

Objective c 知識總結    @property
核心參考《Objective-C Runtime Programming Guide》:
Objective c 知識總結    @property

1. 屬性在Runtime 的內部實現
  • 屬性的定義
typedef struct objc_property *Property;
複製程式碼
/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;
複製程式碼

objc_property 就是屬性的真正型別,是一個結構體,下面看看它的屬性描述定義:

/// 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_attribute_t 是用於描述屬性的,就是儲存屬性的資訊;

  • 屬性的獲取
    • 獲取類的屬性列表(所有屬性) class_copyPropertyList --> 拷貝類宣告的所有屬性
  /** 
   * 類中宣告的所有屬性
   * 
   * @param cls 你想檢查的類.
   * @param outCount 儲存屬性的總數量
   *  如果類中沒有宣告屬性,那麼 outCount 的值不會被改變   
   * 
   * @return objc_property_t * 陣列 
   *  超類中的屬性宣告不會包含在裡面
   *  終端會持續持有這些陣列元素,所以不用的時候要用 free() 釋放掉
   * 
   *  如果類中沒有宣告屬性或 cls = Nil ,那麼返回 NULL,且 outCount = 0
    */
OBJC_EXPORT objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
複製程式碼

下面這兩個是針對 protocol 的:
protocol_copyPropertyList --> 拷貝協議中宣告的所有屬性

  /** 
   * 返回協議中宣告的所有例項屬性宣告
   * 
   * @note 同於
   * \code
   * protocol_copyPropertyList2(proto, outCount, YES, YES);
   * \endcode
   */
OBJC_EXPORT objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
複製程式碼

protocol_copyPropertyList2 --> 功能同上,只不過它可以區分類屬性和例項屬性

  /** 
   * 返回協議中宣告的所有的屬性
   * 
   * @param proto 協議
   * @param outCount 儲存屬性宣告的總數
   * @param isRequiredProperty \c YES 返回要求的屬性, \c NO 返回可選的屬性.
   * @param isInstanceProperty \c YES 返回例項屬性, \c NO 返回類屬性.
     * 
     * @return 是一個 C 型別的指標陣列
     *  其它採納了此協議的協議裡面的屬性宣告不會包含在這裡. 
     *  終端會持續持有這些陣列元素,所以不用的時候要用 free() 釋放掉
     *  如果類中沒有宣告屬性或 cls = Nil ,那麼返回 NULL,且 outCount = 0
  */
OBJC_EXPORT objc_property_t *protocol_copyPropertyList2(Protocol *proto, unsigned int *outCount, BOOL isRequiredProperty, BOOL isInstanceProperty)
    OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0);
複製程式碼
  • 獲取單個屬性 class_getProperty --> 獲取類的某個屬性宣告
  /** 
   * 根據提供的類和屬性名返回屬性
   * 
   * @param cls 類
   * @param name 屬性名
   * 
   * @return objc_property_t * 指向的屬性
   * 如果 cls = Nil 或者 沒有宣告相應的屬性,都會返回 NULL
   */
OBJC_EXPORT objc_property_t class_getProperty(Class cls, const char *name)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
複製程式碼

protocol_getProperty --> 獲取協議的某個屬性宣告

  /** 
   * 根據 protocol 返回指定的屬性
   * 
   * @param proto 協議
   * @param name 屬性名
   * @param isRequiredProperty \c YES 返回要求的屬性, \c NO 返回可選的屬性.
   * @param isInstanceProperty  \c YES 返回例項屬性, \c NO 返回類屬性.
   * 
   * @return 根據引數列表的資訊返回相應的屬性
   *  如果協議沒有宣告相應的屬性會返回 NULL
   */
OBJC_EXPORT objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
複製程式碼
  • 獲取單個屬性的具體資訊
屬性的相關方法
複製程式碼

property_getName --> 獲取屬性宣告的屬性名

  /** 
   * 返回屬性名
   * 
   * @param property 屬性
   * 
   * @return 是一個描述屬性名的 C 字串
   */
OBJC_EXPORT const char *property_getName(objc_property_t property) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
複製程式碼

property_getAttributes --> 獲取屬性宣告的屬性特徵

  /** 
   * 返回屬性的特徵的字串
   * 
   * @param property 屬性
   *
   * @return 是一個描述屬性的特徵的 C 字串
   * 
   * @note 關於特徵字串的格式在 《Objective-C Runtime Programming Guide》 Declared Properties 一節有描述
   */
OBJC_EXPORT const char *property_getAttributes(objc_property_t property) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
複製程式碼

property_copyAttributeList --> 拷貝屬性宣告的所有屬性特徵

  /** 
   * 返回屬性的特徵字串陣列
   * 
   * @param property 要拷貝屬性特徵的屬性
   * @param outCount 屬性特徵總數
   * 
   * @return 屬性特徵的 C 陣列,不再使用的時候要使用 free() 釋放資源
   */
OBJC_EXPORT objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);
複製程式碼

property_copyAttributeValue --> 拷貝屬性宣告的所有屬性特徵值

  /** 
   * 根據屬性的特徵名返回屬性特徵的值
   * 
   * @param property 屬性
   * @param attributeName C 字串的屬性特徵名
   *
   * @return 返回 C 字串形式的特徵值,如果 attributeName 沒有找到就會返回 nil;
   */
OBJC_EXPORT char *property_copyAttributeValue(objc_property_t property, const char *attributeName)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);
複製程式碼
  • 變數的相關方法
例項變數的相關方法
複製程式碼

ivar_getName --> 獲取例項變數的變數名

  /** 
   * 返回例項變數的變數名
   * 
   * @param v 例項變數
   * 
   * @return C 字串形式的例項變數的變數名
   */
OBJC_EXPORT const char *ivar_getName(Ivar v) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
複製程式碼

ivar_getTypeEncoding --> 獲取例項變數的變數型別

  /** 
   * 返回例項變數的變數型別
   * 
   * @param v 例項變數
   * 
   * @return C 字串形式的例項變數的變數型別
   *
   * @note 對於變數的可用型別檢視《 Objective-C Runtime Programming Guide 》 Type Encodings 一節
   */
OBJC_EXPORT const char *ivar_getTypeEncoding(Ivar v) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
複製程式碼

ivar_getOffset --> 獲取例項變數的記憶體偏移量

  /** 
   * 返回例項變數的記憶體偏移量
   * 
   * @param v 例項變數
   * 
   * @return 例項變數的記憶體偏移量
   * 
   * @note 如果是物件型別的例項,就要使用 object_getIvar 來替換這個方法進行訪問記憶體偏移量
   */
OBJC_EXPORT ptrdiff_t ivar_getOffset(Ivar v) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
複製程式碼
  • 應用例子
// 假設有一個類,類名為 ViewController
// 
{
      Class cls = objc_getClass("ViewController");
      int propertiesCount = -1;
      objc_property_t *properties = class_copyPropertyList(&propertiesCount);
      for (int i = 0, i < propertiesCount, i++) {
            objc_property_t property = properties [i];
            NSLog(@"i --> Name: %s, Attributes: %s", property_getName(property), property_getAttributes(property));
      }
}
複製程式碼
2. protocol & category 的應用(關聯物件)

重複:如果宣告並實現了屬性的存取方法就等同於實現了屬性;

objc_setAssociatedObject --> 設定指定例項物件的關聯值

/** 
 * 根據例項物件(object)、key 、policy 關聯一個值
 * 
 * @param object 要關聯的例項物件
 * @param key 用於關聯的 key
 * @param value 要關聯的值,傳入 nil 就相當於重置關聯值
 * @param policy setter 方法的行為,詳細的要檢視《 Objective-C Runtime Programming Guide 》 “Associative Object Behaviors.” 一節
 * 
 * @see objc_setAssociatedObject
 * @see objc_removeAssociatedObjects
 */
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
複製程式碼

補充:Associative Object Behaviors 的內容 描述:就是一個列舉型別,不過裡面的值比較特殊而已

enum {
   OBJC_ASSOCIATION_ASSIGN = 0,
   OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
   OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
   OBJC_ASSOCIATION_RETAIN = 01401,
   OBJC_ASSOCIATION_COPY = 01403
};
複製程式碼

OBJC_ASSOCIATION_ASSIGN --> assign / weak ,直接賦值; OBJC_ASSOCIATION_RETAIN_NONATOMIC --> retain / strong 持有物件,非原子性,執行緒不安全; OBJC_ASSOCIATION_COPY_NONATOMIC --> 拷貝不可變副本,非原子性,執行緒不安全; OBJC_ASSOCIATION_RETAIN --> retain / strong 持有物件,原子性,執行緒安全; OBJC_ASSOCIATION_COPY --> 拷貝不可變副本,原子性,執行緒安全;

objc_getAssociatedObject --> 獲取指定例項物件的關聯值

/** 
 * 根據例項物件和關聯的 key 返回相應的關聯值
 * 
 * @param object 例項物件
 * @param key 關聯的 key
 * 
 * @return 指定例項物件的關聯值
 * 
 * @see objc_setAssociatedObject
 */
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
複製程式碼

objc_removeAssociatedObjects --> 移除例項物件的所有關聯值

/** 
 * 移除例項物件的所有關聯值
 * 
 * @param object 例項物件
 * 
 * @note 這個方法的核心目的是為了方便讓例項物件的所有關聯值還原到初始狀態;你不應該使用此方法來對一個關聯值的進行還原,而應使用 objc_setAssociatedObject 寫入 nil 來達到此目的。
 * 
 * @see objc_setAssociatedObject
 * @see objc_getAssociatedObject
 */
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
複製程式碼

Ep:

Objective c 知識總結    @property

#import <UIKit/UIKit.h>

@interface UIViewController (Test)

- (void)setTestValue:(NSString *)test;
- (NSString *)testValue;

@end
複製程式碼
#import "UIViewController+Test.h"
#import <objc/runtime.h>

static const void * TestValueKey = &TestValueKey;

@implementation UIViewController (Test)

- (void)setTestValue:(NSString *)test {
    
    objc_setAssociatedObject(self, TestValueKey, test, OBJC_ASSOCIATION_COPY_NONATOMIC);
    
}

- (NSString *)testValue {
    
    return objc_getAssociatedObject(self, TestValueKey);
    
}

@end
複製程式碼

具體使用:

Objective c 知識總結    @property

#import "ViewController.h"
#import "UIViewController+Test.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    self.testValue = @"Test Runtime Associated";
    NSLog(@"testValue : %@", self.testValue);
    
}

@end
複製程式碼
  • Category 前向引用

Cocoa沒有任何真正的私有方法。只要知道物件支援的某個方法的名稱,即使該物件所在的類的介面中沒有該方法的宣告,你也可以呼叫該方法。不過這麼做編譯器會報錯,但是隻要新建一個該類的類別,在類別.h檔案中寫上原始類該方法的宣告,類別.m檔案中什麼也不寫,就可以正常呼叫私有方法了。這就是傳說中的私有方法前向引用。 所以說cocoa沒有真正的私有方法。 —— 來自文章《類別(Category)的作用(二)---對私有方法的前向引用》


五、參考書籍、文章

《 Objective-C 程式設計全解 》第3版
《Objective-C Runtime Programming Guide》
《runtime之玩轉成員變數》
《Objective-C Runtime 執行時之二:成員變數與屬性》
《Swift 3.0 令人興奮,但Objective-C也有小改進--Objective-C的類屬性》
《iOS9的幾個新關鍵字(nonnull、nullable、null_resettable、__null_unspecified)》
《Objective—C語言的新魅力——Nullability、泛型集合與型別延拓》