IOS開發基礎——屬性關鍵字(copy strong weak等)

參考博客:

小記

在ios的開發中,咱們最經常使用到的就是那些修飾屬性的關鍵字。ios

  • 內存管理有關的關鍵字:weak,assign,strong,retain,copy
  • 線程安全有關的的關鍵字:nonatomic,atomic
  • 訪問權限有關的的關鍵字:readonly,readwrite(只讀,可讀寫)
  • 修飾變量的關鍵字:const,static,extern 這些都是咱們在平常的開發中經常使用到的一些關鍵字。關於他們的詳細用法以及做用,在下面進行詳細的分析講解。

內存管理有關的的關鍵字:(weak,assign,strong,retain,copy)

關鍵字weak

一樣常常用於修飾OC對象類型的數據,修飾的對象在釋放後,指針地址會自動被置爲nil,這是一種弱引用。安全

注意:在ARC環境下,爲避免循環引用,每每會把delegate屬性用weak修飾;在MRC下使用assign修飾。當一個對象再也不有strong類型的指針指向它的時候,它就會被釋放,即便還有weak型指針指向它,那麼這些weak型指針也將被清除。bash

關鍵字assign

常常用於非指針變量,用於基礎數據類型 (例如NSInteger)和C數據類型(int, float, double, char, 等),另外還有id類型。用於對基本數據類型進行復制操做,不更改引用計數。也能夠用來修飾對象,可是,被assign修飾的對象在釋放後,指針的地址仍是存在的,也就是說指針並無被置爲nil,成爲野指針。app

注意:之因此能夠修飾基本數據類型,由於基本數據類型通常分配在棧上,棧的內存會由系統自動處理,不會形成野指針函數

以及:在MRC下常見的id delegate每每是用assign方式的屬性而不是retain方式的屬性,爲了防止delegation兩端產生沒必要要的循環引用。例如:對象A經過retain獲取了對象B的全部權,這個對象B的delegate又是A, 若是這個delegate是retain方式的,兩個都是強引用,互相持有,那基本上就沒有機會釋放這兩個對象了。測試

weak 和 assign 的區別:

  • 修飾的對象:weak修飾oc對象類型的數據,assign用來修飾是非指針變量。
  • 引用計數:weak 和 assign 都不會增長引用計數。
  • 釋放:weak 修飾的對象釋放後,指針地址自動設置爲 nil,assign修飾的對象釋放後指針地址依然存在,成爲野指針。
  • 修飾delegate 在MRC使用assign,在ARC使用weak。

關鍵字stronng:

用於修飾一些OC對象類型的數據如:(NSNumber,NSString,NSArray、NSDate、NSDictionary、模型類等),它被一個強指針引用着,是一個強引用。在ARC的環境下等同於retain,這一點區別於weak。它是一咱們一般所說的指針拷貝(淺拷貝),內存地址保持不變,只是生成了一個新的指針,新指針和引用對象的指針指向同一個內存地址,沒有生成新的對象,只是多了一個指向該對象的指針。 注意:因爲使用的是一個內存地址,當該內存地址存儲的內容發生變動的時候,會致使屬性也跟着變動:ui

關鍵字copy:

一樣用於修飾OC對象類型的數據,同時在MRC即(MMR)手動內存管理時期,用來修飾block,由於block須要從棧區copy到堆區,在如今的ARC時代,系統自動給咱們作了這個操做,所一如今使用strong或者copy來修飾block都是能夠的。copy和strong相同點在於都是屬於強引用,都會是屬性的計數加一,可是copy和strong不一樣點在於,它所修飾的屬性當引用一個屬性值時,是內存拷貝(深拷貝),就是在引用是,會生成一個新的內存地址和指針地址來,和引用對象徹底沒有相同點,所以它不會由於引用屬性的變動而改變。atom

copy與strong的區別(深拷貝 淺拷貝):

淺拷貝:指針拷貝,內存地址不變呢,指針地址不相同。spa

深拷貝:內存拷貝,內存地址不一樣,指針地址也不相同。線程

聲明兩個copy屬性,兩個strong屬性,分別爲可變和不可變類型:

