@property括號內屬性講解


一、前言
     一個object的屬性允許其他object監督和改變他的狀態。但是在一個設計良好的面向對象程序中,直接訪問一個object的內部狀態是不可能的。相反,存取器(getter setter)方法是一個抽象相互作用object的底層數據。
 
通過訪問器方法與屬性進行交互
@property指令的目標是通過自動的創建這些存取器方法使創建和配置屬性變得更加簡單。它允許你在語義級別上指定公有屬性的行為。而且它比較關注你的詳細實現。
     這個模型調查各種各樣的屬性,這些屬性可以讓你修改getter和setter行為。其中的一些屬性確定是如何處理內存的,所以這個模型也服務於在Objective-C中對內存管理的實際的介紹。
 二、@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;   //Xcode 4.4以上可選
@end

  

   編譯器會為running屬性創建一個getter和setter方法。默認的命名習慣是用屬性自己作為getter,加上前綴set作為setter方法,並且在前面加下划線作為實例變量,就像下面這樣:
-(BOOL)running
{
    return _running;
}
-(void)setRunning:(BOOL)running
{
    _running = running;
}
當用
@property直接生成屬性,你可以直接調用這些方法,就像這些方法就是包含在該類的interface和實現文件中。你也可以在.m中重寫他們,但是這樣會使得@synthesize指令強制。然而,你應該很少需要傳統的存取器盡管@property屬性供這樣做在抽象的級別。
屬性訪問可以是用類實例后加.訪問。所以看下面的代碼:
   
Car *honda = [[Car alloc] init];
    honda.running = YES;
    NSLog(@"%d",honda.running);  
 當執行honda.running時,也就是調用setRunning:方法。
 當給他分配值並且運行時,就是調用getter方法。
為了改變這種存取器方式,我們可以在@properry后加括號指定,下面就是介紹可用的屬性。
1、The getter= and setter= Attributes     
        如果我們不喜歡@property默認的命名方式,我們可以通過The getter= and setter= Attributes 來改變存取器方法名。最常用的就是對Boolean屬性使用這個。可以把getter把慣例的改成is,例如:
 @property(getter=isRunning) BOOL running;
     現在生成存儲器叫做isRunning和setRunning.而標注公共性質還是叫做running。下面是我們應該怎么用逗號使用它。
        
 Car *honda = [[Car alloc] init];
    honda.running = YES;
    NSLog(@"%d",honda.running);
    NSLog(@"%d",[honda isRunning]);
    這些是唯一的屬性,他們都是boolean標記。
2、readonly屬性
     readonly屬性是一個很方便的方法讓你的屬性只讀。這樣會省略setter方法,並且防止作惡通過.調用,但是getter不受影響。例如,我們修改running的屬性為readonly,注:我們可以制定多個屬性,然后用“,”分開:
#import <Foundation/Foundation.h>
@interface Car : NSObject
 
@property(getter=isRunning,readonly) BOOL running;
 
-(void)startEngine;
-(void)stopEngine;
@end

  

不是讓其他object改變running的屬性,我們將會設置兩個方法去訪問。這兩個方法的而實現如下:
-(void)startEngine
{
    _running = YES;
}
-(void)stopEngine
{
    _running = NO;
}
要記得,@property還為我們生成了一個實例變量,這就是我們為什么可以訪問_running在沒有聲明的條件下(我們也可以直接使用self.running因為這個屬性是只讀的)。讓我們來運行下列代碼測試:
   Car *honda = [[Car alloc] init];
//    honda.running = YES;
    NSLog(@"%d",honda.running);
    honda.running = NO;
我們會發現最后一句出錯,因為它是只讀屬性,無法修改。
到這個地方,我們可以很方便快捷地讓我們避免書寫樣板的getter和setter方法。而對remaining屬性,這不是一個好的情況。他們也只適用於屬性存儲OC對象(相當於C數據類型)。
3、nonatomic屬性  
      原子性(Atomicity)的作用是屬性在線程的環境中怎么行為。當你不僅僅有一個線程, 那么getter和setter可能會在同一時間去調用,這就意味着getter/setter可能會被另一個方法打擾,很有可能造成數據錯誤。
                 原子的屬性會封鎖object,防止這種情況發生,確保get或者set操作的操作對象是完整的。沒有被損壞。然而,這僅僅是一個線程安全的方面,我們必須要理解這一點。使用原子性並不能確保我們的代碼就是線程安全的。
                  屬性用@property聲明默認是原子性的,這會導致一些花銷,因此,如果你不是處在多線程環境(或者你正實現你自己的線程安全),你會用notatomic屬性重寫這個行為,就像下邊:
   @property (nonatomic) NSString *model;  //設置非原子性
     當使用原子性屬性時,會有一個小的而且比較實際的警告。針對原子屬性的屬性訪問器必須要么是生成的,要么是用戶自定義的,只有傳統非原子性的屬性會讓你混合搭配合成存儲方法。你可以看一下,如果移去nonatomic從上邊的代碼,然后再Car.m添加傳統的getter。
         會產生一個警告:Setter和getter必須被合成,或者用戶自定義,或者屬性必須是nonatomic
