原文連接git
相信作過iOS開發的同窗都使用過@property,@property翻譯過來是屬性。在定義一個類時,經常會有多個@property,有了@property,咱們能夠用來保存類的一些信息或者狀態。好比定義一個Student類:github
@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *sex;
@end
複製代碼
Student類中有兩個屬性,分別是name和sex。面試
在程序中使用時,可使用安全
self.name = @"xxx";
self.sex = @"xxx";
複製代碼
那麼,爲何能夠這樣用呢?self.name是self的name變量嘛?仍是其餘的什麼?屬性中的copy表明什麼?nonatomic呢?下面來看一下這些問題的答案。bash
@property究竟是什麼呢?實際上@property = 實例變量 + get方法 + set方法。也就是說屬性數據結構
@property (nonatomic, copy) NSString *name;
複製代碼
表明的有實例變量,get方法和set方法。若是你們用過Java,相信對set方法和get方法應該很熟悉,這裏的set、get方法和Java裏面的做用是同樣的,get方法用來獲取變量的值,set方法用來設置變量的值。使用@property生成的實例變量、get方法、set方法的命名有嚴格的規範,實例變量的名稱、get方法名、set方法名稍後再介紹。app
這裏須要注意的是,包括實例變量、get方法和set方法,不會真的出如今咱們的編輯器裏面,使用屬性生成的實例變量、get方法、set方法是在編譯過程當中生成的。下面介紹一下set方法、get方法以及自動生成的實例變量。async
set方法也能夠稱爲setter方法,以後看到setter方法直接理解成set方法便可。同理,get方法也被稱爲getter方法。編輯器
仍是以上面的屬性:ide
@property (nonatomic, copy) NSString *name;
複製代碼
爲例,屬性name生成的setter方法是
- (void)setName:(NSString *)name;
複製代碼
該命名方法是固定的,是約定成束的。若是屬性名是firstName,那麼setter方法是:
- (void)setFirstName:(NSString *)firstName;
複製代碼
項目中,不少時候會有重寫setter方法的需求,只要重寫對應的方法便可。好比說重寫name屬性的setter方法:
- (void)setName:(NSString *)name
{
NSLog(@"rewrite setter");
_name = name;
}
複製代碼
關於_name是什麼,後續會介紹。
以屬性
@property (nonatomic, copy) NSString *name;
複製代碼
爲例,編譯器自動生成的getter方法是
- (NSString *)name;
複製代碼
getter方法的命名也是固定的。若是屬性名是firstName,那麼getter方法是:
- (NSString *)firstName;
複製代碼
重寫getter方法:
- (NSString *)name
{
NSLog(@"rewrite getter");
return _name;
}
複製代碼
若是咱們定義了name屬性,而且按照上面所述,重寫了getter方法和setter方法,Xcode會提示以下的錯誤:
Use of undeclared identifier '_name'; did you mean 'name'?
複製代碼
稍後咱們再解釋爲什麼會有該錯誤,以及如何解決。先來看一下_name究竟是什麼。
既然@property = 實例變量 + getter + setter,那麼屬性所生成的實例變量名是什麼呢?根據上面的例子,也很容易猜到,項目中也常用,實例變量的名稱就是_name。實例變量的命名也是有固定格式的,下劃線+屬性名。若是屬性是@property firstName,那麼生成的實例變量就是_firstName。這也是爲什麼咱們在setter方法和getter方法,以及其餘的方法中可使用_name的緣由。
這裏再提一下,不管是實例變量,仍是setter、getter方法,命名都是有嚴格規範的。正是由於有了這種規範,編譯器纔可以自動生成方法,這也要求咱們在項目中,對變量的命名,方法的命名遵循必定的規範。
定義一個@property,在編譯期間,編譯器會生成實例變量、getter方法、setter方法,這些方法、變量是經過自動合成(autosynthesize)的方式生成並添加到類中。實際上,一個類通過編譯後,會生成變量列表ivar_list,方法列表method_list,每添加一個屬性,在變量列表ivar_list會添加對應的變量,如_name,方法列表method_list中會添加對應的setter方法和getter方法。
既然有自動合成,那麼相對應的就要有非自動合成,非自動合成又稱爲動態合成。定義一個屬性,默認是自動合成的,默認會生成getter方法和setter方法,這也是爲什麼咱們能夠直接使用self.屬性名的緣由。實際上,自動合成對應的代碼是:
@synthesize name = _name;
複製代碼
這行代碼是編譯器自動生成的,無需咱們來寫。相應的,若是咱們想要動態合成,須要本身寫以下代碼:
@dynamic sex;
複製代碼
這樣代碼就告訴編譯器,sex屬性的變量名、getter方法、setter方法由開發者本身來添加,編譯器無需處理。
那麼這樣寫和自動合成有什麼區別呢?來看下面的代碼:
Student *stu = [[Student alloc] init];
stu.sex = @"male";
複製代碼
編譯,不會有任何問題。運行,也沒問題。可是當代碼執行到這一行的時候,程序崩潰了,崩潰信息是:
[Student setSex:]: unrecognized selector sent to instance 0x60000217f1a0
複製代碼
即:Student沒有setSex方法,沒有屬性sex的setter方法。這就是動態合成和自動合成的區別。動態合成,須要開發者本身來寫屬性的setter方法和getter方法。添加上setter方法:
- (void)setSex:(NSString *)sex
{
_sex = sex;
}
複製代碼
因爲使用@dynamic,編譯器不會自動生成變量,所以除此以外,還須要手動定義_sex變量,以下:
@interface Student : NSObject
{
NSString *_sex;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *sex;
@end
複製代碼
如今再編譯,運行,執行沒有錯誤和崩潰。
上面的例子中,重寫了屬性name的getter方法和setter方法,以下:
- (void)setName:(NSString *)name
{
NSLog(@"rewrite setter");
_name = name;
}
- (NSString *)name
{
NSLog(@"rewrite getter");
return _name;
}
複製代碼
可是編譯器會提示錯誤,錯誤信息以下:
Use of undeclared identifier '_name'; did you mean 'name'?
複製代碼
提示沒有_name變量。爲何呢?咱們沒有聲明@dynamic,那默認就是@autosynthesize,爲什麼沒有_name變量呢?奇怪的是,假若咱們把getter方法,或者setter方法註釋掉,gettter、setter方法只留下一個,不會有錯誤,爲何呢?
仍是編譯器作了些處理。對於一個可讀寫的屬性來講,當咱們重寫了其setter、getter方法時,編譯器會認爲開發者想手動管理@property,此時會將@property做爲@dynamic來處理,所以也就不會自動生成變量。解決方法,顯示的將屬性和一個變量綁定:
@synthesize name = _name;
複製代碼
這樣就沒問題了。若是一個屬性是隻讀的,重寫了其getter方法時,編譯器也會認爲該屬性是@dynamic,關於可讀寫、只讀,下面會介紹。這裏提醒一下,當項目中重寫了屬性的getter方法和setter方法時,注意下是否有編譯的問題。
使用自動合成時,針對
@property (nonatomic, copy) NSString *name;
複製代碼
屬性,生成的變量名是_name。假若,不習慣使用下劃線開頭的變量名,可否指定屬性對應的變量名呢?答案是能夠的,使用的是上面介紹過的@synthesize關鍵字。以下:
@synthesize name = stuName;
複製代碼
這樣,name屬性生成的變量名就是stuName,後續使用時須要寫stuName,而不是_name。如getter、setter方法:
- (void)setName:(NSString *)name
{
NSLog(@"rewrite setter");
stuName = name;
}
- (NSString *)name
{
NSLog(@"rewrite getter");
return stuName;
}
複製代碼
注意:雖然可使用@synthesize關鍵字修改變量名,可是如無特殊需求,不建議這樣作。由於默認狀況下編譯器已經爲咱們生成了變量名,大多數的項目、開發者也都會遵循這樣的規範,既然蘋果已經定義了一個好的規範,爲何不遵照呢?
有經驗的開發者應該都知道這一點,在getter方法中是不能使用self.的,好比:
- (NSString *)name
{
NSLog(@"rewrite getter");
return self.name; // 錯誤的寫法,會形成死循環
}
複製代碼
緣由代碼註釋中已經寫了,這樣會形成死循環。這裏須要注意的是:self.name實際上就是執行了屬性name的getter方法,getter方法中又調用了self.name, 會一直遞歸調用,直到程序崩潰。一般程序中使用:
self.name = @"aaa";
複製代碼
這樣的方式,setter方法會被調用。
當咱們定義一個字符串屬性時,一般咱們會這樣寫:
@property (nonatomic, copy) NSString *name;
複製代碼
當咱們定義一個NSMutableArray類型的屬性時,一般咱們會這樣寫:
@property (nonatomic, strong) NSMutableArray *books;
複製代碼
而當咱們定一個基本數據類型時,會這樣寫:
@property (nonatomic, assign) int age;
複製代碼
定義一個屬性時,nonatomic、copy、strong、assign等被稱做是關鍵字,或者是修飾符。
修飾符有四種:
@property (nonatomic, assign, getter=isPass) BOOL pass;
複製代碼
屬性pass的getter方法就是
- (BOOL)isPass;
複製代碼
聲明屬性時,若是不顯示指定修飾符,那麼默認的修飾符是哪些呢?或者說未指定的修飾符,默認取值是什麼呢?若是是基本數據類型,默認取值是:
atomic,readwrite,assign
複製代碼
若是是Objective-C對象,默認取值是:
atomic,readwrite,strong
複製代碼
上面提到了,聲明屬性時,一般使用nonatomic修飾符,緣由就是由於atomic並不能保證絕對的線程安全。舉例來講,假設有一個線程A在不斷的讀取屬性name的值,同時有一個線程B修改了屬性name的值,那麼即便屬性name是atomic,線程A讀到的仍舊是修改後的值,可見不是線程安全的。若是想要實現線程安全,須要手動的實現鎖。下面是一段示例代碼:
聲明name屬性,使用atomic修飾符
@property (atomic, copy) NSString *name;
複製代碼
對屬性name賦值。同時,一個線程在不斷的讀取name的值,另外一個線程在不斷的設置name的值:
stu.name = @"aaa";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for(int i = 0 ; i < 1000; ++i){
NSLog(@"stu.name = %@",stu.name);
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
stu.name = @"bbb";
});
複製代碼
看一下輸出:
2018-12-06 15:42:26.837215+0800 TestClock[15405:175815] stu.name = aaa
2018-12-06 15:42:26.837837+0800 TestClock[15405:175815] stu.name = bbb
複製代碼
證明了即便使用了atomic,也不能保證線程安全。
常常會有面試題問weak和assign的區別,這裏介紹一下。
weak和strong是對應的,一個是強引用,一個是弱引用。weak和assign的區別主要是體如今二者修飾OC對象時的差別。上面也介紹過,assign一般用來修飾基本數據類型,如int、float、BOOL等,weak用來修飾OC對象,如UIButton、UIView等。
假設聲明一個int類型的屬性,可是用weak來修飾,會發生什麼呢?
@property (nonatomic, weak) int age;
複製代碼
Xcode會直接提示錯誤,錯誤信息以下:
Property with 'weak' attribute must be of object type
複製代碼
也就是說,weak只能用來修飾對象,不能用來修飾基本數據類型,不然會發生編譯錯誤。
假設聲明一個UIButton類型的屬性,可是用assign來修飾,會發生什麼呢?
@property (nonatomic, assign) UIButton *assignBtn;
複製代碼
編譯,沒有問題,運行也沒有問題。咱們再聲明一個UIButton,使用weak來修飾,對比一下:
@interface ViewController ()
@property (nonatomic, assign) UIButton *assignBtn;
@property (nonatomic, weak) UIButton *weakButton;
@end
複製代碼
正常初始化兩個button:
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100,100,100,100)];
[btn setTitle:@"Test" forState:UIControlStateNormal];
btn.backgroundColor = [UIColor lightGrayColor];
self.assignBtn = btn;
self.weakButton = btn;
複製代碼
此時打印兩個button,沒有區別。釋放button:
btn = nil;
複製代碼
釋放以後打印self.weakBtn和self.assignBtn
NSLog(@"self.weakBtn = %@",self.weakButton);
NSLog(@"self.assignBtn = %@",self.assignBtn);
複製代碼
運行,執行到self.assignBtn的時候崩潰了,崩潰信息是
EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
複製代碼
weak和assign修飾對象時的差異體現出來了。
weak修飾的對象,當對象釋放以後,即引用計數爲0時,對象會置爲nil
2018-12-06 16:17:05.774298+0800 TestClock[15863:192570] self.weakBtn = (null)
複製代碼
而向nil發送消息是沒有問題的,不會崩潰。
assign修飾的對象,當對象釋放以後,即引用計數爲0時,對象會變爲野指針,不知道指向哪,再向該對象發消息,很是容易崩潰。
所以,當屬性類型是對象時,不要使用assign,會帶來一些風險。
上面說到,屬性用assign修飾,當被釋放後,容易變爲野指針,容易帶來崩潰問題,那麼,爲什麼基本數據類型能夠用assign來修飾呢?這就涉及到堆和棧的問題。
相對來講,堆的空間大,一般是不連續的結構,使用鏈表結構。使用堆中的空間,須要開發者本身去釋放。OC中的對象,如 UIButton 、UILabel ,[[UIButton alloc] init] 出來的,都是分配在堆空間上。
棧的空間小,約1M左右,是一段連續的結構。棧中的空間,開發者不須要管,系統會幫忙處理。iOS開發 中 int、float等變量分配內存時是在棧上。若是棧空間使用完,會發生棧溢出的錯誤。
因爲堆、棧結構的差別,棧和堆分配空間時的尋址方式也是不同的。由於棧是連續的控件,因此棧在分配空間時,會直接在未使用的空間中分配一段出來,供程序使用;若是剩下的空間不夠大,直接棧溢出;堆是不連續的,堆尋找合適空間時,是順着鏈表結點來尋找,找到第一塊足夠大的空間時,分配空間,返回。根據二者的數據結構,能夠推斷,堆空間上是存在碎片的。
回到問題,爲什麼assign修飾基本數據類型沒有野指針的問題?由於這些基本數據類型是分配在棧上,棧上空間的分配和回收都是系統來處理的,所以開發者無需關注,也就不會產生野指針的問題。
擴展一下,棧是線程安全的嘛?回答問題以前,先看一下進程和線程的關係。
線程是進程的一個實體,是CPU調度和分派的基本單位。一個進程能夠擁有多個線程。線程自己是不配擁有系統資源的,只擁有不多的,運行中必不可少的資源(如程序計數器、寄存器、棧)。可是線程能夠與同屬於一個進程的其餘線程,共享進程所擁有的資源。一個進程中全部的線程共享該進程的地址空間,可是每一個線程有本身獨立的棧,iOS系統中,每一個線程棧的大小是1M。而堆則不一樣。堆是進程所獨有的,一般一個進程有一個堆,這個堆爲本進程中的全部線程所共享。
其實經過上面的介紹,該問題答案已經很明顯了:棧是線程安全的。
堆是多個線程所共有的空間,操做系統在對進程進行初始化的時候,會對堆進行分配; 棧是每一個線程所獨有的,保存線程的運行狀態和局部變量。棧在線程開始的時化,每一個線程的棧是互相獨立的,所以棧是線程安全的。
屬性修飾符中,還有一個常常被問到的面試題是copy和strong。何時用copy,爲何?何時用strong,爲何?以及mutableCopy又是什麼?這一節介紹一下這些內容。
首先看一下copy和strong,copy和strong的區別也是面試中出現頻率最高的。以前舉得例子中其實已經出現了copy和strong:
@property (nonatomic, copy) NSString *sex;
@property (nonatomic, strong) NSMutableArray *books;
複製代碼
一般狀況下,不可變對象屬性修飾符使用copy,可變對象屬性修飾符使用strong。
Objective-C中存在可變對象和不可變對象的概念。像NSArray、NSDictionary、NSString這些都是不可變對象,像NSMutableArray、NSMutableDictionary、NSMutableString這些是可變對象。可變對象和不可變對象的區別是,不可變對象的值一旦肯定就不能再修改。下面看個例子來講明。
- (void)testNotChange
{
NSString *str = @"123";
NSLog(@"str = %p",str);
str = @"234";
NSLog(@"after str = %p",str);
}
複製代碼
NSString是不可變對象。雖然在程序中修改了str的值,可是此處的修改其實是系統從新分配了空間,定義了字符串,而後str從新指向了一個新的地址。這也是爲什麼修改以後地址不一致的緣由:
2018-12-06 22:02:41.350812+0800 TestClock[884:17969] str = 0x106ec1290
2018-12-06 22:02:41.350919+0800 TestClock[884:17969] after str = 0x106ec12d0
複製代碼
再來看可變對象的例子:
- (void)testChangeAble
{
NSMutableString *mutStr = [NSMutableString stringWithString:@"abc"];
NSLog(@"mutStr = %p",mutStr);
[mutStr appendString:@"def"];
NSLog(@"after mutStr = %p",mutStr);
}
複製代碼
NSMutableString是可變對象。程序中改變了mutStr的值,且修改先後mutStr的地址一致:
2018-12-06 22:10:08.457179+0800 TestClock[1000:21900] mutStr = 0x600002100540
2018-12-06 22:10:08.457261+0800 TestClock[1000:21900] after mutStr = 0x600002100540
複製代碼
上面說了,可變對象使用strong,不可變對象使用copy。那麼,若是不可變對象使用strong來修飾,會有什麼問題呢?寫代碼測試一下:
@property (nonatomic, strong) NSString *strongStr;
複製代碼
首先明確一點,既然類型是NSString,那麼則表明咱們不但願testStr被改變,不然直接使用可變對象NSMutableString就能夠了。另外須要提醒的一點是,NSMutableString是NSString的子類,對繼承瞭解的應該都知道,子類是能夠用來初始化父類的。
介紹完以後,來看一段代碼。
- (void)testStrongStr
{
NSString *tempStr = @"123";
NSMutableString *mutString = [NSMutableString stringWithString:tempStr];
self.strongStr = mutString; // 子類初始化父類
NSLog(@"self str = %p mutStr = %p",self.strongStr,mutString); // 二者指向的地址是同樣的
[mutString insertString:@"456" atIndex:0];
NSLog(@"self str = %@ mutStr = %@",self.strongStr,mutString); // 二者的值都會改變,不可變對象的值被改變
}
複製代碼
注意:**咱們定義的不可變對象strongStr,在開發者無感知的狀況下被篡改了。**所謂無感知,是由於開發者沒有顯示的修改strongStr的值,而是再修改其餘變量的值時,strongStr被意外的改變。這顯然不是咱們想獲得的,並且也是危險的。項目中出現相似的bug時,一般都很難定位。這就是不可變對象使用strong修飾所帶來的風險。
上面說了不可變對象使用strong的問題,那麼可變對象使用copy有什麼問題呢?仍是寫代碼來驗證一下:
@property (nonatomic, copy) NSMutableString *mutString;
複製代碼
這裏仍是強調一下,既然屬性類型是可變類型,說明咱們指望再程序中可以改變mutString的值,不然直接使用NSString了。
看一下測試代碼:
- (void)testStrCopy
{
NSString *str = @"123";
self.mutString = [NSMutableString stringWithString:str];
NSLog(@"str = %p self.mutString = %p",str,self.mutString); // 二者的地址不同
[self.mutString appendString:@"456"]; // 會崩潰,由於此時self.mutArray是NSString類型,是不可變對象
}
複製代碼
執行程序後,會崩潰,崩潰緣由是:
[NSTaggedPointerString appendString:]: unrecognized selector sent to instance 0xed877425eeef9883
複製代碼
即 self.mutString沒有appendString方法。self.mutString是NSMutableString類型,爲什麼沒有appendString方法呢?這就是使用copy形成的。看一下
self.mutString = [NSMutableString stringWithString:str];
複製代碼
這行代碼到底發生了什麼。這行代碼實際上完成了兩件事:
// 首先聲明一個臨時變量
NSMutableString *tempString = [NSMutableString stringWithString:str];
// 將該臨時變量copy,賦值給self.mutString
self.mutString = [tempString copy];
複製代碼
注意,經過[tempString copy]獲得的self.mutString是一個不可變對象,不可變對象天然沒有appendString方法,這也是爲什麼會崩潰的緣由。
另外經常使用來作對比的是copy和mutableCopy。copy和mutableCopy之間的差別主要和深拷貝和淺拷貝有關,先看一下深拷貝、淺拷貝的概念。
所謂淺拷貝,在Objective-C中能夠理解爲引用計數加1,並無申請新的內存區域,只是另一個指針指向了該區域。深拷貝正好相反,深拷貝會申請新的內存區域,原內存區域的引用計數不變。看圖來講明深拷貝和淺拷貝的區別。
首先A指向一塊內存區域,如今設置B = A
如今B和A指向了同一塊內存區域,即爲淺拷貝。
再來看深考貝
首先A指向一塊內存區域,如今設置B = A
A和B指向的不是同一塊內存區域,只是這兩塊內存區域中的內容是同樣的,即爲深拷貝。
可變對象的copy和mutableCopy都是深拷貝。以可變對象NSMutableString和NSMutableArray爲例,測試代碼:
- (void)testMutableCopy
{
NSMutableString *str1 = [NSMutableString stringWithString:@"abc"];
NSString *str2 = [str1 copy];
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"str1 = %p str2 = %p str3 = %p",str1,str2,str3);
NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@"a",@"b", nil];
NSArray *array2 = [array1 copy];
NSMutableArray *array3 = [array1 mutableCopy];
NSLog(@"array1 = %p array2 = %p array3 = %p",array1,array2,array3);
}
複製代碼
輸出結果:
2018-12-07 13:01:27.525064+0800 TestClock[9357:143436] str1 = 0x60000086d8f0 str2 = 0xc8c1a5736a50d5fe str3 = 0x60000086d9b0
2018-12-07 13:01:27.525198+0800 TestClock[9357:143436] array1 = 0x600000868000 array2 = 0x60000067e5a0 array3 = 0x600000868030
複製代碼
能夠看到,只要是可變對象,不管是集合對象,仍是非集合對象,copy和mutableCopy都是深拷貝。
不可變對象的copy是淺拷貝,mutableCopy是深拷貝。以NSString和NSArray爲例,測試代碼以下:
- (void)testCopy
{
NSString *str1 = @"123";
NSString *str2 = [str1 copy];
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"str1 = %p str2 = %p str3 = %p",str1,str2,str3);
NSArray *array1 = @[@"1",@"2"];
NSArray *array2 = [array1 copy];
NSMutableArray *array3 = [array1 mutableCopy];
NSLog(@"array1 = %p array2 = %p array3 = %p",array1,array2,array3);
}
複製代碼
輸出結果:
2018-12-07 13:06:29.439108+0800 TestClock[9442:147133] str1 = 0x1045612b0 str2 = 0x1045612b0 str3 = 0x6000017e4450
2018-12-07 13:06:29.439236+0800 TestClock[9442:147133] array1 = 0x6000019f5c80 array2 = 0x6000019f5c80 array3 = 0x6000017e1170
複製代碼
能夠看到,只要是不可變對象,不管是集合對象,仍是非集合對象,copy都是淺拷貝,mutableCopy都是深拷貝。
項目開發中常常會有自定義對象的需求,那麼自定義對象是否能夠copy呢?如何支持copy?
自定義對象能夠支持copy方法,咱們所須要作的是:自定義對象遵照NSCopying協議,且實現copyWithZone方法。NSCopying協議是系統提供的,直接使用便可。
遵照NSCopying協議:
@interface Student : NSObject <NSCopying>
{
NSString *_sex;
}
@property (atomic, copy) NSString *name;
@property (nonatomic, copy) NSString *sex;
@property (nonatomic, assign) int age;
@end
複製代碼
實現CopyWithZone方法:
- (instancetype)initWithName:(NSString *)name age:(int)age sex:(NSString *)sex
{
if(self = [super init]){
self.name = name;
_sex = sex;
self.age = age;
}
return self;
}
- (instancetype)copyWithZone:(NSZone *)zone
{
// 注意,copy的是本身,所以使用本身的屬性
Student *stu = [[Student allocWithZone:zone] initWithName:self.name age:self.age sex:_sex];
return stu;
}
複製代碼
測試代碼:
- (void)testStudent
{
Student *stu1 = [[Student alloc] initWithName:@"Wang" age:18 sex:@"male"];
Student *stu2 = [stu1 copy];
NSLog(@"stu1 = %p stu2 = %p",stu1,stu2);
}
複製代碼
輸出結果:
stu1 = 0x600003a41e60 stu2 = 0x600003a41fc0
複製代碼
這裏是一個深拷貝,根據copyWithZone方法的實現,應該很容易明白爲什麼是深拷貝。
除了NSCopying協議和copyWithZone方法,對應的還有NSMutableCopying協議和mutableCopyWithZone方法,實現都是相似的,不作過多介紹。