@property(nonatomic,strong)NSString * Strstrong;
@property(nonatomic,copy)NSString * Strcopy;
@property(nonatomic,copy)NSMutableString * MutableStrcopy;
@property(nonatomic,strong)NSMutableString * MutableStrstrong;`
複製代碼

對屬性進行賦值:

```
NSString * OriginalStr = @"我已經開始測試了";
//對 不可變對象賦值 不管是 strong 仍是 copy 都是原地址不變,內存地址都爲(0x10c6d75c0),生成一個新指針指向對象(淺拷貝)
self.Strcopy = OriginalStr;
self.Strstrong = OriginalStr;
self.MutableStrcopy = OriginalStr;
self.MutableStrstrong = OriginalStr;
NSLog(@"rangle=>%@\n normal:copy=>%@=====strong=>%@\nMutable:copy=>%@=====strong=>%@",OriginalStr,_Strcopy,_Strstrong,_MutableStrcopy,_MutableStrstrong);
NSLog(@"rangle=>%p\n normal:copy=>%p=====strong=>%p\nMutable:copy=>%p=====strong=>%p",OriginalStr,_Strcopy,_Strstrong,_MutableStrcopy,_MutableStrstrong);
NSLog(@"rangle=>%p\n normal:copy=>%p=====strong=>%p\nMutable:copy=>%p=====strong=>%p",&OriginalStr,&_Strcopy,&_Strstrong,&_MutableStrcopy,&_MutableStrstrong);
```
複製代碼

輸出結果:

```
//內容值:  rangle=>我已經開始測試了
//        normal:copy=>我已經開始測試了=====strong=>我已經開始測試了
//        Mutable:copy=>我已經開始測試了=====strong=>我已經開始測試了
//內存地址:rangle=>0x10c6d75c0
//        normal:copy=>0x10c6d75c0=====strong=>0x10c6d75c0
//        Mutable:copy=>0x10c6d75c0=====strong=>0x10c6d75c0
//指針地址:rangle=>0x7ffee360d368
//        normal:copy=>0x7fc238907490=====strong=>0x7fc238907498
//        Mutable:copy=>0x7fc2389074a0=====strong=>0x7fc2389074a8
```
複製代碼

由上面能夠看出,strong修飾的對象,在引用一個對象的時候,內存地址都是同樣的,只有指針地址不一樣,copy修飾的對象也是如此。爲何呢?不是說copy修飾的對象是生成一個新的內存地址嘛?這裏爲何內存地址仍是原來的呢?

由於,對不可變對象賦值,不管是strong仍是copy,都是同樣的,原內存地址不變,0x10c6d75c0,生成了新的指針地址。

而後咱們試試用 可變對象 對屬性進行賦值:

```
NSMutableString * OriginalMutableStr = [NSMutableString stringWithFormat:@"我已經開始測試了"];
self.Strcopy = OriginalMutableStr;
self.Strstrong = OriginalMutableStr;
self.MutableStrcopy = OriginalMutableStr;
self.MutableStrstrong = OriginalMutableStr;
```
複製代碼

這一次的輸出結果:

```
//內容值:  rangle=>我已經開始測試了
//        normal:copy=>我已經開始測試了=====strong=>我已經開始測試了
//        Mutable:copy=>我已經開始測試了=====strong=>我已經開始測試了
//內存地址:rangle=>0x6000032972a0
//        normal:copy=>0x600003297720=====strong=>0x6000032972a0
//        Mutable:copy=>0x6000032974e0=====strong=>0x6000032972a0
//指針地址:rangle=>0x7ffee360d368
//        normal:copy=>0x7fc238907490=====strong=>0x7fc238907498
//        Mutable:copy=>0x7fc2389074a0=====strong=>0x7fc2389074a8
```
複製代碼

在上面的結果能夠看出,strong修飾的屬性內存地址依然沒有改變,可是copy修飾的屬性內存值產生了變化,再也不是0x6000032972a0。由此得出結論:

對可變對象賦值 strong 是原地址不變(0x600003f173f0),引用計數+1(淺拷貝)。 copy是生成一個新的地址和對象(0x600003297720和0x6000032974e0),生成一個新指針指向新的內存地址(深拷貝)

咱們來測試一下此時修改一下OriginalMutableStr的值,看看結果:

```
[OriginalMutableStr appendFormat:@"改變了"];
```
複製代碼

再打印一下:

```
//內容值:  rangle=>我已經開始測試了改變了
//        normal:copy=>我已經開始測試了=====strong=>我已經開始測試了改變了
//        Mutable:copy=>我已經開始測試了=====strong=>我已經開始測試了改變了
//內存地址:rangle=>0x6000032972a0
//        normal:copy=>0x600003297720=====strong=>0x6000032972a0
//        Mutable:copy=>0x6000032974e0=====strong=>0x6000032972a0
//指針地址:rangle=>0x7ffee360d368
//        normal:copy=>0x7fc238907490=====strong=>0x7fc238907498
//        Mutable:copy=>0x7fc2389074a0=====strong=>0x7fc2389074a8
```
複製代碼

看到 strong 修飾的屬性,跟着進行了改變 當改變了原有值的時候,因爲OriginalMutableStr是可變類型,是在原有內存地址上進行修改,不管是指針地址和內存地址都沒有b改變,只是當前內存地址所存放的數據進行改變。因爲 strong 修飾的屬性雖然指針地址不一樣,可是指針是指向原內存地址的,因此會跟着 OriginalMutableStr 的改變而改變。

不一樣於strong,copy修飾的類型不只指針地址不一樣,並且指向的內存地址也和OriginalMutableStr 不同,因此不會跟着 OriginalMutableStr 的改變而改變。

注意

  • 使用self.Strcopy 和 _Strcopy 來賦值也是兩個不同的結果,由於後者沒有調用 set 方法,而 copy 和 strong 之因此會產生差異就是由於在 set 方法中,copy修飾的屬性: 調用了 _Strcopy = [Strcopy copy] 方法。
  • copy也分爲 copy 和 mutableCopy,在對容器對象和非容器對象操做的時候也是有區別,下面來分析下:

多種copy模式:copy 和 mutableCopy 對 容器對象 進行操做

在對容器對象(NSArray)進行copy操做時,分爲多種:

  • copy:僅僅進行了指針拷貝
  • mutableCopy:進行內容拷貝這裏的單層指的是完成了NSArray對象的深copy,而未對其容器內對象進行處理使用(NSArray對象的內存地址不一樣,可是內部元素的內存地址不變)
[arr mutableCopy];
複製代碼
  • 雙層深拷貝:這裏的雙層指的是完成了NSArray對象和NSArray容器內對象的深copy(爲何不是徹底,是由於沒法處理NSArray中還有一個NSArray這種狀況)使用:
[[NSArray alloc] initWithArray:arr copyItems:YES]
複製代碼
  • 徹底深拷貝:完美的解決NSArray嵌套NSArray這種情形,可使用歸檔、解檔的方式可使用:
[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:testArr]];
複製代碼

線程安全有關的的關鍵字:(nonatomic,atomic)

關鍵字nonatomic

nonatomic非原子操做:(不加鎖,線程執行快,可是多個線程訪問同一個屬性時,結果沒法預料)

關鍵字atomic

atomic原子操做:加鎖,保證 getter 和 setter 存取方法的線程安全(僅對setter和getter方法加鎖)。 由於線程枷鎖的緣由,在別的線程來讀寫這個屬性以前,會先執行完當前的操做。

例如:

線程A調用了某一屬性的setter方法,在方法還未完成的狀況下,線程B調用了該屬性的getter方法,那麼只有在執行完A線程的setter方法之後才執行B線程的getter操做。當幾個線程同時調用同一屬性的 setter 和 getter方法時,會獲得一個合法的值,可是get的值不可控(由於線程執行的順序不肯定)。

注意:

atomic只針對屬性的 getter/setter 方法進行加鎖,因此安全只是針對getter/setter方法來講,並非整個線程安全,由於一個屬性並不僅有 setter/getter 方法,例:(若是一個線程正在getter 或者 setter時,有另一個線程同時對該屬性進行release操做,若是release先完成,會形成crash)

修飾變量的關鍵字:(const,static,extern)

常量 const

常量修飾符,表示不可變,能夠用來修飾右邊的基本變量和指針變量(放在誰的前面修飾誰(基本數據變量p,指針變量*p))。

經常使用寫法例如:

const 類型 * 變量名a:能夠改變指針的指向,不能改變指針指向的內容。 const放 號的前面約束參數,表示*a只讀。只能修改地址a,不能經過a修改訪問的內存空間

int x = 12;
int new_x = 21;
const int *px = &x; 
px = &new_x; // 改變指針px的指向,使其指向變量y
複製代碼

類型 * const 變量名:能夠改變指針指向的內容,不能改變指針的指向。 const放後面約束參數,表示a只讀,不能修改a的地址,只能修改a訪問的值,不能修改參數的地址

int y = 12;
int new_y = 21;
int * const py = &y;
(*py) = new_y; // 改變px指向的變量x的值
複製代碼

常量(const)和宏定義(define)的區別:

使用宏和常量所佔用的內存差異不大,宏定義的是常量,常量都放在常量區,只會生成一分內存 缺點:

  • 編譯時刻:宏是預編譯(編譯以前處理),const是編譯階段。致使使用宏定義過多的話,隨着工程愈來愈大,編譯速度會愈來愈慢
  • 宏不作檢查,不會報編譯錯誤,只是替換,const會編譯檢查,會報編譯錯誤。

優勢:

  • 宏能定義一些函數,方法。 const不能。

常量 static

定義所修飾的對象只能在當前文件訪問,不能經過extern來引用

默認狀況下的全局變量 做用域是整個程序(能夠經過extern來引用) 被static修飾後僅限於當前文件來引用 其餘文件不能經過extern來引用

  • 修飾局部變量:

有時但願函數中的局部變量的值在函數調用結束後不消失而繼續保留原值,即其佔用的存儲單元不釋放,在下一次再調用的時候該變量已經有值。這時就應該指定該局部變量爲靜態變量,用關鍵字 static 進行聲明。

  • 延長局部變量的生命週期(沒有改變變量的做用域,只在當前做用域有用),程序結束纔會銷燬。

注意:當在對象A裏這麼寫static int i = 10; 當A銷燬掉以後 這個i還存在 當我再次alloc init一個A的對象以後 在新對象裏 依然能夠拿到i = 90 除非殺死程序 再次進入才能獲得i = 0。

  • 局部變量只會生成一分內存,只會初始化一次。把它分配在靜態存儲區,該變量在整個程序執行期間不釋放,其所分配的空間始終存在
- (void)test{
    // static修飾局部變量1
    static int age = 0;
    age++;
    NSLog(@"%d",age);
}
-(void)test2{
    // static修飾局部變量2
    static int age = 0;
    age++;
    NSLog(@"%d",age);
}

[self test];
[self test2];
[self test];
[self test2];
[self test];
[self test2];
複製代碼

輸出結果:

2019-02-13 16:51:00.147356+0800 ProjectExecises[9872:2237314] 1
2019-02-13 16:51:00.916502+0800 ProjectExecises[9872:2237314] 1
2019-02-13 16:51:01.898249+0800 ProjectExecises[9872:2237314] 2
2019-02-13 16:51:02.514333+0800 ProjectExecises[9872:2237314] 2
2019-02-13 16:51:03.097697+0800 ProjectExecises[9872:2237314] 3
2019-02-13 16:51:05.832851+0800 ProjectExecises[9872:2237314] 3
複製代碼

因而可知 變量生命週期延長了,做用域沒有變

  • 修飾全局變量:
  • 只能在本文件中訪問,修改全局變量的做用域,生命週期不會改
  • 避免重複定義全局變量(單例模式)

常量 extern

只是用來獲取全局變量(包括全局靜態變量)的值,不能用於定義變量。先在當前文件查找有沒有全局變量,沒有找到,纔會去其餘文件查找(優先級)。

#import "JMProxy.h"
@implementation JMProxy
int ageJMProxy = 20;
@end

@implementation TableViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    extern int ageJMProxy;
    NSLog(@"%d",ageJMProxy);
}
@end
複製代碼

static與const聯合使用

聲明一個靜態的全局只讀常量。開發中聲明的全局變量,有些不但願外界改動,只容許讀取。

iOS中staic和const經常使用使用場景,是用來代替宏,把一個常用的字符串常量,定義成靜態全局只讀變量.

// 開發中常常拿到key修改值,所以用const修飾key,表示key只讀,不容許修改。
static  NSString * const key = @"name";

// 若是 const修飾 *key1,表示*key1只讀,key1仍是能改變。

static  NSString const *key1 = @"name";
複製代碼

extern與const聯合使用

在多個文件中常用的同一個字符串常量,可使用extern與const組合

extern與const組合:只須要定義一份全局變量,多個文件共享

@interface JMProxy : NSProxy
extern NSString * const nameKey = @"name";
@end

#import "JMProxy.h"
@implementation JMProxy
NSString * const nameKey = @"name";
@end
複製代碼
相關文章
相關標籤/搜索