iOS基礎·屬性的修飾詞與setter的關系(@property、@synthesize、@dynamic、retain、assign、copy、weak、strong、nonatomic、atomic、readonly、readwrite等修飾詞與setter、getter等存取方法之間的關系)

很多人講屬性修飾詞的時候,喜歡從字面或者定義的角度介紹它們間的區別。這篇文章,我們側重從修飾詞對setter方法的影響直接展示區別。

1. 實例變量:命名區別于全局變量和局部變量


1.1 命名法則:

  • 以下劃線_作為實例變量名字的前綴,如_student
  • 這樣,可以很容易地通過下劃線區分實例變量與其它變量(全局變量,局部變量,靜態變量等)

1.2 聲明位置:

  • 在.h頭文件中
  • 或者,在.m實現文件的類拓展

1.3 聲明形式:

  • 頭文件,寫在類似@interface Person : NSObject {...}這樣的花括號{...}里面
  • 實現文件的類拓展中,寫在類似@interface Person ()<UIScrollViewDelegate> {...}這樣的花括號{...}里面

1.4 聲明示例:

@interface TYPagerController ()<UIScrollViewDelegate> {
    NSInteger   _countOfControllers;
    BOOL        _needLayoutContentView;
    CGFloat     _preOffsetX;
    float    _heightInMeters;

    struct {
        unsigned int transitionFromIndexToIndex :1;
        unsigned int transitionFromIndexToIndexProgress :1;
    }_delegateFlags;
    
    struct {
        unsigned int transitionFromIndexToIndex :1;
        unsigned int transitionFromIndexToIndexProgress :1;
    }_methodFlags;
}

1.5 setter、getter方法

可以自己手動為實例變量在頭文件 中聲明setter、getter方法,并在實現文件中實現setter、getter方法。你也可以不聲明不實現,但不要再企圖調用setter、getter方法了,甚至點語法。

//一個例子
//實現getter
- (float)heightInMeters{
    return _heightInMeters;
}
//實現setter
- (float)setHeightInMeters:(float)h{
    _heightInMeters = h;
}

1.6 調用setter、getter方法

如果你實現了setter,getter方法,才可以調用存取方法,例如:

//調用getter
float temp = [self heightInMeters];
//調用setter
[self setHeightInMeters:10.0];

1.7 點語法

如果你實現了setter,getter方法,才可以使用點語法 簡化調用存取方法。

//調用getter
float temp = self.heightInMeters;
//調用setter
self.heightInMeters = 10.0;

1.8 實例變量類型:

  • 1)可以是簡單的C類型,如 int _sudentNum;,float _heightInMeters;

這種實例變量及其值會在聲明對象的內部保存。

  • 2)可以是指針,用來指向其他對象,如NSString *tempStr,CMPersonModel *personModel等等。這種屬性叫對象實例變量

這種變量,聲明的對象內部僅保存指向相應實例對象的指針(對象地址),而不保存實例對象本身。實例對象本身由堆負責保存,管理機制由ARC負責。

1.9 繼承特性:

  • 子類繼承不了父類寫在類拓展 中的示例變量

2. 屬性:自動聲明實例變量和存取方法,并實現存取方法


2.1 聲明位置:

  • 聲明頭文件
  • 或者實現文件的類拓展中

2.2 聲明形式:

  • 寫在@interface與@end之間,花括號{...}之外
  • 必須有@property修飾詞修飾,后面可選擇性地添加其他修飾詞如(nonatomic, strong) 等

2.3 聲明示例:

#import <UIKit/UIKit.h>
@interface TMAddCategoryViewController ()
@property (nonatomic, strong) NSMutableArray *dataSource;
@end

2.4 存取方法:編譯器會自動聲明和實現

  • @property會讓編譯器自動聲明相應的實例變量和存取方法,并實現存取方法。除非你用其它關鍵詞修飾,專門告訴編譯器做什么其它特殊處理。
  • 即使自動生成存取方法,遇到一些需求時,你也可以再自己重寫存取方法。一般添加數據模型示例對象的時候,喜歡重寫getter方法,設置一些默認值,這種叫懶加載。
  • 有一些例外,不會自動生成存取方法:
  1. 同時重寫了getter setter
  2. 重寫只讀屬性的 getter
  3. 使用了@dynamic
  4. @protocol 中定義的屬性
  5. category 中定義的屬性
  6. 重載的屬性:當你在子類中重載父類的屬性,你必須用 @synthesize 手動合成

2.5 示例:重寫getter

- (NSMutableArray *)dataSource
{
    if (!_dataSource) {
        _dataSource = [NSMutableArray array];
        RLMResults *results = [[TMDataBaseManager defaultManager] queryAddCategorysWithPaymentType:self.paymentType];
        for (TMAddCategory *category in results) {
            [_dataSource addObject:category];
        }
    }
    return _dataSource;
}

注:RLMResults是第三方數據庫框架Realm中的一個類名

2.6 示例:重寫setter

- (void)setDataSource:(id<iCarouselDataSource>)dataSource
{
    if (_dataSource != dataSource)
    {
        _dataSource = dataSource;
        if (_dataSource)
        {
            [self reloadData];
        }
    }
}

2.7 重寫setter和getter導致的特別情況:

@property聲明的屬性,編譯器是否會合成存取方法和成員變量有如下三種特別情況

  • 若手動實現了setter方法,編譯器就只會自動生成getter方法
  • 若手動實現了getter方法,編譯器就只會自動生成setter方法
  • 若同時手動實現了setter和getter方法,編譯器就不會自動生成不存在的成員變量

2.8 編譯器自動實現的存取方法有什么弱點?

  • @property只會生成最簡單的getter/setter方法,而不會進行數據判斷

2.9 指定所生成的方法的方法名稱

  • getter=你定制的getter方法名稱
  • setter=你定義的setter方法名稱(注意setter方法必須要有 :)
  • 示例:
@property(getter=isMarried)BOOL married;

通常BOOL類型的屬性的getter方法要以is開頭

2.10 繼承特性:

  • 父類聲明在頭文件 中的屬性,子類無法繼承這些屬性聲明的實例變量,只能看到屬性自動生成的存取方法。

3. 修飾詞:@synthesize 與 @dynamic

修飾詞:告訴編譯器是否或怎樣自動給屬性生成存取方法


@property有兩個對應的修飾詞,一個是@synthesize,一個是@dynamic。如果@synthesize和@dynamic都沒寫,那么默認的就是@syntheszie var = _var;。 顯然,這兩個修飾的功能是互斥的。

3.1 @synthesize 與 @dynamic

3.1.1 位置

@dynamic或者@synthesize,寫在.m文件的@implementation中。

3.1.2 功能區分

