身邊一同事,我印象在過去三個月,有兩次因為使用“copy”修飾UIKit控件的屬性,導致程序崩潰。他還一本正經的說我以前一直使用copy。
好了,到這里我們就不得不說說什么時候使用copy。我的印象中,只有兩處使用了copy,即修飾NSString類型與block,其他的都是使用strong關鍵字修飾。
說到這里,我們先來說說NSString類型,我在創建的NSString類型的屬性中,也曾也使用過strong修飾的,因為我幾乎沒有使用過NSMutableString類型轉換,我不用去考慮是用copy還是strong更好,當然為了代碼的健壯使用copy更好,以下我就作具體分析緣由。
在MRC中,使用retain,copy進行拷貝,會使retainCount結果+1.但是如果是深拷貝,便會改變指針,retainCount = 1;下面我直接在ARC下調試,我只關心內存指針,不關心retainCount。
NSString *str0 = @"a"; NSLog(@"str0內存地址: %p",str0); //0x107fcb088 在64位系統上得到的內存地址較短,說明存放在常量區(代碼,常量,全局,堆,棧) NSString *string0 = [str0 copy]; NSLog(@"string0的內存地址: %p",string0); //0x107fcb088 淺拷貝 NSString *str = [NSString stringWithFormat:@"%@",@"a"]; NSLog(@"str內存地址: %p",str); //0xa000000000000611 (棧區) NSString *string = [str copy]; NSLog(@"string的內存地址: %p",string); //0xa000000000000611 淺拷貝 NSMutableString *str1 = [NSMutableString stringWithFormat:@"a"]; NSLog(@"str1的內存地址:%p",str1); //0x60000007c5c0 NSMutableString *string1 = [str1 copy]; NSLog(@"string1的內存地址: %p",string1); //0xa000000000000611 內存地址發生了改變,進行了深拷貝,而且跟上面的地址一樣
總結:對於NSString類型只是引用了內存,淺拷貝;NSMutableString作為NSString的子類進行copy才是深拷貝。
剛剛上面的深拷貝,出現跟淺拷貝一樣的地址,不由得我們需要多做兩個測試,如下:
NSMutableString *strEx = [str mutableCopy]; NSLog(@"strEx的內存地址:%p",strEx); //0x608000263040 深拷貝 NSMutableString *strExCopy = [str mutableCopy]; NSLog(@"strExCopy的內存地址:%p",strExCopy); //0x60000026a440 str兩次mutableCopy的地址不一樣 NSMutableString *stringEx = [strEx copy]; NSLog(@"stringEx的內存地址: %p",stringEx); //0xa000000000000611 與上面地址一樣 NSMutableString *stringEx1 = [strExCopy copy]; NSLog(@"stringEx1的內存地址: %p",stringEx1); //這個也是0xa000000000000611,說明兩次copy都指向同一個地址 NSMutableString *strExEx = [strEx mutableCopy]; NSLog(@"strExEx的內存地址:%p",strExEx); //0x600000073d00 深拷貝
結論:可以看成,str1所謂的“深拷貝”,其實不是“深拷貝”,它還是拷貝了之前的地址。這樣,我得出,當進行mutable創建,其實是系統首先創建了一份NSString的地址,然后再深拷貝,相當於[NSMutableString stringWithFormat:@"a"];來自於 [str mutableCopy];。
在字符串類型NSString中使用strong還是copy,到底哪個更好,蘋果自己的API中告訴了我,copy更好,那么我們就進一步進行驗證。首先我們創建兩個字符串對象分別為strong與copy修飾的,然后再進行賦值比較,如下。
@property (strong,nonatomic)NSString *testStr; @property (copy,nonatomic)NSString *testStrCopy; - (void)viewDidLoad { [super viewDidLoad]; NSString *testStr = [NSString stringWithFormat:@"%@",@"a"]; NSLog(@"testStr內存地址: %p",testStr); //0xa000000000000611 self.testStr = testStr; NSLog(@"self.testStr內存地址: %p",self.testStr);//0xa000000000000611 淺拷貝 self.testStrCopy = testStr; NSLog(@"self.testStrCopy內存地址: %p",self.testStrCopy);//0xa000000000000611 淺拷貝 NSMutableString *testStr1 = [NSMutableString stringWithFormat:@"%@",@"a"]; NSLog(@"testStr1內存地址: %p",testStr); //0x608000078a00 與上面的str1 0x60000007c5c0也不一樣,MutableCopy是重新創建了地址 self.testStr = testStr1; NSLog(@"self.testStr內存地址: %p",self.testStr);//0x608000078a00 strong指向同一個地址 self.testStrCopy = testStr1; NSLog(@"self.testStrCopy內存地址: %p",self.testStrCopy);//0xa000000000000611 雖然地址變了,但還是指向原來的地址 }
總結:對於NSString類型,使用copy修飾,不會改變它原有的類型,strong會指向引用的對象,有可能改變其類型狀態,所以copy能增強NSString的健壯性----------------用下面一張圖表示

block特性
另一個使用copy的地方就是修飾block:
@property (nonatomic,copy)void(^demoBolck)(); @property (nonatomic,strong)void(^demoBolck1)(); int b=8; void (^demoBolck)() = ^{ NSLog(@"demoBolck"); }; NSLog(@"demoBolck %@",demoBolck); //<__NSGlobalBlock__: 0x1085af0e0> 無論ARC還是MRC下,因不訪問外部局部(包括無外部變量或者只有全局變量),NSGlobalBlock表示在全局區 void (^demoBolck4)() = ^{ NSLog(@"demoBolck4 %d",b); }; NSLog(@"demoBolck4 %@",demoBolck4); //<__NSGlobalBlock__: 0x10150b120> 全局區 __block int a = 6; //block內部引用a,並修改其值,需要用block修飾,不然可以不用。不過是引用行屬性,需要 void (^demoBolck2)() = ^{ NSLog(@"demoBolck2 %d",a++); }; demoBolck2(); NSLog(@"demoBolck2 %@,%d",demoBolck2,a); //<__NSMallocBlock__: 0x600000056c50> ARC下堆區 <__NSStackBlock__: 0x7fff5d0ada28>MRC下在棧區 NSLog(@"demoBolck2.Copy %@",[demoBolck2 copy]); //<__NSMallocBlock__: 0x600000056c50>copy操作不管MRC或者ARC都在堆區,只是在MRC下進行copy會改變地址 self.demoBolck = demoBolck2; NSLog(@"self.demoBolck %@",self.demoBolck); self.demoBolck1 = demoBolck2; self.demoBolck1(); //demoBolck2 7 能執行無問題 NSLog(@"self.demoBolck1 %@",self.demoBolck1); //<__NSMallocBlock__: 0x600000056c50> strong修飾並沒有問題
__weak 本身是可以避免循環引用的問題的,但是其會導致外部對象釋放了之后,block 內部也訪問不到這個對象的問題,我們可以通過在 block 內部聲明一個 __strong 的變量來指向 weakObj,使外部對象既能在 block 內部保持住,又能避免循環引用的問題。
__block 本身無法避免循環引用的問題(__block NSObj *a = (NSObj*)b;),但是我們可以通過在 block 內部手動把 blockObj 賦值為 nil 的方式來避免循環引用的問題。另外一點就是 __block 修飾的變量在 block 內外都是唯一的,要注意這個特性可能帶來的隱患。但是__block有一點:這只是限制在ARC環境下。在非arc下,__block是可以避免引用循環的
總結:在 Objective-C 語言中,一共有 3 種類型的 block:
- _NSConcreteGlobalBlock 全局的靜態 block,不會訪問外部局部變量。
- _NSConcreteStackBlock 保存在棧中的 block,當函數返回時會被銷毀。
- _NSConcreteMallocBlock 保存在堆中的 block,當引用計數為 0 時會被銷毀。
關於block的知識,參考http://blog.devtang.com/2013/07/28/a-look-inside-blocks/
