細(xì)說@property(三)

@property修飾符

定義一個(gè)屬性時(shí),nonatomic、copy、strong、assign等被稱作是關(guān)鍵字,或者是修飾符。

修飾符種類

  • 原子性。原子性有nonatomic、atomic兩個(gè)值,如果不寫nonatomic,那么默認(rèn)是atomic的。如果屬性是atomic的,那么在訪問其getter和setter方法之前,會(huì)有一些判斷,大概是判斷是否可以訪問等,這里系統(tǒng)使用的是自旋鎖。由于使用atomic并不能絕對保證線程安全,且會(huì)耗費(fèi)一些性能,因此通常情況下都使用nonatomic。
  • 讀寫權(quán)限。讀寫權(quán)限有兩個(gè)取值,readwrite和readonly。聲明屬性時(shí),如果不指定讀寫權(quán)限,那么默認(rèn)是readwrite的。如果某個(gè)屬性不想讓其他人來寫,那么可以設(shè)置成readonly。
  • 內(nèi)存管理。內(nèi)存管理的取值有assign、strong、weak、copy、unsafe_unretained。
  • set、get方法名。如果不想使用自動(dòng)合成所生成的setter、getter方法,聲明屬性時(shí)甚至可以指定方法名。

默認(rèn)修飾符
聲明屬性時(shí),如果不顯示指定修飾符或者不指定修飾符時(shí),那么默認(rèn)修飾符分兩種

  • 基本數(shù)據(jù)類型:atomic, readwrite, assign
  • Objective-C對象類型:atomic, readwrite, strong

atomic是否是線程安全的
聲明屬性時(shí),通常使用nonatomic修飾符,原因就是因?yàn)閍tomic并不能保證絕對的線程安全。舉例來說,假設(shè)有一個(gè)線程A在不斷的讀取屬性name的值,同時(shí)有一個(gè)線程B修改了屬性name的值,那么即使屬性name是atomic,線程A讀到的仍舊是修改后的值,可見不是線程安全的。如果想要實(shí)現(xiàn)線程安全,需要手動(dòng)的實(shí)現(xiàn)鎖。示例:

stu.name = @"aaa";

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for(int i = 0 ; i < 1000; ++i){
        NSLog(@"stu.name = %@",stu.name);
    }
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    stu.name = @"bbb";
});

輸出:

2019-01-16 14:42:26.837215+0800 TestClock[15405:175815] stu.name = aaa
2019-01-16 14:42:26.837837+0800 TestClock[15405:175815] stu.name = bbb

如此證實(shí)了即使使用了atomic,也不能保證線程安全。

weak和assign區(qū)別
weak和strong是對應(yīng)的,一個(gè)是強(qiáng)引用,一個(gè)是弱引用。weak和assign的區(qū)別主要是體現(xiàn)在兩者修飾OC對象時(shí)的差異。上面也介紹過,assign通常用來修飾基本數(shù)據(jù)類型,如int、float、BOOL等,weak用來修飾OC對象,如UIButton、UIView等。

基本數(shù)據(jù)類型用weak來修飾:假設(shè)聲明一個(gè)int類型的屬性,但是用weak來修飾,那么Xcode會(huì)直接提示錯(cuò)誤,錯(cuò)誤信息如下

Property with 'weak' attribute must be of object type

也就是說,weak只能用來修飾對象,不能用來修飾基本數(shù)據(jù)類型,否則會(huì)發(fā)生編譯錯(cuò)誤。

對象使用assign來修飾:假設(shè)聲明一個(gè)UIButton類型的屬性,但是用assign來修飾,你會(huì)發(fā)現(xiàn)編譯沒有問題,運(yùn)行也沒有問題。我們聲明兩個(gè)UIButton類型屬性,一個(gè)用assign修飾,一個(gè)用weak修飾,創(chuàng)建button,打印比較發(fā)現(xiàn)一致沒問題。但是釋放button,將兩個(gè)屬性置為nil,再打印這兩個(gè)屬性:

UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100,100,100,100)];
[btn setTitle:@"Test" forState:UIControlStateNormal];
btn.backgroundColor = [UIColor lightGrayColor];
self.assignBtn = btn;
self.weakButton = btn;
btn = nil;
NSLog(@"self.weakBtn = %@",self.weakButton);
NSLog(@"self.assignBtn = %@",self.assignBtn);

運(yùn)行,執(zhí)行到self.assignBtn的時(shí)候崩潰了,崩潰信息是:

 EXC_BAD_ACCESS (code=EXC_I386_GPFLT)