理解這兩個修飾詞的功能,可以先看看這兩個單詞的意思。

  • synthesize 與 dynamic 英文意思
    • synthesize ['s?nθ?sa?z] v. 合成;綜合
    • dynamic [da?'n?m?k] adj. 動態的;動力的;動力學的;有活力的
3.1.3 單詞區分

單詞混淆: 多線程的概念里面有個關鍵詞@synchronized跟@synthesize容易視覺混淆,這里也看看兩個單詞的意思。

  • synthesize 與 synchronized 單詞比較
    • synthesize ['s?nθ?sa?z] v. 合成;綜合
    • synchronized ['s??kr?na?zd] adj. 同步的;同步化的 v. 使協調(synchronize的過去分詞);同時發生;校準

3.2 @synthesize

3.2.1 介紹

定義屬性后,編譯器會自動編寫訪問這些屬性所需的方法,此過程叫做自動合成 (autosynthesis)。需要強調的是,這個過程由編譯器在編譯期執行,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼。除了生成方法代碼 getter、setter 之外,編譯器還要自動向類中添加適當類型的實例變量,并且在屬性名前面加下劃線,以此作為實例變量的名字。

3.2.2 用法
  • 多個屬性可以通過一行@synthesize搞定,多個屬性之間用逗號連接
  • 可以在類的實現代碼里通過@synthesize語法來指定實例變量的名字。
@synthesize name = realName;

對于上面的實例變量則為生成的是realName而不是name,方法也對應改變。定義的實例變量是根據@synthesize name = realName;來定的。用的比較多的情況是:

@synthesize name = _name;

上述代碼的意思是,在@property 聲明的name,在setter/getter方法中使用NSObject * _name;這個實例變量來賦值與返回。

3.2.3 三種寫法比較
@synthesize age = _age;
  • setter和getter實現中會訪問成員變量_age
  • 如果成員變量_age不存在,就會自動生成一個@private的成員變量_age
@synthesize age;//等效下面
@synthesize age = age;
  • setter和getter實現中會訪問@synthesize后同名成員變量age
  • 如果成員變量age不存在,就會自動生成一個@private的成員變量age
3.2.4 用法場景

當你在子類中重載了父類中的屬性,你必須使用@synthesize來手動合成實例變量。

3.3 @dynamic

3.3.1 介紹
  • @dynamic告訴編譯器:屬性的setter與getter方法由用戶自己實現,不自動生成。(當然對于readonly的屬性只需提供getter即可)。
3.3.2 崩潰
  • 假如一個屬性被聲明為@dynamic var,然后你沒有提供@setter方法和@getter方法,編譯的時候沒問題,但是當程序運行到instance.var = someVar,由于缺setter方法會導致程序崩潰;或者當運行到 someVar = var時,由于缺getter方法同樣會導致崩潰。
  • 編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。

3.4. 注意的事

  • @synthesize 僅僅是一個 clang 的 Objective-C 語言擴展 (autosynthesis of properties), 然后clang恰好是 Xcode 的默認編譯器.
  • 如果編譯器換成了 gcc, 那么這個特性也就不復存在了。

4. 其它修飾詞


很多人講這些修飾詞的時候,喜歡從字面或者定義的角度介紹它們間的區別。這篇文章,我們從修飾詞對setter方法的影響直接展示區別。

retain、assign、copy、weak、strong、nonatomic、atomic、readonly、readwrite

假設為了修飾一個屬性nameStr,代碼如下:

#import <UIKit/UIKit.h>
@interface CMViewController ()
@property (nonatomic, strong) NSString *nameStr;
@end

其中,當括號內的修飾詞(nonatomic, strong)換成下面各種修飾詞的時候,分別分析一下setter方法(有些修飾詞修飾字符串并不合適,但這里僅為分析區別)。

4.1 assign

4.1.1 基本特性
  • assign (默認):直接賦值,不更改引用計數。
4.1.2 對setter的影響
  • assign修飾詞對setter的影響:
- (void) setName:(NSString *)newValue{
  nameStr = newValue;
}
4.1.3 使用場景
  • 常用于基本數據類型(NSInteger)和C數據類型(int、float、double、char以及id類型。
  • 這個修飾符不會牽涉到內存管理,但是如果是對象類型,可能會導致內存泄漏或者EXC_BAD_ACCESS錯誤。
  • 除了assign以外的其他修飾符,是必須用于修飾OC對象的。
4.1.4 危險場景
  • assign修飾的對象銷毀后不會把該對象的指針置nil。對象已經被銷毀,但指針還在癡癡的指向它,這就成了野指針,這是比較危險的。所以assign修飾的OC屬性是非常危險的,比如,一些老的第三方框架用assign修飾的delegate屬性經常會導致崩潰。

4.2 retain

4.2.1 基本特性
  • retain: 指針拷貝。指針拷貝后,地址不變,內容不變,引用計數器加1。
4.2.2 對setter的影響
  • retain修飾詞對setter的影響:
- (void) setName:(NSString *)newValue{
  if (nameStr !=newValue){
     [nameStr release]
     nameStr = [newValue retain];
  }
}
4.2.3 使用場景
  • 適用NSObject和其子類。MRC下,用于修飾多數的OC類型的對象。ARC下,一般功能被strong取代。
  • 不能用于基本的數據類型或者Core Foundation的對象(retain操作會使對象的引用計數器加1,但是基本的數據類型或者Core Foundation對象壓根就沒有引用計數器,所以無法進行retain操作。換言之:基本數據類型或者CF不是指針,不是指針就無法進行retain操作。對象即指針嘛)。

4.3 copy

4.3.1 基本特點
  • copy是內容拷貝。釋放舊對象,然后建立一個索引計數為1的對象。
  • strong修飾的屬性在賦值時不會調用copy,而copy修飾的屬性在賦值相當于自動多調用了一次copy方法。
4.3.2 對setter的影響
  • copy修飾詞對setter的影響:
- (void) setName:(NSString *)newValue{
  if (nameStr !=newValue){
     [nameStr release]
     nameStr = [newValue copy];
  }
}
4.3.3 使用場景

copy的使用場景為,實現了NSCopying protocol的類,我想獲取它的值,但是我又不想在原對象上改變,于是深賦值一份新的值給你,讓你來自由操作。

  • 用于修飾block。
  • 用于含有可深拷貝的mutable子類的類,如NSString,NSArray,NSSet,NSDictionary,NSData的,NSCharacterSet,NSIndexSet。
  • 但是NSMutableArray這樣的不可以,Mutable的不能用copy,不然初始化會有問題。

4.4 weak

weak、strong是ARC出現后才出現的概念,但這并不代表weak、strong這兩個修飾都不能在MRC模式下使用。事實上,strong在MRC模式依舊可使用。

4.4.1 基本特性
  • weak 用來修飾強引用的屬性,類似于對應原來的assign。
  • weak是一種弱引用,并不會使對象的引用計數加1,可以避免循環引用的問題。
  • 不保留傳入的對象。如果該對象被釋放,那么相應的實例變量會被自動賦為nil,不會變為懸空指針(也稱野指針)。懸空指針指向的是不再存在的對象,向懸空指針發送消息通常會導致程序崩潰。
4.4.2 兩種模式下
  • MRC模式
    • weak: MRC模式下無法使用
  • ARC模式
    • weak: 弱引用,不會使對象的引用計數器加1。
4.4.3 與assign的區別
  • weak修飾的對象銷毀的時候,指針會自動設置為nil。而assign不會。
  • assign可以用于非OC對象,而weak必須用于OC對象。
4.4.4 使用場景
  • 用于修飾UI控件。
  • UI控件拖到xib/Storyboard后,系統自動為控件賦了strong,所以拖到代碼就用weak就行了。
  • 代理屬性。@property (nonatomic, weak) id delegate; // 修飾代理屬性
4.5.5 對setter的影響
  • weak修飾詞對setter的影響:假設nameStr和newValue都是用weak修飾的屬性
[nameStr release]
nameStr = newValue;

4.5 strong

4.5.1 基本特性

strong 用來修飾強引用的屬性,類似于對應原來的retain。

4.5.2 兩種模式
  • MRC模式
    • strong: 與retain等價
  • ARC模式
    • strong: 強引用(它使對象的引用計數加1)
4.5.3 使用場景
  • 當要保住某個對象的命,讓這個對象可以用于其他的方法時(即在某段時間內要經常用到這個對象,又不想每次用到這個對象都要重新alloc),此時你要把這個對象變為強指針,即變為strong,讓strong強引用著這個對象,使這個對象不會被釋放。
  • 用于修飾NSMutableArray,NSMutableDictionary等copy無法修飾的屬性。
  • 一般的指針變量默認就是strong類型的,所以我們對于strong變量不加__strong修飾。
NSString *name = self.nameField.text;  // 等價
__strong NSString *name = self.nameField.text;  // 等價
4.5.4 對setter的影響
  • strong修飾詞對setter的影響:假設nameStr和newValue都是用strong修飾的屬性
[newValue retain]
[nameStr release]
nameStr = newValue;

4.6 讀寫屬性

讀寫性修飾符——readwrite、readonly

4.6.1 readwrite
  • readwrite(默認): 可讀可寫(系統自動創建getter 和 setter 方法)
4.6.1 readonly
  • readonly: 只讀,系統只會生成 getter方法

4.7 原子屬性

4.7.1 atomic
  • 1.原子屬性,聲明的屬性默認就是atomic.所以底層默認為屬性的setter方法加鎖,目的就是防止多(條)線程訪問同一個內存地址,造成數據錯誤。
  • 2.在多線程環境下,原子操作非常有必要,因為它能提供多線程安全,如果沒有原子操作,可能引起異常。--->線程保護
  • 3.需要消耗大量的資源。
4.7.2 nonatomic
  • 1.非原子屬性,不會為setter方法加鎖。
  • 2.沒有涉及多線程的編程時,用nonatomic。
  • 3.不會消耗大量的資源,所以會提高性能。

5. @property的幾個例子

@property (nonatomic, readonly) CGFloat itemWidth;
@property (nonatomic, assign) double brightness;
@property (nonatomic, assign, getter=isOpenMenu) BOOL openMenu;
@property (nonatomic, strong) UILabel *newsBooksLabel;
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, weak) IBOutlet UIButton *nextButton;
@property (nonatomic, copy) void (^cancelBtnBlock)();
@property (nonatomic, weak) id<TMSideCellDelegate> sideCellDelegate;

6. 推薦閱讀

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 最近,關于房價的話題都被刷屏了,斗膽我用此做個標題哈哈。 講買房之前,允許我先介紹下一個新詞! 你知道有個詞叫“斜...
    斜杠青年行動社閱讀 516評論 0 2
  • 大神萬理小鳥游事務所以前搞過 模特 舞者 和聲i7這個project社長計劃了很久のり弁→幕ノ內 / 懷石(暫定)...
    fantasmagoria閱讀 216評論 0 0
  • 兵荒馬亂的青春: 像栗子小姐一樣,一愛四年!四年的青春,迷戀著自己心中的那份愛,即使她周圍的那些粉紅色泡泡,...
    畫細語閱讀 361評論 4 4