4、內存管理
      在面向對象語言中,objects存在於計算機內存中,尤其在移動設備中,內存這是一個很缺乏的資源。內存管理系統以一個高效的管理方式創建和破壞object,目標就是確保程序不占用超出它所需要的空間。
          許多語言都是通過垃圾回收去完成的,但是OC用的是一個更加高效替代品,就是Object ownership(目標擁有者)。當你開始與對象交互時,你會告訴對象自己,這就意味着它確保只要你再使用對象,它就會存在.當你不再使用的時候,你放棄所有權,如果對象沒有其他的所有者,操作系統會銷毀這個對象,然后釋放底層內存資源。
          隨着自動引用計數(ARC)出現,編譯器自動管理你所有的對象,大多數情況下,意味着你從來不必擔心內存管理系統是怎么工作的,但是,你必須明白strong,weak和copy屬性,因為他們告訴編譯器對象應該有什么關系。
5、strong屬性
          無論對象被指定為什么屬性,強壯的屬性可以創建擁有關系,這對所有對象屬性來說是一種內隱行為,它默認是安全的,因為只要它被指定為strong屬性,它就會確保對對象的值存在。
     通過下面的例子,讓我們看一下它是怎么工作的。
@interface Person : NSObject
 
@property(nonatomic)NSString *name;
@end
它的實現如下,它@property產生的使用默認的存儲方法,它也重寫了NSObject的描述方法,返回一個代表該對象的字符串。
#import "Person.h"

@implementation Person

-(NSString *)description
{
    return self.name;
}
@end
 然后,讓我們添加Person屬性給車,改變Car.h如下:
#import <Foundation/Foundation.h>
#import "Person.h"

@interface Car : NSObject


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

@end
然后考慮下邊的代碼: 
    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);
     只要driver是一個strong關聯,honda對象就會持有john,這確保只要honda需要它,它就會有效。
6、weak屬性
大多數情況下,強屬性可以直觀知道你想要什么對象屬性,強引用會暴漏一個問題,例如,我們需要從driver引用他正在開的Car對象,     首先,我們需要給Person添加一個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,那樣我們會陷入無盡的導入循環(編譯器不喜歡無窮的循環)。
然后,添加下面的代碼,在honda、driver分配后:
Person *john = [[Person alloc] init];
    john.name = @"John";
    Car *honda = [[Car alloc] init];
    honda.model = @"Honda Civic";
    honda.driver = john;
    john.car = honda;  //添加這行
    NSLog(@"%@ is driving the %@",honda.driver,honda.model);
這樣我們現在有一個現象,就是john擁有honda,honda擁有john。這就意味着他們相互擁有,所以盡管他們不再有用,內存管理系統也不能夠釋放他們。
 
這叫做 retain cycle(保持循環),是一種內存泄露的形式,內存泄露是很不好的。幸運的是,要想解決這個辦法很簡單,只需要告訴其中一個屬性維持一個weak屬性引用另一個對象。在Person.h中,改變car的聲明:
@property(nonatomic,weak)Car *car;
這種weak(弱)屬性會給car創建一個非擁有關系。它允許john有一個honda的引用,同時避免了保持循環。但是,還有一個可能性就是honda會被銷毀,而這個時候john正在引用honda,如果這種情況發生,weak屬性會很方便地設置car為nil,去避免懸掛指針。
 
一個在Person中弱引用car
一個公用的使用weak屬性例子就是父親-孩子數據結構,根據約定,父對象應該保持一個強引用對子對象,子對象應該存儲一個弱引用父對象。弱引用在代理設計模式中是內在的,也就是自帶的。
     重點是兩個對象不恩給你都是強引用彼此,weak屬性讓保持一個不通過創建保持循環循環的關系變成可能。