那么weak和assign修飾對象時(shí)的差別體現(xiàn)出來了:weak修飾的對象,當(dāng)對象釋放之后,即引用計(jì)數(shù)為0時(shí),對象會(huì)置為nil;assign修飾的對象,當(dāng)對象釋放之后,即引用計(jì)數(shù)為0時(shí),對象會(huì)變?yōu)橐爸羔?,不知道指向哪,再向該對象發(fā)消息,非常容易崩潰。因此,當(dāng)屬性類型是對象時(shí),不要使用assign,會(huì)帶來一些風(fēng)險(xiǎn)。

堆和棧
上面說到,屬性用assign修飾,當(dāng)被釋放后,容易變?yōu)橐爸羔?,容易帶來崩潰問題,那么,為何基本數(shù)據(jù)類型可以用assign來修飾呢?這就涉及到堆和棧的問題。

相對來說,堆的空間大,通常是不連續(xù)的結(jié)構(gòu),使用鏈表結(jié)構(gòu)。使用堆中的空間,需要開發(fā)者自己去釋放。OC中的對象,如 UIButton 、UILabel ,[[UIButton alloc] init] 出來的,都是分配在堆空間上。

棧的空間小,約1M左右,是一段連續(xù)的結(jié)構(gòu)。棧中的空間,開發(fā)者不需要管,系統(tǒng)會(huì)幫忙處理。iOS開發(fā) 中 int、float等變量分配內(nèi)存時(shí)是在棧上。如果??臻g使用完,會(huì)發(fā)生棧溢出的錯(cuò)誤。

由于堆、棧結(jié)構(gòu)的差異,棧和堆分配空間時(shí)的尋址方式也是不一樣的。因?yàn)闂J沁B續(xù)的控件,所以棧在分配空間時(shí),會(huì)直接在未使用的空間中分配一段出來,供程序使用;如果剩下的空間不夠大,直接棧溢出;堆是不連續(xù)的,堆尋找合適空間時(shí),是順著鏈表結(jié)點(diǎn)來尋找,找到第一塊足夠大的空間時(shí),分配空間,返回。根據(jù)兩者的數(shù)據(jù)結(jié)構(gòu),可以推斷,堆空間上是存在碎片的。

那么assign修飾基本數(shù)據(jù)類型沒有野指針的問題?因?yàn)檫@些基本數(shù)據(jù)類型是分配在棧上,棧上空間的分配和回收都是系統(tǒng)來處理的,因此開發(fā)者無需關(guān)注,也就不會(huì)產(chǎn)生野指針的問題。

棧線程安全
進(jìn)程和線程的關(guān)系:
線程是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度和分派的基本單位。一個(gè)進(jìn)程可以擁有多個(gè)線程。線程本身是不配擁有系統(tǒng)資源的,只擁有很少的,運(yùn)行中必不可少的資源(如程序計(jì)數(shù)器、寄存器、棧)。但是線程可以與同屬于一個(gè)進(jìn)程的其他線程,共享進(jìn)程所擁有的資源。一個(gè)進(jìn)程中所有的線程共享該進(jìn)程的地址空間,但是每個(gè)線程有自己獨(dú)立的棧,iOS系統(tǒng)中,每個(gè)線程棧的大小是1M。而堆則不同。堆是進(jìn)程所獨(dú)有的,通常一個(gè)進(jìn)程有一個(gè)堆,這個(gè)堆為本進(jìn)程中的所有線程所共享。

通過上面的介紹,我們可以清楚的了解到:棧是線程安全的。
堆是多個(gè)線程所共有的空間,操作系統(tǒng)在對進(jìn)程進(jìn)行初始化的時(shí)候,會(huì)對堆進(jìn)行分配;
棧是每個(gè)線程所獨(dú)有的,保存線程的運(yùn)行狀態(tài)和局部變量。棧在線程開始的時(shí)化,每個(gè)線程的棧是互相獨(dú)立的,因此棧是線程安全的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,650評(píng)論 1 32
  • @property介紹 相信做過iOS開發(fā)的同學(xué)都使用過@property,@property翻譯過來是屬性。在定...
    子瘋zp閱讀 897評(píng)論 0 6
  • 面試題參考1 : 面試題[http://www.cocoachina.com/ios/20150803/12872...
    江河_ios閱讀 1,816評(píng)論 0 4
  • 原文鏈接 @property介紹 相信做過iOS開發(fā)的同學(xué)都使用過@property,@property翻譯過來是...
    acBool閱讀 1,173評(píng)論 1 10
  • 1.設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式,并簡要敘述?設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的邏輯去處理某一種類型...
    龍飝閱讀 2,302評(píng)論 0 12

友情鏈接更多精彩內(nèi)容