歡迎光臨
每天分享高質量文章

iOS 修飾詞 詳解

作者:郡王丶千夜

連結:https://www.jianshu.com/p/ddecb19454ff

前言:

最近公司在擴招,做為公司僅有的唯一一個首席iOS開發工程師(手動滑稽),我不得不硬著頭皮上陣。

然後卻發現很多人的水平和年限嚴重不符,公司招的人都是3年+以上經驗的人,然而這些人中有一半連修飾詞的作用也說的模稜兩可,加上自己水平也不高,對以後的職業生涯產生了嚴重的危機感,遂決定以後每週希望能寫一篇有價值的文章,與君共勉,今天就說說iOS常見的幾個修飾詞。

一、readOnly,readWrite

readOnly:


根據字面意思,大家都很容易知道是“只讀”的意思,意味著只生成了getter方法,而沒有生成setter方法,如果這時候呼叫setter方法,會報一個Assignment to readonly property錯誤

PS:這裡順便說一下self.和_的區別

self.就是呼叫property自動生成的getter和setter方法,而_則是直接去呼叫實體變數(property會自動生成一個實體變數,如果你重寫了getter與setter方法,property自動生成的實體變數就無效了,需要手動去申明一個實體變數或者用@@synthesize).

回到正題,那麼這意味著我們就完全沒辦法去修改readOnly的屬性了嗎?不然,如果你嘗試一下setValue:forKey:,你就會發現竟然改變成功了,amazing,讓我們來看看程式碼:


@interface MyFirstClass : NSObject

