Properties - 屬性

來源于 Ry’s Objective-C Tutorial - RyPress

一個學習Objective-C基礎知識的網站.

個人覺得很棒,所以決定抽時間把章節翻譯一下.

本人的英語水平有限,有讓大家誤解或者迷惑的地方還請指正.

原文地址:http://rypress.com/tutorials/objective-c/properties.html

僅供學習,如有轉摘,請注明出處.


一個對象的屬性允許其他對象查看或者修改它的狀態.但是,在一個良好的面向對象設計中,是不可能(允許)直接獲取一個對象的內部狀態.而是通過存取器(getters與setters)與對象的內部數據進行交互.


Interacting with a property via accessor methods
Interacting with a property via accessor methods

@property指令的目的是通過自動生成存取器方法來方便的創建,配置屬性.它允許你在語義上指定公有屬性的行為,并且不用你操心實現細節.

這個模塊概覽這些不同的允許你變更getters與setters行為的特性.其中的一些特性決定了屬性如何控制(管理)自己內存,所以該模塊也是對OC內存管理的一個實用介紹.對于(這方面)更詳細的討論,請參閱內存管理.

@property指令

首先,讓我們先看一下當使用@property指令時到底發生了什么事情.請看下面的一個簡單Car類的接口和對應實現.

// Car.h
#import <Foundation/Foundation.h>

@interface Car : NSObject

@property BOOL running;

@end
// Car.m
#import "Car.h"

@implementation Car

@synthesize running = _running;    // Optional for Xcode 4.4+

@end

編譯器為running屬性(分別)生成了getter和setter.默認的命名規約是使用其屬性名稱作為getter,在屬性名之前加上set作為setter,其對應的實例變量則是在屬性名前加上下劃線,就像這樣:

- (BOOL)running {
    return _running;
}
- (void)setRunning:(BOOL)newValue {
    _running = newValue;
}

在你通過@property指令聲明了屬性之后,你就能調用這些方法,好像(這些方法)已經被包含在你的類接口和實現文件中.你也可以在Car.m中重寫它們以提供自定義的getter/setter,這樣的話就得必須使用@synthesize指令.然而,你應該沒有必要要自定義存取器,盡管@property指令允許在抽象的層級上這樣做.

通過點語法訪問屬性實際上是轉換成通過上述的存取器方法,所以下面(代碼塊)的honda.running代碼在給其賦值時實際上是調用setRunning:在讀取它的值時是調用running方法:

// main.m
#import <Foundation/Foundation.h>
#import "Car.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Car *honda = [[Car alloc] init];
        honda.running = YES;                // [honda setRunning:YES]
        NSLog(@"%d", honda.running);        // [honda running]
    }
    return 0;
}

為了更改已生成的存取器的行為,你可以在@property指令后的小括號內指定其特性.下面的模塊將介紹這些可用的特性.

getter= 與 setter= 特性

如果你不喜歡@property這種默認的命名規約,你可以使用getter=與setter=特性來更改getter/setter的方法名.對于Boolean屬性,常用的getter方法名是(在其屬性名)添加is前綴.將Car.h的屬性聲明調整成下面這樣:

@property (getter=isRunning) BOOL running;

此時生成的存取器就是isRunning以及setRunning了.注意,公有屬性(名稱)仍然是running,這也是你在點語法中該使用的名稱:

Car *honda = [[Car alloc] init];
honda.running = YES;                // [honda setRunning:YES]
NSLog(@"%d", honda.running);        // [honda isRunning]
NSLog(@"%d", [honda running]);      // Error: method no longer exists

這是唯一一個需要參數的屬性特性(用于標識存取器方法) - 其他的都是boolean標識.

只讀特性

只讀特性讓你方便地將一個屬性變成只讀的.它會忽略setter方法,并會阻止通過點語法(給其)賦值,但是對getter沒有影響.作為例子,將Car接口修改如下,注意我們怎么使用逗號分隔來給屬性指定多個特性.

#import <Foundation/Foundation.h>

@interface Car : NSObject

@property (getter=isRunning, readonly) BOOL running;

- (void)startEngine;
- (void)stopEngine;

@end

現在,我們將通過startEngine和stopEngine方法在內部設置running屬性,而不是讓其他對象修改.對應的實現如下.

// Car.m
#import "Car.h"

@implementation Car

- (void)startEngine {
    _running = YES;
}
- (void)stopEngine {
    _running = NO;
}

@end

請記住,@property也為我們生成了一個實例變量,這是我們為什么在不聲明的情況下,也可以使用_running的原因(在此處,我們不能使用self.running因為它是只讀的).我們在main.m中添加如下代碼來測試新的Car類.

Car *honda = [[Car alloc] init];
[honda startEngine];
NSLog(@"Running: %d", honda.running);
honda.running = NO;                      // Error: read-only property

到此(來看),屬性是一個避免我們自己(根據樣板)寫getter/setter方法的快捷方式.(與readonly相比),這種情況不適用于其余的那些能顯著變更屬性行為的特性.它們只適用于存儲OC對象的屬性(并非C語言的基本類型).

非原子特性

原子性必須處理線程環境下的屬性行為.當有多個線程時,有可能會同時調用setter與getter方法.這也就意味著getter/setter會被其他操作打斷,也就可能會產生壞數據.

原子屬性通過鎖定一個基礎的(普通的)對象來阻止這種事情的發生,并保證get/set操作的完整性(數據完整性).然而,使用原子屬性只是線程安全的一個方面,并不意味著你的代碼是線程安全的,理解這一點很重要.

@property聲明的屬性默認是原子特性,這會產生一些管理成本(開銷).所以,如果處在一個非多線程的環境(或者你自己實現了線程安全),你肯定會用nonatomic特性來重寫(變更)這個行為,像這樣:

@property (nonatomic) NSString *model;

這有一個關于atomic特性小的,實用的警告.對于具有原子特性的屬性,它的存取器要么自己生成,要么自定義.只有non-atomic特性的屬性才允許混合-既可使用synthesized,又可以自定義.可以通過移除上面代碼中的nonatomic,并在Car.m增加一個自定義的getter來驗證這一點.

內存管理

在任何一種OOP(objected-oriented Pragram)語言中,對象都駐留在電腦內存中,在移動設備上- 這些(內存)是稀缺資源.內存管理的目標通過一種高效的創建,銷毀對象的方式來保證程序不會占用超出它們所需(內存)空間的額外空間.

很多編程語言通過垃圾回收機制來實現(內存管理),而OC使用另一種更高效的,被稱作object ownership的機制.當你與一個對象開始進行交互時,你便擁有了那個對象,也就意味著這種關系(擁有該對象)能保證在你使用期間,該對象一直存在.當你不在使用,你便打破(放棄)這種擁有關系,此時,如果該對象沒有其他擁有者,系統便會銷毀該對象,并釋放其內存.


Destroying an object with no owners
Destroying an object with no owners

隨著自動引用計數的出現,編譯器會幫自動給幫你管理這些擁有關系.意味著大多數情況下,你都不用操心內存管理如何工作.但是,你還是都明白屬性的strong,weak與copy特性,因為它們告訴編譯器這種對象關系屬于哪一種.

strong特性

strong特性下,無論給屬性分配什么樣的對象,都會創建一個擁有關系.這對所有的對象屬性來說都是明確地行為,這樣就保證了只要屬性被賦值,那值就會存儲(存在).

我們創建另一個Person類來看一下上訴的工作原理.該類的接口僅聲明了一個name屬性:

// Person.h
#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic) NSString *name;

@end

下面是實現.它使用@property默認生成的存取器.還重寫了NSObject的description方法,該方法是用來返回對象的字符串描述.

// Person.m
#import "Person.h"

@implementation Person

- (NSString *)description {
    return self.name;
}

@end

接下來,我們在Car類中添加一個Person屬性,修改Car.h如下.

// Car.h
#import <Foundation/Foundation.h>
#import "Person.h"

@interface Car : NSObject

@property (nonatomic) NSString *model;
@property (nonatomic, strong) Person *driver;

@end

然后,調整(又一次出現的)main.m的代碼:

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *john = [[Person alloc] init];
        john.name = @"John";
        
        Car *honda = [[Car alloc] init];
        honda.model = @"Honda Civic";
        honda.driver = john;
        
        NSLog(@"%@ is driving the %@", honda.driver, honda.model);
    }
    return 0;
}

因為driver是一個strong關系(強引用),所以handa對象擁有john.這樣就保證了john(對象)只要在honda對象需要,它就一直有效(存在).

weak特性

大多數情況,在設置對象屬性時,第一直覺都是給它strong特性.然而,strong引用(在一些情況下)會引起一個問題.舉個例子,我們需要一個對Car對象的引用來標識人正在開車.首先,我們在Person.h中增加一個car屬性.

// Person.h
#import <Foundation/Foundation.h>

@class Car;

@interface Person : NSObject

@property (nonatomic) NSString *name;
@property (nonatomic, strong) Car *car;

@end

@class Car這一行是對Car類的向前聲明.就像是在告訴編譯器,"相信我,Car類肯定存在,不用現在去加載它."我們必須用這種方式來替代我們常用的#import語句,因為Car也可以導入Person.h,這樣我們就會陷入一個無盡的導入循環.(編譯器可不喜歡無盡的循環.)

接下來,在main.m中,給honda.driver分配值之后加入下面一行代碼:

honda.driver = john;
john.car = honda;       // Add this line

現在,我們有一個honda擁有john的關系,也有一個jhon擁有honda的關系.這意味著兩個對象相互擁有(引用),導致內存管理系統無法銷毀它們,即使這兩個對象不再需要了.


A retain cycle between the Car and Person classes
A retain cycle between the Car and Person classes

這被稱作是retain cycle,一種內存泄露的形式,內存泄露are bad.幸運的是,很容易修復這個問題-只要告訴其中的一個屬性對另外一個對象保持一個弱引用即可.在Person.h中,將car的聲明調整如下:

@property (nonatomic, weak) Car *car;