7、copy屬性

      它是strong的替代品,不是保持擁有一個存在的對象,而是創建一個引用,無論你指定什么屬性,都會持有這個擁有。只有符合NSCopying protocol的對象才能使用這個屬性。
     代表值的屬性(相對於鏈接或關系)是一個使用copy的不錯選擇。例如,開發者通常復制字符串屬性,而不是強引用它們:
//Car.h
@property (nonatomic,copy) NSString *model;
         現在Car將會存儲一個全新的實例,不管我們指定的model值,如果你處理可變的值,它會有一個好處,就是凍結對象無論什么時候有值當它被指定時。演示如下:
         Car *honda = [[Car alloc] init];
    honda.model = @"Honda Civic";
  
    NSMutableString *model = [NSMutableString stringWithString:@"Honda Civic"];
    honda.model = model;
   
    NSLog(@"%@",honda.model);
    [model setString:@"Nissa Versa"];  
    NSLog(@"%@",honda.model); //輸出結果還是Honda Civic
NSMutableString 是NSString的一個子類,它可以在自身的基礎上修改它的值。如果model屬性不被聲明為copy屬性,我們將會看到在最后一個NSLog將會輸出Nissa Versa
8、其他屬性
上面的的這些@property性質我們需要使用在如今的OC應用中,但是下面的一些屬性我們可能會在老得類庫或者文檔中偶爾遇到。
  •  retain屬性
 retain屬性是手動釋放保留版本的strong,它有准確一樣的影響:為指定的values聲稱所有權.ARC環境中不能使用。
  • unsafe_unretained屬性
     它類似與weak,但是如果引用對象銷毀,不自動設置nil。使用該屬性的唯一原因就是:去讓你的類兼容不支持weak屬性的代碼。
  • assign屬性
     當給一個這個屬性指定一個新的value,assign屬性不執行任何類型的內存管理調用。這個一個原始數據類型的默認行為,它的應用是在iOS5之前去實現弱引用,就像retain,現在的application你不應該明確的使用。
總結:
     這個模塊講解了所有的@property選擇屬性,下面是總結屬性:
    1. getter=  讓getter方法使用自定義的名字
    2. setter = 讓setter方法使用自定義名字
    3. readonly 不合成setter方法
    4. nonatomic 不保證在多線程環境下存取訪問器的完整性,這比原子性更加高效
    5. strong 在屬性和指定value之間創建一個擁有關系,這是默認的對象屬性
    6. weak 在屬性和制定value之間創建一個不擁有關系,使用這個防止循環引用
    7. copy 創建一個指定值的副本,而不是引用存在的對象實例。
三、參考
 
四、更新 2018-04-08更新
1.這里需要注意:我們所謂的所有屬性關鍵字都是在設置方法時才觸發,也就是說現在有一個name的屬性,如果使用_name,那么屬性關鍵字不會生效,因為這些關鍵詞都是在setter中才能起作用。舉個例子:
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,copy)NSArray *constArr;
@property (nonatomic,strong)NSMutableArray *mArr;
@property (nonatomic,strong)NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    _mArr = [[NSMutableArray alloc] initWithObjects:@"1",@"2", nil];
    _constArr = _mArr;
    NSLog(@"%@",self.constArr);
    [_mArr addObject:@"3"];
    NSLog(@"%@",self.constArr);
}
@end

這個例子中,雖然我們對constArr使用的是copy,但是兩次打印的結果是不一樣的。如果我們這樣寫:

#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,copy)NSArray *constArr;
@property (nonatomic,strong)NSMutableArray *mArr;
@property (nonatomic,strong)NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    _mArr = [[NSMutableArray alloc] initWithObjects:@"1",@"2", nil];
    self.constArr = _mArr;
    NSLog(@"%@",self.constArr);
    [_mArr addObject:@"3"];
    NSLog(@"%@",self.constArr);
}
@end

輸出打印的結果才會是一樣的,因為前者只是對實例變量的指針賦值,而后者才是利用了屬性特性,對屬性進行賦值。這也正解釋了我們平時所理解的:

對於strong,為這種屬性設置新值得時候,設置方法會先保留新值,並釋放舊值,然后將新值設置上去。

對於copy,設置方法並不保留新值,而是將其拷貝。

上面提到的設置方法就是我們說的setter,也就是后者使用self.constArr調用的方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM