屬性修飾符是什么辆憔?有什么作用?
屬性修飾符涤躲,顧名思義就是對(duì)屬性進(jìn)行修飾的符號(hào)棺耍。不同修飾符修飾的屬性會(huì)表現(xiàn)出不一樣的屬性特性,其表現(xiàn)形式并不是那么顯而易見种樱。
那它的作用是什么呢蒙袍?從上面的定義上面我們就能發(fā)現(xiàn),被不同修飾符修飾的屬性會(huì)有不同的特性嫩挤,根據(jù)這些特性害幅,我們能更好的優(yōu)化自己的代碼邏輯,更簡(jiǎn)單的實(shí)現(xiàn)效果俐镐。
接下來(lái)我們就逐個(gè)詳細(xì)分析一下。
屬性修飾符有哪些哺哼?
這里我把常用的屬性修飾符分為三大類
- 線程安全類
nonatomic/atomic
- 讀寫權(quán)限類
readwrite/readonly
- 內(nèi)存管理類
assign/copy/strong/weak
還有其他的例如
-
class
類屬性 -
setter=/getter=
自定義setter/getter方法名 -
retain
MRC下內(nèi)存管理
現(xiàn)在我們來(lái)詳細(xì)說(shuō)一下常用的三大類屬性修飾符佩抹。
1、線程安全類 nonatomic/atomic
- nonatomic 非原子屬性取董。它的特點(diǎn)是多線程并發(fā)訪問性能高棍苹,但是訪問不安全;與之相對(duì)的就是atomic茵汰,特點(diǎn)就是安全但是是以耗費(fèi)系統(tǒng)資源為代價(jià)枢里,所以一般在工程開發(fā)中用nonatomic的時(shí)候比較多。
- 系統(tǒng)默認(rèn)的是atomic蹂午,為setter方法加鎖栏豺,而nonatomic 不為setter方法加鎖。
- 如1所述豆胸,使用nonatomic要注意多線程間通信的線程安全奥洼。
根據(jù)上述描述,項(xiàng)目中我們基本上只使用nonatomic
晚胡,因?yàn)樗脑L問性能高灵奖,對(duì)于多線程處理的時(shí)候嚼沿,也建議自己去實(shí)現(xiàn)加鎖的處理。一是因?yàn)?code>atomic的加鎖占用系統(tǒng)資源量大瓷患,二是因?yàn)?code>atomic只是在setter/getter方法中進(jìn)行了加鎖處理骡尽,在其他操作中是沒有的,這里可能會(huì)出現(xiàn)遺漏擅编。
2攀细、讀寫權(quán)限類 readwrite/readonly
這個(gè)就是訪問權(quán)限的控制,決定該屬性是否可讀和可寫沙咏,默認(rèn)是readwrite
辨图,所以我們定義屬性的時(shí)候,一般不需要這個(gè)修飾肢藐。只有只讀屬性才需要加上readonly
的修飾故河。
readonly
來(lái)控制讀寫權(quán)限的方式就是只生成getter方法,不生成setter方法吆豹。
3鱼的、內(nèi)存管理類 assign/copy/strong/weak
這一類的的修飾符是該文檔中最重要的部分了,這也是我們項(xiàng)目編碼中痘煤,經(jīng)常會(huì)混淆凑阶,理解錯(cuò)誤的地方。不知道這些修飾符都要什么時(shí)候時(shí)候使用衷快。
assign
該修飾符是給那些不需要進(jìn)行內(nèi)存管理的變量使用的宙橱,包括所有的基礎(chǔ)類型變量,例如int float double long BOOL NSInteger
等蘸拔。還有的是由棧區(qū)师郑,全局區(qū),常量區(qū)管理的變量也可以使用assign來(lái)修飾调窍,因?yàn)樗麄兊膬?nèi)存已經(jīng)被系統(tǒng)自動(dòng)管理了宝冕,無(wú)需手動(dòng)額外管理。copy
該修飾符修飾的屬性在賦值的時(shí)候會(huì)多一個(gè)執(zhí)行copy方法的操作邓萨,對(duì)于copy的方法實(shí)現(xiàn)完全按照對(duì)應(yīng)的對(duì)象的copy實(shí)現(xiàn)地梨,如NSString的copy是不變的。
對(duì)于那些屬性賦值需要進(jìn)行淺拷貝的缔恳,也就是當(dāng)前類中處理該屬性的值不影響傳入變量的情況下宝剖,可以使用該類。但是要謹(jǐn)慎使用歉甚,很容易會(huì)產(chǎn)生問題诈闺,例如可變類型不能使用copy來(lái)進(jìn)行修飾,否則賦值的變量都會(huì)變成不可變類型的屬性铃芦,與原先定義的屬性類型不一致雅镊。在編寫代碼過程中可能會(huì)調(diào)用不屬與該類的方法襟雷。strong
該修飾符相當(dāng)于是MRC中的retain
修飾符。它是一種強(qiáng)引用仁烹,被它修飾的屬性都會(huì)進(jìn)行內(nèi)存管理耸弄,也就是引用計(jì)數(shù)的管理。weak
弱引用卓缰,和strong
相對(duì)應(yīng)计呈。常用于解決循環(huán)引用問題。被修飾的屬性在其他持有者都被釋放之后征唬,該屬性會(huì)自動(dòng)指向nil
捌显,也就是說(shuō),作用和assign一樣总寒,但是更加安全扶歪!
總結(jié):可以說(shuō)屬性修飾符就是對(duì)setter/getter方法的一些功能性擴(kuò)展,使其定義簡(jiǎn)單的同時(shí)能夠滿足更多的功能要求摄闸。
棧區(qū)善镰、常量區(qū)、全局區(qū)用
assign
堆區(qū)持有用strong
年枕、copy
炫欺,不持有用weak
什么叫不持有?
就是一個(gè)對(duì)象別其他變量持有了熏兄,那該變量就指向該對(duì)象品洛,如果別的變量都不持有該對(duì)象了,那該變量也不需要指向該對(duì)象了摩桶,這就叫不持有桥状。
注意點(diǎn)
接下來(lái)我們說(shuō)明一下在實(shí)際使用過程中比較常見的一些誤區(qū)和知識(shí)點(diǎn)。
-
NSString
為什么要用copy
典格?為什么不能用strong
岛宦?
如果是簡(jiǎn)單的就是NSString
類型的字符串台丛,它們都是存放在常量區(qū)里面 的耍缴,就不需要進(jìn)行內(nèi)存管理,assign,copy,strong
都是可以修飾的挽霉,它們實(shí)際上都不會(huì)去改變?nèi)魏螙|西防嗡。
@property (nonatomic, assign) NSString *str1;
@property (nonatomic, copy) NSString *str2;
@property (nonatomic, strong) NSString *str3;
self.str1 = @"str1";
self.str2 = @"str2";
self.str3 = @"str3";
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"str1:%@ %p", self.str1, self.str1);
NSLog(@"str2:%@ %p", self.str2, self.str2);
NSLog(@"str3:%@ %p", self.str3, self.str3);
});
2020-07-01 14:37:16.465702+0800 Test_UI[76089:3507003] str1:str1 0x10c1d5150
2020-07-01 14:37:16.465822+0800 Test_UI[76089:3507003] str2:str2 0x10c1d5170
2020-07-01 14:37:16.465888+0800 Test_UI[76089:3507003] str3:str3 0x10c1d5190
但是為什么只能用 copy
呢?因?yàn)?NSString
的子類 NSMutableString
侠坎,他是存放在堆區(qū)的蚁趁。子類也是可以用父類來(lái)接收的,這中情況下 assign
就不能使用了实胸,它無(wú)法持有 NSMutableString
會(huì)導(dǎo)致被提前釋放他嫡。不使用 strong
而使用 copy
是為了在賦值的時(shí)候去除可變的特性番官,優(yōu)化內(nèi)存管理,讓存放在堆區(qū)的 NSMutableString
對(duì)象及時(shí)得到釋放钢属。
@property (nonatomic, assign) NSString *str1;
@property (nonatomic, copy) NSString *str2;
@property (nonatomic, strong) NSString *str3;
self.str1 = [@"str1" mutableCopy];
self.str2 = [@"str2" mutableCopy];
self.str3 = [@"str3" mutableCopy];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"str1:%@ %p", self.str1, self.str1);
NSLog(@"str2:%@ %p", self.str2, self.str2);
NSLog(@"str3:%@ %p", self.str3, self.str3);
NSLog(@"str class: %@", [@"str123" class]);
NSLog(@"mutable str copy class : %@", [[[@"str123" mutableCopy] copy] class]);
});
2020-07-01 14:38:10.159028+0800 Test_UI[76124:3508299] str1:str3 0x6000012d9e00
2020-07-01 14:38:10.159150+0800 Test_UI[76124:3508299] str2:str2 0xaffe863aa45e1a58
2020-07-01 14:38:10.159220+0800 Test_UI[76124:3508299] str3:str3 0x6000012d9e00
2020-07-01 14:38:10.159310+0800 Test_UI[76124:3508299] str class: __NSCFConstantString
2020-07-01 14:38:10.159393+0800 Test_UI[76124:3508299] mutable str copy class : NSTaggedPointerString
我們來(lái)分析一下上面輸出的情況可以看到 str1
和 str3
的地址變成一樣的了徘熔,str1
并沒有變成野指針崩潰,然后我同時(shí)也打印了一下內(nèi)容淆党,發(fā)現(xiàn) str1
的內(nèi)容變成了 str3
的內(nèi)容酷师。這是為什么呢?
因?yàn)槲覀兪褂?assign
修飾的 str1
染乌,在賦值之后沒有持有導(dǎo)致馬上就被釋放掉了山孔,而該屬性指向的內(nèi)容也沒有了,而在之后 str3
創(chuàng)建的對(duì)象剛好也是從這個(gè)地址開始創(chuàng)建的荷憋,所以就造成了上面的那種現(xiàn)象台颠。這里我們把后面 str2,str3
的代碼刪除之后運(yùn)行會(huì)發(fā)現(xiàn), str1
的內(nèi)容就變成空了台谊。
2020-07-01 15:13:42.852822+0800 Test_UI[92383:3561873] str1:<__NSMallocBlock__: 0x60000089e220> 0x60000089e220
接下來(lái)我們發(fā)現(xiàn) str2
的地址不是在常量區(qū)的蓉媳,這是為什么呢?
最后我們也打印了一下兩種字符串的類型锅铅,發(fā)現(xiàn)它們是不一樣的酪呻,為什么呢?同樣都是不可變字符串盐须,還都是 NSString
玩荠。
這是因?yàn)?NSString
是另一個(gè)類蔟,類蔟的定義和表現(xiàn)我們之后再講贼邓。__NSCFConstantString
該類型的會(huì)直接存放在常量區(qū)阶冈,這是代碼編譯時(shí)就能夠決定的,然后直接存放在常量區(qū)塑径。NSTaggedPointerString
這種類型的可以說(shuō)是動(dòng)態(tài)生成的女坑,編譯的時(shí)候無(wú)法判斷,所以在他產(chǎn)生的時(shí)候 動(dòng)態(tài)添加到棧區(qū)里面统舀。
可變類型不能使用
copy
匆骗。
可變類型的屬性如果使用copy
來(lái)修飾,在賦值的時(shí)候誉简,該值會(huì)進(jìn)行一次copy
操作碉就,導(dǎo)致屬性指向的是一個(gè)不可變的對(duì)象,如果用該屬性去調(diào)用可變對(duì)象的方法闷串,會(huì)產(chǎn)生崩潰瓮钥。Block到底使用什么來(lái)修飾?
block有一個(gè)特性,當(dāng)它訪問了外部局部變量(注意:這里是外部的局部變量碉熄,全局變量不受影響)桨武,就會(huì)存放在堆區(qū)。
@property (nonatomic, assign) void(^block1)(void);
@property (nonatomic, copy) void(^block2)(void);
@property (nonatomic, strong) void(^block3)(void);
void(^block1)(void) = ^() {
NSLog(@"1");
};
void(^block2)(void) = ^() {
NSLog(@"2");
};
void(^block3)(void) = ^() {
NSLog(@"3");
};
NSLog(@"block1: %p", block1);
NSLog(@"block2: %p", block2);
NSLog(@"block3: %p", block3);
self.block1 = block1;
self.block2 = block2;
self.block3 = block3;
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"block1: %p", self.block1);
NSLog(@"block2: %p", self.block2);
NSLog(@"block3: %p", self.block3);
});
2020-07-01 16:39:06.271597+0800 Test_UI[24869:3706043] block1: 0x10a984108
2020-07-01 16:39:06.271695+0800 Test_UI[24869:3706043] block2: 0x10a984128
2020-07-01 16:39:06.271759+0800 Test_UI[24869:3706043] block3: 0x10a984148
2020-07-01 16:39:06.294224+0800 Test_UI[24869:3706043] block1: 0x10a984108
2020-07-01 16:39:06.294314+0800 Test_UI[24869:3706043] block2: 0x10a984128
2020-07-01 16:39:06.294391+0800 Test_UI[24869:3706043] block3: 0x10a984148
所以這樣的block所有修飾符都可以修飾锈津。
@property (nonatomic, assign) void(^block1)(void);
@property (nonatomic, copy) void(^block2)(void);
@property (nonatomic, strong) void(^block3)(void);
int num = 2;
void(^block1)(void) = ^() {
NSLog(@"%d", num);
};
void(^block2)(void) = ^() {
NSLog(@"%d", num);
};
void(^block3)(void) = ^() {
NSLog(@"%d", num);
};
NSLog(@"block1: %p", block1);
NSLog(@"block2: %p", block2);
NSLog(@"block3: %p", block3);
self.block1 = block1;
self.block2 = block2;
self.block3 = block3;
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"block1: %p", self.block1);
NSLog(@"block2: %p", self.block2);
NSLog(@"block3: %p", self.block3);
});
2020-07-01 17:08:27.093104+0800 Test_UI[25619:3736522] block1: 0x600003fb1950
2020-07-01 17:08:27.093214+0800 Test_UI[25619:3736522] block2: 0x600003fb12f0
2020-07-01 17:08:27.093287+0800 Test_UI[25619:3736522] block3: 0x600003fb08d0
2020-07-01 17:08:27.102584+0800 Test_UI[25619:3736522] block1: 0x600003fb1950
2020-07-01 17:08:27.102696+0800 Test_UI[25619:3736522] block2: 0x600003fb12f0
2020-07-01 17:08:27.102785+0800 Test_UI[25619:3736522] block3: 0x600003fb08d0
這種block就不能使用 assign
來(lái)修飾玻募,如果及時(shí)釋放的話,訪問 self.block1
的時(shí)候會(huì)產(chǎn)生崩潰一姿。
而同樣的我們也可以看到七咧,不管是 copy
還是 strong
,block的地址都沒有變叮叹,所以它們是等價(jià)的艾栋,而使用 strong
更加直接,性能會(huì)更好蛉顽,而同樣的蝗砾,對(duì)已經(jīng)自動(dòng)管理的block類型而言,我們所有修飾符都可以使用携冤,所以為了通用悼粮,我們?cè)贏RC下使用 strong
來(lái)修飾所有的block,當(dāng)然也可以用 copy
曾棕,可以說(shuō) copy
的修飾是從MRC中繼承過來(lái)的扣猫。