weak特性創建一個對car的非擁有關系.這樣就允許john在避免retain cycle的情況下,仍存在一個對honda的引用.這意味,很可能存在(下述的)這種情況,honda已經被銷毀了,而john仍有一個對honda的引用.這不應該發生,weak特性會設置car為nil以避免指針懸掛.

A weak reference from the Person class to Car
A weak reference from the Person class to Car

一個常用weak特性的情況是在父子數據結構中.按照規約,父對象應該保持對子對象的強引用,子對象則保持對父對象的弱引用.弱引用也是代理設計模式中與生俱來部分.

(上述的)關鍵點在于,兩個對象永遠不要互相強引用.weak特性讓在不產生retain cycle的情況下保持一個循環引用的關系成為可能.

copy特性

相對于strong特性,copy特性可以作為替代(品).它不形成一個擁有關系,而是對任何分配給屬性的對象創建一個副本,然后對副本有擁有關系.只有遵從NSCopying 協議的對象才可以使用這個特性.

Properties that represent values (opposed to connections or relationships) are good candidates for copying. 比如,開發人員一般都會復制NSString屬性,而不是強引用它們:

// Car.h
@property (nonatomic, copy) NSString *model;

現在,無論你給model賦什么值,Car都將存儲一個嶄新的實例.如果你使用的是可變值,那么當(屬性)第一次被賦值時,它便是不變的了.* If you’re working with mutable values, this has the added perk of freezing the object at whatever value it had when it was assigned.*.可以通過下面的代碼證明:

// main.m
#import <Foundation/Foundation.h>
#import "Car.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Car *honda = [[Car alloc] init];
        NSMutableString *model = [NSMutableString stringWithString:@"Honda Civic"];
        honda.model = model;
        
        NSLog(@"%@", honda.model);
        [model setString:@"Nissa Versa"];
        NSLog(@"%@", honda.model);            // Still "Honda Civic"
    }
    return 0;
}

NSMutableString是NSString的一個子類,可以隨時編輯.如果model沒有對原始的實例創建一個副本,我們將會看到第二行NSLog()輸入的是改變后的字符串(Nissan Versa) .

其他特性

對于最新的OC程序(iOS5+),上述的@property特性是你應該需要(使用)的,但是這仍然有一些其他的(特性),你可能會在比較舊的庫或者文檔中遇到.

retain特性

retain特性是strong特性在手動引用計數中的版本,它有著同樣地效果:分配值時要求一個擁有關系.在自動引用計數環境下,你不應該再使用它.

unsafe_unretained 特性

有著unsafe_unretained特性的屬性在行為上與(有著)weak特性的屬性類似,但是如果被引用的對象被銷毀后,它們的值不會自動設為nil.你應該使用unsafe_unretained特性的唯一原因就是為了讓你的類與不支持weak屬性的代碼兼容.

assign特性

assign特性在給屬性賦值時不會執行任何一種內存管理要求.它是基本數據類型的默認行為,并且它曾是在iOS5之前實現弱引用的一種方式.與retain一樣,在最新的(當代)應用程序你不需要明確地使用它.

總結

這個模塊展示了@property所有的供選擇的特性,希望你對修改已生成的存取器方法感覺相對適應.請記住,這些特性的目的是幫助你關注什么樣的數據應該被記錄.(這些)特性被總結如下.

特性 描述
getter= Use a custom name for the getter method.
setter= Use a custom name for the setter method.
readonly Don’t synthesize a setter method.
nonatomic Don’t guarantee the integrity of accessors in a multi-threaded environment. This is more efficient than the default atomic behavior.
strong Create an owning relationship between the property and the assigned value. This is the default for object properties.
weak Create a non-owning relationship between the property and the assigned value. Use this to prevent retain cycles.
copy Create a copy of the assigned value instead of referencing the existing instance.

現在,我們已經搞定了屬性,我們將深入了解OC類中的另一半:方法.我們將探討(與方法相關的)一切,從命名規約背后的怪癖(原因)到動態方法調用.


寫于15年09月06號

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

推薦閱讀更多精彩內容

  • 1.項目經驗 2.基礎問題 3.指南認識 4.解決思路 ios開發三大塊: 1.Oc基礎 2.CocoaTouch...
    陽光的大男孩兒閱讀 5,012評論 0 13
  • 我素來不太喜歡觀賞夜景。 但最近不知道什么原因,晚上特別想出門,尤其今天這個無星無月的深秋之夜,特別想出去走走。 ...
    失業獵手閱讀 872評論 0 2
  • 這次的課程伴隨著優美的音樂,甜美的聲音的帶領,我化作一只蝴蝶進入了冥想,當我飛過河流,飛過高山,來到一個小村...
    JessieMM閱讀 386評論 0 0
  • 姑娘,你可以長得丑,畢竟樣貌這種事,是父母給的,我們沒有權利拒絕。你可以活得糙,可能你家庭情況不太好,又或者是你比...
    正在上天閱讀 1,366評論 4 4
  • 我想,也許,我能寫一本書!能寫一些東西是一種很好的感覺吧。剛剛跑步回來,流了很多汗。這個大概可以寫進我的書里吧,哦...
    搪塞君閱讀 316評論 0 1