@property詳解,@property修飾符以及各個修飾符區別(上)


相信很多參加過面試的人員很多都會被問到: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

上述代碼就是手動創建變量的gettersetter的實現,gettersetter本質就是符合一定命名規范的實例方法。

具體使用

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允許使用點語法來訪問gettersetter

合成使用,使用@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引出的那些問題


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM