相信很多參加過面試的人員很多都會被問到:weak與assign的區別,copy與strong的區別。如果你僅僅說一點點copy一般對NSString,weak對於控件的修飾,assign對於基本類型,那么面試官可以會對你深入問,block用過嗎?修飾block用什么,又為什么用copy,這樣一層層問下去,可能場面就很尷尬了,即使你進去,可能薪資也不能達到你所期望的。這篇我准備花幾天完成,希望對大家有所幫助,閱讀這篇問題大約需要20-30分鍾……
一.@property
1.講解
Objective-C的屬性(property)是通過用@property定義的公有或者私有的方法。屬性(property)提供了一種安全、便捷的方式來與這些屬性(attribute)交互,而不需要手動編寫一系列的訪問方法,如果需要的話可以自定義getter和setter方法來覆蓋編譯器自動生成的相關方法。
盡量多的使用屬性(property)而不是實例變量(attribute)因為屬性(property)相比實例變量有很多的好處:
(1)自動合成getter和setter方法。當聲明一個屬性(property)的時候編譯器默認情況下會自動生成相關的getter和setter方法更好的聲明一組方法。
(2)因為訪問方法的命名約定,可以很清晰的看出getter和setter的用處。
2.用法
@interface Person : NSObject { NSString *_name; NSUInteger _age; } - (void)setName:(NSString*)name; - (NSString*)name; - (void)setAge:(NSUInteger)age; - (NSUInteger)age; @end @implementation Person - (void)setName:(NSString*)name { _name = [name copy]; } - (NSString*)name { return _name; } - (void)setAge:(NSUInteger)age { _age = age; } - (NSUInteger)age { return _age; } @end
上述代碼就是手動創建變量的getter
和setter
的實現,getter
和setter
本質就是符合一定命名規范的實例方法。
具體使用
int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = [[Person alloc] init]; //函數調用name的setter [p setName:@"Jiaming Chen"]; //函數調用age的setter [p setAge:22]; //函數調用name和age的getter,輸出 Jiaming Chen 22 NSLog(@"%@ %ld", [p name], [p age]); } return 0; } //另一種寫法 int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = [[Person alloc] init]; //使用點語法訪問name的setter p.name = @"Jiaming Chen"; //使用點語法訪問age的setter p.age = 22; //使用點語法訪問name和age的getter,輸出 Jiaming Chen 22 NSLog(@"%@ %ld", p.name, p.age); } return 0; }
Objective-C允許使用點語法來訪問getter
和setter
。
合成使用,使用@property方法
@interface Person : NSObject @property (nonatomic, copy) NSString* name; @property (nonatomic, assign) NSUInteger age; @end @implementation Person //編譯器會幫我們自動生成_name和_age這兩個實例變量,下面代碼就可以正常使用這兩個變量了 @synthesize name = _name; @synthesize age = _age; - (void)setName:(NSString*)name { //必須使用_name來賦值,使用self.name來設置值時編譯器會自動轉為調用該函數,會導致無限遞歸 //使用_name則是直接訪問底層的存儲屬性,不會調用該方法來賦值 //這里使用copy是為了防止NSMutableString多態 _name = [name copy]; } - (NSString*)name { //必須使用_name來訪問屬性值,使用self.name來訪問值時編譯器會自動轉為調用該函數,會造成無限遞歸 return _name; } @end
二.@property后面有哪些修飾符
1.線程安全的-------atomic、nonatomic
atomic:原子性:Object-C使用的是一種線程保護技術,從基本上講,是防止在未完成的時候被另一個線程更改,造成數據錯誤。而這種機制是耗費系統資源,所以在iPhone這種小型設備上,如果沒有使用多線程之間的通訊編程,那么nonatomic是一個非常好的選擇。
nonatomic:非原子性: 禁止多線程,變量保護,提高性能。
原子性:默認
這個屬性是為了保證程序在多線程下,編譯器會自動生成自旋鎖代碼,避免該變量的讀寫不同步問題,提供多線程線程,多線程只有一個線程對它訪問。
attention
(1)atomic原子性指的是一個操作不可以被CPU中途叫停,然后再調度。即不能被中斷,要么執行完,要么不執行。
(2)atomic是自旋鎖,當上一個線程沒有執行完畢的時候(被鎖住),下一個線程會一直等待,不會進入睡眠,當上一個線程執行完畢后,下一個線程會立即執行。它區別於互斥鎖,互斥鎖在等待的時候,會進入睡眠狀態,當上一個線程執行完畢之后,會被喚醒,然后再執行。
(3)atomic需要消耗大量的資源,執行效率低
但是atomic並不保證線程安全,因為只會保持set內部安全,在外面並不能保證安全!例如在數組array set方法中atomic原子操作,但是外面使用array addobject並不會保證線程安全!
二、訪問權限
readwrite 默認 擁有getter/setter方法,可讀可寫
readonly 只讀屬性,只會生成getter方法,不會生成setter方法
三、內存管理
1.assign 默認
適用於基本數據類型:NSInteger,CGFloat和C數據類型int,float等,另外還有id類型
2.strong對應MRC中的retain
強引用,只有OC對象才能使用該屬性,它使對象的引用計數加1
3.weak
弱引用,只是單純引用某個對象,但是並未擁有該對象
即一個對象被持有無數個弱引用,只要沒有強引用指向它,那么它就會被清楚釋放。
下面通過一些拓展一下:面試重點!!!
(1)strong與retain
相同點:strong和retain都是針對對象類型進行內存管理。如果去修飾基本數據類型,Xcode會直接報錯,當給對象類型使用此修飾符時,setter方法先將舊的對象屬性release掉,再將新的對象賦值給屬性並對該對象進行一次retain操作,兩者都會增加對象的引用計數。
不同點:strong一般用於ARC,retain一般用於MRC環境。
(2)assgin與weak
相同點:assgin和weak不會牽扯到內存管理,不會增加引用計數
不同點:assign可修飾基本數據類型,也可修飾OC對象,但如果修飾對象類型指向的是一個強指針,當它指向的這個指針釋放后,他仍指向這塊內存,必須手動給置為nil,否則就會產生野指針,如果還通過此指針操作那塊內存,便會導致EXC_BAD_ACCESS錯誤,調用了已經釋放的內存空間;而weak只能修飾OC對象,且相比assign比較安全,如果指向的對象消失了,那么他會自動置為nil,不會產生野指針。
(3)strong與copy(重點重點)--這個可能比較難懂,多看兩遍,可能有點乏味,不過很重要!!!
1.copy(拓展)----深拷貝和淺拷貝的區別
淺拷貝:指針拷貝,不產生新的對象,源對象的引用計數器加1;只是多了一個指向這塊內存的指針,共用一塊內存。
深拷貝:對象拷貝,會產生新的對象,源對象的引用計數器不變;兩塊內存是完全不同的,也就是兩個對象指針分別指向不同的內存,互不干涉。
判斷是淺拷貝和深拷貝就看一下兩個變量的內存地址是否一樣,一樣就是淺拷貝,不一樣就是深拷貝,也可以改變一個變量的其中一個屬性值看兩者的值都會發生變化;
系統原生的對象深淺拷貝區別:
NSObject類提供了copy和mutableCopy方法,通過這兩個方法即可拷貝已有對象的副本,主要的系統原生對象有:NSString和NSMutableString、NSArray和NSMutableArray、NSDictionary和NSMutableDictionary、NSSet和NSMutableSet。 NSValue和NSNumber 只遵守的NSCopying協議。
NSString-------copy/mutableCopy
NSString *string = @"copyTest"; NSString *copyString = [string copy]; NSString *mutableCopyString = [string mutableCopy]; NSMutableString *copyMutableString = [string copy]; NSMutableString *mutableCopyMutableString = [string mutableCopy]; NSLog(@"\n string = %p \n copystring = %p \n mutablecopystring = %p " "\n copyMutableString = %p \n mutableCopyMutableString = %p \n", string, copyString, mutableCopyString, copyMutableString, mutableCopyMutableString);
打印結果:
2018-05-06 10:31:51.209346+0800 copy[829:67521] string = 0x100001040 copystring = 0x100001040 mutablecopystring = 0x10058c8e0 copyMutableString = 0x100001040 mutableCopyMutableString = 0x10058cde0 Program ended with exit code: 0
小結論:在字符串是直接賦值的,是否生成新對象是和=右邊相關的,如果=右邊的是mutableCopy才會產生新的對象
NSMutableString----copy/mutableCopy
NSMutableString *string = [NSMutableString stringWithString:@"學習研究"]; NSString *copyString = [string copy]; NSString *mutableCopyString = [string mutableCopy]; NSMutableString *copyMutableString = [string copy]; NSMutableString *mutableCopyMutableString = [string mutableCopy]; NSLog(@"\n string = %p \n copystring = %p \n mutablecopystring = %p " "\n copyMutableString = %p \n mutableCopyMutableString = %p \n", string, copyString, mutableCopyString, copyMutableString, mutableCopyMutableString);
打印結果:
2018-05-06 10:48:44.755398+0800 copy[929:77373] string = 0x100504600 copystring = 0x100554e10 mutablecopystring = 0x100555880 copyMutableString = 0x100506e40 mutableCopyMutableString = 0x1005558f0 Program ended with exit code: 0
小結論:只要=右邊從創建到賦值,至少包含一個MSMutable便會重新創建生成一個對象。
其他對象NSArray、NSMutableArray 、NSDictionary、NSMutableDictionary、NSSet、NSMutableSet一樣適用。
剛剛疏解完copy,下面看一下面試最喜歡問的strong和copy修飾的區別:
下面看一組例子:
以NSString為例說明下,首先定義以下屬性。
1 @property (nonatomic, strong) NSString *strongString; 2 @property (nonatomic, copy) NSString *copyedString; 3 @property (nonatomic, strong) NSMutableString *strongMutableString; 4 @property (nonatomic, copy) NSMutableString *copyedMutableString;
2.1 當外部賦給對應屬性一個不可變(非mutable)的字符串 NSString
1 - (void)testPropertyCopyOrStrong 2 { 3 NSString *string = [NSString stringWithFormat:@"abc"]; 4 self.strongString = string; 5 self.strongMutableString = string; 6 self.copyedString = string; 7 self.copyedMutableString = string; 8 string = [string stringByReplacingOccurrencesOfString:@"c" withString:@"233"]; 9 10 NSLog(@"\n origin string: %p, %p %@ %@", string, &string, string, NSStringFromClass([string class])); 11 NSLog(@"\n strong string: %p, %p %@ %@", _strongString, &_strongString, _strongS tring, NSStringFromClass([_strongString class])); 12 NSLog(@"\n strongMutable string: %p, %p %@ %@", _strongMutableString, &_strongMutableSt ring, _strongMutableString, NSStringFromClass([_strongMutableString class])); 13 NSLog(@"\n copy string: %p, %p %@ %@", _copyedString, &_copyedString, _copyedS tring, NSStringFromClass([_copyedString class])); 14 NSLog(@"\n copyMutable string: %p, %p %@ %@", _copyedMutableString, &_copyedMutableSt ring, _copyedMutableString, NSStringFromClass([_copyedMutableString class])); 15 16 }
打印結果:
1 origin string: 0x103a74098, 0x7fff5c18ca88 ab233 __NSCFString 2 strong string: 0xa000000006362613, 0x7f84c9f056d8 abc NSTaggedPointerString 3 strongMutable string: 0xa000000006362613, 0x7f84c9f056e8 abc NSTaggedPointerString 4 copy string: 0xa000000006362613, 0x7f84c9f056e0 abc NSTaggedPointerString 5 copyMutable string: 0xa000000006362613, 0x7f84c9f056f0 abc NSTaggedPointerString
可能大家不是很看懂這個例子:我們換一個簡單的操作:
首先在類延展中聲明兩個屬性變量:
1 @property (nonatomic, strong)NSString * stringStrong; //strong修飾的字符串對象 2 @property (nonatomic, copy)NSString * stringCopy; //copy修飾的字符串對象
接着創建兩個不可變字符串(NSString)
1 //新創建兩個NSString對象 2 NSString * strong1 = @"I am Strong!"; 3 NSString * copy1 = @"I am Copy!";
將這兩個屬性進行賦值
1 //初始化兩個字符串 2 self.stringStrong = strong1; 3 self.stringCopy = copy1;
分別打印四個變量的地址
1 StrongOrCopy[5046:421886] strong1 = 0x10a0b3078 2 StrongOrCopy[5046:421886] stringStrong = 0x10a0b3078
//這是兩個字符串
3 StrongOrCopy[5046:421886] copy1 = 0x10a0b3098
4 StrongOrCopy[5046:421886] stringCopy = 0x10a0b3098
結果發現:可以看出,無論是strong修飾的字符串還是copy修飾的字符串,都進行了淺拷貝(僅僅是多了個指向該內存的指針,地址不會發揮變化)
如果創建兩個不可變字符串對象(NSMutableString)呢
1 //新創建兩個NSMutableString對象 2 NSMutableString * mutableStrong = [NSMutableString stringWithString:@"StrongMutable"]; 3 NSMutableString * mutableCopy = [NSMutableString stringWithString:@"CopyMutable"];
分別對屬性再次賦值
self.stringStrong = mutableStrong;
self.stringCopy = mutableCopy;
打印結果:
1 1 StrongOrCopy[5046:421886] mutableStrong = 0x7fccba425d60 2 2 StrongOrCopy[5046:421886] stringStrong = 0x7fccba425d60 3 3 StrongOrCopy[5046:421886] mutableCopy = 0x7fccba40d7c0 4 4 StrongOrCopy[5046:421886] stringCopy = 0x7fccba4149e0
結果發現:這時就發現了,用strong修飾的字符串依舊進行了淺Copy,而由copy修飾的字符串進行了深Copy,所以mutableStrong與stringStrong指向了同一塊內存,而mutableCopy和stringCopy指向的是完全兩塊不同的內存。
看了一些實例,有什么用呢,看如下:
1 //新創建兩個NSString對象 2 NSString * strong1 = @"I am Strong!"; 3 NSString * copy1 = @"I am Copy!"; 4 5 //初始化兩個字符串 6 self.stringStrong = strong1; 7 self.stringCopy = copy1; 8 9 //兩個NSString進行操作 10 [strong1 stringByAppendingString:@"11111"]; 11 [copy1 stringByAppendingString:@"22222"];
結果如下:
1 StrongOrCopy[5146:439360] strong1 = I am Strong! 2 StrongOrCopy[5146:439360] stringStrong = I am Strong! 3 StrongOrCopy[5146:439360] copy1 = I am Copy! 4 StrongOrCopy[5146:439360] stringCopy = I am Copy!
分別對在字符串后面進行拼接,當然這個拼接對原字符串沒有任何的影響,因為不可變自字符串調用的方法都是有返回值的,原來的值是不會發生變化的.打印如下,對結果沒有任何的影響:(不可變的字符串)
然后是對可變字符串進行操作:
1 //新創建兩個NSMutableString對象 2 NSMutableString * mutableStrong = [NSMutableString stringWithString:@"StrongMutable"]; 3 NSMutableString * mutableCopy = [NSMutableString stringWithString:@"CopyMutable"]; 4 5 //初始化兩個字符串 6 self.stringStrong = mutableStrong; 7 self.stringCopy = mutableCopy; 8 9 //兩個MutableString進行操作 10 [mutableStrong appendString:@"Strong!"]; 11 [mutableCopy appendString:@"Copy!"];
打印結果如下:
1 StrongOrCopy[5245:446189] stringStrong = StrongMutableStrong! 2 StrongOrCopy[5245:446189] mutableStrong = StrongMutableStrong! 3 StrongOrCopy[5245:446189] stringCopy = CopyMutable 4 StrongOrCopy[5245:446189] mutableCopy = CopyMutableCopy!
對mutableStrong進行的操作,由於用strong修飾的stringStrong沒有進行深Copy,導致共用了一塊內存,當mutableStrong對內存進行了操作的時候,實際上對stringStrong也進行了操作; 相反,用copy修飾的stringCopy進行了深Copy,也就是說stringCopy與mutableCopy用了兩塊完全不同的內存,所以不管mutableCopy進行了怎么樣的變化,原來的stringCopy都不會發生變化.這就在日常中避免了出現一些不可預計的錯誤。
總結:在不可變對象之間進行轉換,strong與copy作用是一樣的,但是如果在不可變與可變之間進行操作,那么樓主比較推薦copy,這也就是為什么很多地方用copy,而不是strong修飾NSString,NSArray等存在可變不可變之分的類對象了,避免出現意外的數據操作.
>>>>>>>拓展
修飾block為什么要用copy修飾?
關於block的用法,前幾篇博客有,請關注:下面直接說原因:
(1)block內部沒有調用外部局部變量時存放在全局區(ARC和MRC下均是)
(2)block使用了外部局部變量,這種情況也正是我們平時所常用的方式。MRC:Block的內存地址顯示在棧區,棧區的特點就是創建的對象隨時可能被銷毀,一旦被銷毀后續再次調用空對象就可能會造成程序崩潰,在對block進行copy后,block存放在堆區.所以在使用Block屬性時使用copy修飾。但是ARC中的Block都會在堆上的,系統會默認對Block進行copy操作
(3)用copy,strong修飾block在ARC和MRC都是可以的,都是在堆區
下面繼續講解
4.指定方法名稱: setter= getter=
今天到此結束(如果太多,可能大家一時接受不了),下一步,我可能繼續講解block造成的循環引用,@property與ivar的區別等,看@property引出的那些問題