@property (nonatomiccopyreadonlyNSString * string;

@end


#import "MyFirstClass.h"

@implementation MyFirstClass

- (instancetype) init{

    self = [super init];
    if (self) {
        _string = @"來改變我啊";
    }

    return self;
}

@end

- (void)viewDidLoad {

    [super viewDidLoad];

    MyFirstClass * class = [MyFirstClass new];
    NSLog(@"string === %@"class.string);
    [class setValue:@"改變了" forKey:NSStringFromSelector(@selector(string))];
    NSLog(@"string === %@"class.string);
}

Log如下:

2018-03-16 11:08:58.932303+0800 PropertyDesc[5681:445705string === 來改變我啊
2018-03-16 11:08:58.932454+0800 PropertyDesc[5681:445705string === 改變了

而如果這個時候在MyFirstClass裡加入

@implementation MyFirstClass

- (instancetype) init{
    self = [super init];
    if (self) {
        _string = @"來改變我啊";
    }

    return self;
}

+ (BOOL) accessInstanceVariablesDirectly{
    return NO;
}

@end

在執行,boom,系統會報以下錯誤

2018-03-16 11:19:21.619747+0800 PropertyDesc[6016:478446] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key string.'


沒有找到當前要賦值的屬性,那麼accessInstanceVariablesDirectly究竟是什麼呢,我們開啟蘋果的官方檔案找到Key-Value Coding Programming Guide

在這裡可以看到,如果這個方法設為YES,訪問器就會去尋找名稱為_, _is, , or is的成員變數,如果為NO,就會跳到第6步,第6步就會報[valueForUndefinedKey:]錯誤。

總結:

readOnly並不能完全保證只讀,我們可以透過KVC嘗試去修改其值。

PS:有興趣的小夥伴可以嘗試去修改別人的SDK,包括蘋果爸爸的

readWrite:

這個實在沒什麼可說的,預設的修飾詞就是readWrite,代表可讀可寫

二、nonatomic、atomic

我們先看一下官方檔案

atomic:

預設的屬性修飾詞,按官方檔案上說即使從不同的執行緒透過getter或setter方法去訪問屬性也能完全的獲取到或設定值,從這裡也可以看出,atomic並不是執行緒安全的,因為atomic只能保證透過setter和getter方法能獲取到一個完整的value,如果A執行緒在getter,B、C執行緒在setter,可能A獲取到的值是BC執行之後的值,也可能是BC執行緒執行完之前的值,也可能是B C執行緒任何一個執行緒執行完的值。

因此atomic的偽程式碼大概如下:

- (void)setString:(NSString *)string{
    @synchronized(self) {
        if (_string != string) {
            [_string release];//MRC
            _string = [string copy];
        }
    }
}

- (NSString *) string{
    @synchronized(self) {
        return _ string;
    }
}

nonatomic:

相對而言,透過nonatomic修飾的屬性,並沒有做鎖的操作,多執行緒同時進行setter/getter操作,並不能保證得到一個完整的value,所以相對atomic來說nonatomic修飾的屬性訪問速度更快,而且平時對執行緒安全我們更傾向於使用訊號量、NSLock和synchronized去控制執行緒安全,他們都能保證程式碼塊的原子性,所以幾乎所有的屬性都用nonatomic去修飾。

三、assign、weak與strong

assign:

一般來說,我們都用assign去修飾OC的基本資料型別,but why?

因為assign並不會使物件的取用計數加1,也就是說如果用assign去修飾一個物件,這個物件會立即被釋放,重要的是assgin在被釋放的時候是不會自動置為nil,還是保留物件的指標地址,會形成野指標,這個時候向其傳送訊息就會崩潰,簡單實驗的程式碼如下:

@interface MySecondClass : NSObject

@property (nonatomicassignNSMutableArray * array;

@end

- (void) testMethodTwo{
    MySecondClass * class = [[MySecondClass alloc] init];
    self.secondClass = class;
    self.secondClass.array = [NSMutableArray array];
    [self.secondClass.array addObject:@(0)];
}

在執行到最後一步的時候程式會崩潰報EXC_BAD_ACCESS的錯誤,如果打斷點的話會發現在執行到這步的時候array還是有地址的。

weak:

如果把上面的程式碼

@property (nonatomicassignNSMutableArray * array;換成
@property (nonatomicweakNSMutableArray * array;

這個時候程式並不會崩潰,如果你打個斷點的話會發現array被自動置為nil,而OC的特性使得像nil傳送訊息並不會崩潰,這就是weak和assgin最大的區別,此外weak必須用於修飾物件,這和他自動置為nil相關,如果強行使用weak修飾基本資料型別,編譯器會報一個大大的紅色錯誤!

strong:

strong的作用和assign和weak恰恰相反,strong也是屬性預設的修飾詞,代表著被修飾的物件取用計數+1

如果把上面的程式碼

@property (nonatomicassignNSMutableArray * array;換成
@property (nonatomicstrongNSMutableArray * array;

self.secondClass.array = [NSMutableArray array];

最後一句程式碼可以解釋為[NSMutableArray array]創造了一個物件A,此時A的取用計數為1,self.secondClass.array做為物件B,把A賦值給B的時候,A的取用計數加1,此時A的取用計數為2,B指向了A,然後編譯器會自動對A進行釋放操作(因為是區域性變數),A的取用計數-1。在擁有B的物件不釋放的時候,A的取用計數永遠不可能為0,除非你手動釋放或者把B指向一個新的物件,這樣A永遠不會被釋放,這就是所謂的強取用。

四、copy與mutableCopy


在說copy與mutableCopy之前我們先看看官方檔案對深複製與淺複製的闡釋,如下

深複製:

物件複製 – 重新申請一片記憶體保留這個物件,與原物件之間沒有半點關係。

淺複製:

指標複製 – 實際上相當於取用計數+1,被複製的和複製的取用同一個物件。

接下來我們分兩個方面做測試:

1、對非集合類物件的copy操作,以NSString為例

對immutableObject做copy操作

    NSString * string = [NSString stringWithFormat:@"1"];
    NSString * copyString = [string copy];
    NSString * mutableCopyString = [string mutableCopy];
    NSLog(@"string:%p", string);
    NSLog(@"copyString:%p", copyString);
    NSLog(@"mutableCopyString:%p", mutableCopyString);

Log如下:

2018-03-19 15:51:38.785253+0800 PropertyDesc[10283:759804] string:0xa000000000000311
2018-03-19 15:51:38.785435+0800 PropertyDesc[10283:759804] copyString:0xa000000000000311
2018-03-19 15:51:38.785518+0800 PropertyDesc[10283:759804] mutableCopyString:0x608000055150

可以看出對string和copyString的地址是一樣的,而mutableCopyString則不同。

對mutableObject做copy操作

    NSMutableString * string = [NSMutableString stringWithFormat:@"1"];
    NSString * copyString = [string copy];
    NSString * mutableCopyString = [string mutableCopy];
    NSLog(@"string:%p - %@", string, string);
    NSLog(@"copyString:%p - %@", copyString, copyString);
    NSLog(@"mutableCopString:%p - %@", mutableCopyString, mutableCopyString);
    [string appendString:@",2"];
    NSLog(@"copyString:%p - %@", copyString, copyString);
    NSLog(@"mutableCopString:%p - %@", mutableCopyString, mutableCopyString);

Log如下:

2018-03-19 15:51:38.785670+0800 PropertyDesc[10283:759804] string:0x60400005a940 - 1
2018-03-19 15:51:38.785784+0800 PropertyDesc[10283:759804] copyString:0xa000000000000311 - 1
2018-03-19 15:51:38.785834+0800 PropertyDesc[10283:759804] copyString:0xa000000000000311 - 1
2018-03-19 15:51:38.785891+0800 PropertyDesc[10283:759804] mutableCopyString:0x60400005a910 - 1
2018-03-19 15:51:38.786037+0800 PropertyDesc[10283:759804] mutableCopyString:0x60400005a910 - 1

可以看出對string與copyString、mutableCopyString三者的地址都是不同的。


即使改變了原string的value,copyString與mutableCopystring也沒有改變,這與下文對集合類物件得出的結論正好相反。

結論:

對 immutableObject進行 copy 操作是指標複製,mutableCopy 操作時物件複製。

對 mutable Object進行 copy 和 mutableCopy 都是物件複製。簡單的表格圖如下:


2、對集合類物件的copy操作

對immutableObject做copy操作

    NSArray * array = [NSArray arrayWithObject:@"1"];
    NSArray * copyArry = [array copy];
    NSMutableArray * mutableCopyArray = [array mutableCopy];

    NSLog(@"array:%p", array);
    NSLog(@"copyArry:%p", copyArry);
    NSLog(@"mutableCopyArray:%p", mutableCopyArray);

Log如下

2018-03-19 15:51:38.786167+0800 PropertyDesc[10283:759804] array:0x6000000094c0
2018-03-19 15:51:38.786278+0800 PropertyDesc[10283:759804] copyArray:0x6000000094c0
2018-03-19 15:51:38.786385+0800 PropertyDesc[10283:759804] mutableCopyArray:0x600000240030

可以看出array與copyArray的地址是一樣的,而mutableCopyArray則不同。

對mutableObject做copy操作

    NSMutableString * string = [NSMutableString stringWithFormat:@"1"];
    NSMutableArray * array = [NSMutableArray arrayWithObject:string];
    NSArray * copyArry = [array copy];
    NSMutableArray * mutableCopyArray = [array mutableCopy];

    NSLog(@"array:%p", array);
    NSLog(@"copyArry:%p", copyArry);
    NSLog(@"mutableCopyArray:%p", mutableCopyArray);
    [array addObject:@"2"];
    [string appendString:@"1"];

    NSLog(@"array:%p - %@", array, array);
    NSLog(@"copyArry:%p - %@", copyArry, copyArry);
    NSLog(@"mutableCopArray:%p - %@", mutableCopyArray, mutableCopyArray);

Log如下

2018-03-26 13:36:38.786499+0800 PropertyDesc[10283:759804] array:0x600000240150
2018-03-26 13:36:38.786600+0800 PropertyDesc[10283:759804] copyArry:0x6000000095f0
2018-03-26 13:36:38.786698+0800 PropertyDesc[10283:759804] mutableCopyArray:0x6000002400f0
2018-03-26 13:36:38.786865+0800 PropertyDesc[10283:759804] array:0x600000240150 - (
    11,
    2
)
2018-03-26 13:36:38.787018+0800 PropertyDesc[10283:759804] copyArry:0x6000000095f0 - (
    11
)
2018-03-26 13:36:38.787142+0800 PropertyDesc[10283:759804] mutableCopyArray:0x6000002400f0 - (
    11
)

What??不管是copy還是mutableCopy我們可以看到當我們修改了string的值後,陣列中的值都變了,但是在 [array addObject:@”2″];的時候兩個複製出來的陣列的物件並沒有變化?

這裡我們要提到一個新概念 不完全深複製(也有人說是單層深複製 )—— 即雖然新開闢了記憶體地址,但是存放在記憶體上的值(也就是陣列裡的元素仍然指向原陣列元素值,並沒有另外複製一份),所以說上文中的array和mutableCopArray類似於有一個或多個相交點的相交連結串列。

而且我們也可以看到不管是copy還是mutableCopy都是不完全深複製,三者的地址都是不一樣的。

結論:

對immutableObject做copy是指標複製,做mutableCopy是不完全深複製。

對mutableObject做copy或mutableCopy都是不完全深複製。

有趣的是,這與上文的結論有類似之處。簡單的表格圖如下:


並且如果打個斷點可以發現對任何物件做copy操作傳回的是一個不可變的物件,對任何物件做mutableCopy傳回的是一個可變的物件(有興趣的請自行驗證)。

五、是用copy還是strong?

透過上述對copy與strong的描述,copy和strong最終都會傳回一個取用計數為1的物件,所以單單從記憶體管理上說copy和strong並無任何區別,但是copy始終會傳回一個不可變物件,他的值是不會被修改的。

而strong不同,被strong修飾的物件,可能會被可變物件賦值,從而在外部導致不可預料的被更改的情況。

總而言之,是否使用copy或strong還是根據具體場景來定,這裡還有個效能最佳化的小技巧,如果copy的是可變的物件,會對它做一次完全深複製/不完全深複製,效能上是肯定不如strong直接取用計數+1來的快。

六、結尾

這次的修飾詞講解到此就告一段落了,然後想一下這周寫什麼樣的文章。



編號292,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

Web開發

更多推薦18個技術類微信公眾號

涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

贊(0)

分享創造快樂