Swift與OC中拷貝與可變性總結
為了解釋方便,定義兩個類:Person
和MyObject
,它們都繼承自NSObject
。他們的關係如下:
// Person.h
@property (strong, nonatomic, nullable) MyObject *object;
// MyObjec.h
@property (copy, nonatomic) NSString *name;
普通物件拷貝
對於一個OC中的物件來說,可能涉及拷貝的有三種操作:
1.retain操作: Person *p = [[Person alloc] init]; Person *p1 = p; 這裡的p1預設是__strong,所以它會對p進行retain操作。retain與複製無關,只會對引用計數加1。p1和p的地址是完全一樣的: 2015-12-23 21:24:31.893 Copy[1300:120857] p = 0x1006012c0 2015-12-23 21:24:31.894 Copy[1300:120857] p1 = 0x1006012c0 這種寫法最簡單,而且嚴格來說不是複製,但值得一提,因為在接下來的OC和Swift中,都會涉及到這樣的程式碼。 2.copy方法: 呼叫copy方法需要實現NSCopying協議,並提供copyWithZone方法: - (id)copyWithZone:(NSZone *)zone { Person *copyInstance = [[self class] allocWithZone:zone]; copyInstance.object = self.object; return copyInstance; } 第二行程式碼就是剛剛所說的retain操作。因此,我們雖然複製了Person物件的指標,但是其內部的屬性,依然和原來物件的相同。 3.自定義拷貝方法: 我們當然可以自己定義一個拷貝方法,在複製Person物件的同時,把其中的object屬性也複製,而不是僅僅retain。
第二三種複製方法的區別如圖所示:
兩種拷貝方式
淺拷貝與深拷貝
標為紅色的是兩種拷貝方式的不同之處。對於左邊這種,只拷貝指標本身的拷貝方法,我們稱為淺拷貝。對於右邊那種,不僅拷貝指標自身,還拷貝指標中所有元素的拷貝方法,我們稱為深拷貝。
沒有明確的限制copy
和自定義的拷貝方法要如何實現。也就是說copy
方法可以用來進行深拷貝,我們也可以自定義淺拷貝的方法。這完全取決於我們自己如何實現copy
方法和自定義的拷貝方法。在OC中,對於自定義的類來說,淺拷貝與深拷貝只是一種概念,並沒有明確的標註哪種方法就是淺拷貝。
注意
“深拷貝將被拷貝的物件完全複製”這種說法不完全正確。比如上圖中我們看到data
p
深拷貝後得到的p'
的object
指標地址和p
的object
指標地址不同。
但是至於data
會不會被拷貝,這取決於MyObject
類如何設計,如果MyObject
的copy
方法只是淺拷貝,就會形成如上圖所示的情況。如果MyObject
的copy
方法也是深拷貝,那麼data
的地址也會不同。
容器物件拷貝
在OC中,所有Foundation
中的容器類,分為可變容器和不可變容器,它們的拷貝都是淺拷貝。這也就是為什麼建議自定義的物件實現淺拷貝,如果有需要才自定義深拷貝方法。因為這樣一來,所有的方法呼叫就都可以統一,不至於造成誤解。
如果我們把陣列想象成一個三層的資料結構,第一層是陣列自己的指標,第二層是存放在陣列中的指標,第三層(如果第二層是指標)則是這些指標指向的物件。那麼在複製陣列時,複製的是前兩層,第三層的物件不會被複制。如果把前兩層看做指標,第三層看做物件,那麼陣列的拷貝,無論是copy
還是mutableCopy
都是淺拷貝。當然,也有人把這個稱為“單層深拷貝”,這些概念性的定義都不重要,重要的是知道陣列拷貝時的原理。
這一點很好理解。首先,指標所指向的物件,也許很大,深拷貝可能佔用過多的記憶體和時間。其次,容器不知道自己儲存的物件是否實現了NSCopying
協議。如果容器的拷貝預設是深拷貝,同時你在陣列中存放了Person
類的物件,而Person
類根本沒有實現NSCopying
協議,後果是複製容器會導致程式崩潰。這是任何語言開發者都不希望看到的,所以設身處地想一下,如果是你來設計OC,也不會讓陣列深拷貝吧。
觀察下面這段程式碼,思考一下為什麼a1[0] = @0
沒有影響a2
:
NSMutableArray *a1 = [[NSMutableArray alloc] initWithObjects:@1, @2, nil];
NSMutableArray *a2 = [a1 mutableCopy];
a1[0] = @0;
NSLog(@"a2 = %@", a2);
/*
2015-12-23 23:11:53.711 Copy[1795:220469] a2 = (
1,
2
)
*/
可變性
容器物件分為可變容器與不可變容器,NSData
、NSArray
、NSString
等都是不可變容器,以NSMutable
開頭的則是它們的可變版本。下面統一用NSArray
和NSMutableArray
舉例說明。
因為NSMutableArray
是NSArray
的子類,所以NSArray
物件不能強制轉換成NSMutableArray
,否則在呼叫addObject
方法時會崩潰。反之,NSMutableArray
可以轉換成它的父類NSArray
,這麼做會導致它失去可變性。
容器拷貝的難點在於可變性的變化。容器有兩種方法:copy
和mutableCopy
,再次強調這兩者都是淺拷貝。它們的區別在於,返回值是否是可變的。前者返回不可變容器,後者返回可變容器。
這也就是說,返回值的可變性與被拷貝物件的可變性無關,僅取決於呼叫了何種拷貝方法。比如:
NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithObjects:@1, @2, nil];
NSMutableArray *array = [mutableArray copy];
[array addObject:@3];
儘管我們呼叫了mutableArray
的拷貝方法,返回值也宣告為NSMutableArray
,但是呼叫addObject
方法時依然會導致執行時錯誤。這是由錯誤的呼叫了copy
方法導致的。
呼叫一個物件的淺拷貝方法會得到一個新的物件(地址不同),但是容器類中有一個特例:
NSArray *array1 = @[@1, @2];
NSArray *array2 = [array1 copy];
// array1和array2地址相同
這是因為既然
array1
和array2
都不能再修改,那麼兩者共用同一塊記憶體也是無所謂的,所以OC做了這樣的優化。
字串拷貝
字串也可以被當做容器來理解。它有NSString
和NSMutableString
兩個版本。
於是為什麼字串屬性要定義成@property(copy, nonatomic)
就很好理解了。它主要用於處理這種情況:
NSMutableString *string = @"hello";
self.propertyString = string;
[string appendString:@" world"];
如果屬性定義成
strong
,那麼在第二步執行了retain
操作,第三步對string
的修改就會影響到原來的屬性。現在我們把屬性定義為copy
,那麼第二步操作其實是得到了一個新的,不可變字串。這符合我們的預期目的。
Swift拷貝
結構體拷貝
陣列、字典等容器在Swift中被定義成了結構體,它們的拷貝規則和OC完全不同:
var array1 = [1,2,3]
var array2 = array1
array1[0] = 0
print(array2) // 輸出結果:[1, 2, 3]
可以看到,即使是最簡單的等號賦值,也會淺拷貝原來的值。這是由Swift中結構體的值語義決定的。之所以說是淺拷貝而不是深拷貝,理由參見前文解釋OC中容器的淺拷貝,尤其是第二點理由,不管是對於OC還是Swift來說都是通用的。
物件拷貝
和OC中指標賦值類似,物件的直接賦值操作與拷貝無關:
class Person {
var name: String;
init(name:String) {
self.name = name
}
}
let person1 = Person(name: "zxy")
let person2 = person1
person1.name = "new name"
print(person2.name) //結果是“new name”
如果要拷貝物件,有兩種方法。首先,最自然想到的是實現
NSCopying
協議,注意只有NSObject
類的物件才能實現這個協議:
class Person : NSObject, NSCopying {
var name: String;
init(name:String) {
self.name = name
}
func copyWithZone(zone: NSZone) -> AnyObject {
return Person(name: self.name)
}
}
但這樣做最大的問題在於,你必須繼承自
NSObject
,這就又回到了OC的那一套。如果我們希望定義純粹的Swift類,完全可以自己定義並實現拷貝方法。
“面向介面程式設計”的原則告訴我們,我們應該讓Person
實現某個介面,而不是繼承自某個子類:
protocol Copyable {
func copy() -> Copyable
}
class Person : Copyable {
var name: String;
init(name:String) {
self.name = name
}
func copy() -> Copyable {
return Person(name: self.name)
}
}
let person1 = Person(name: "zxy")
let person2 = person1.copy() as! Person
這樣就完美的實現Swift-Style拷貝了。
總結
在OC中,淺拷貝通常由NSCopying
協議的copyWithZone
方法實現,深拷貝需要自定義方法。直接複製意味著retain
而不是拷貝。
在Swift中,值型別直接用等號賦值意味著淺拷貝,引用型別的拷貝可以通過實現自定義的Copyable
協議或OC的NSCopying
協議完成。
在OC中,我們需要容器的可變性,而Swift在這一點做的要比OC好得多。它的可變性非常簡單,完全通過let
和var
控制,這也是Swift相比於OC的一個優點吧,畢竟高階的語言應該儘可能封裝底層實現。
簡書部落格地址
相關推薦
Swift與OC中拷貝與可變性總結
為了解釋方便,定義兩個類:Person和MyObject,它們都繼承自NSObject。他們的關係如下: // Person.h @property (strong, nonatomic, nullable) MyObject *object; // MyObjec.h @property (copy,
OC中方法與函式的區別
方法:方法是Objective-C獨有的一種結構,只能在Objective-C中宣告、定義和使用,C語言不能宣告、定義和使用。1、類方法以+號開頭,物件方法以-號開頭+ (void) init; // 類方法- (void) show;
OC中對於屬性的總結(@property)
sar 版本號 來講 ret ado ews -a sco retain 在沒有屬性之前: 對成員變量進行改動都要用到設置器:setter來改動 Person *per =[[Person alloc] init]; 對象通過設置器
python2與python3中編碼與解碼的區別
需要 顯示 數據類型 在屏幕上 nbsp gb2312 python2 想要 區別 python2中程序默認數據類型為ASCII, 所以需要先將數據解碼(decode)成為Unicode類型, 然後再編碼(encode)成為想要轉換的數據類型(gbk,utf-8,gb180
swift 開發過程中的一些小總結
import FoundationextensionString {var md5 : String{let str = self.cString(using:String.Encoding.utf8)let strLen = CC_LONG(self.lengthOfBytes(using:Str
Mockito與Spring中@Autowired與@InjectMocks組合
@InjectMocks @Autowired private TestClass testClass; @Mock private TestClassPropert
OC 與 Swift混編 Swift的閉包傳值到OC中
隨著Swift的日益完善,終究會取代OC的地位,就像Kotlin取代Java一樣,不是時間的問題,而是使用者願意不願意被時代淘汰的問題 首先是Swift中定義閉包 一定要注意在Swift的類前面加上@objcMembers,這樣才能在OC中引用;在引用的變數前面加上@objc
NSString為什麼要用copy關鍵字,如果用strong會有什麼問題 OC中的深拷貝與淺拷貝
首先說一下深拷貝和淺拷貝,深拷貝是記憶體拷貝,淺拷貝是指標拷貝 寫程式碼的時候有兩個copy方法 - (id)copy; - (id)mutableCopy; copy出的物件為不可變型別 mutableCopy出的物件為可變型別 NSString N
iOS開發之swift與OC混編出現的坑,oc中不能對swift的代理進行呼叫,不能訪問swift中的代理,swift中的回撥方法
1. swift與oc混編譯具體怎麼實現,這兒我就不重複講出了,網上有大把的人講解。 2. 在swift與OC混編的編譯環境下, oc類不能訪問swift建立類中的代理? 解決方法如下: 在代理的頭部加上 @objc(代理名字),這樣就在外部就可以訪問了,如下圖。 然
js中的innerText、innerHTML、屬性值、value與jQuery中的text()、html()、屬性值、val()總結
att text color btn col class 屬性 fun value js與jQuery獲取text、html、屬性值、value的方法是不一樣的。 js與jQuery,text與innerText獲取(<!---->中為結果) html:
敏捷轉型中why與how的總結
技術分享 統計 增量 實現 this 球隊 append log 動軟 敏捷轉型參考框架: 為了成功順暢地推行敏捷開發。下面將對整個敏捷轉型參考框架作個整體說明。為企業進行敏捷轉型提供基本方法參考。整個敏捷轉型參考框架主要包括5個步驟,前兩個步驟主要
UIWebView中JS與OC交互 WebViewJavascriptBridge的使用
del js代碼 document types alua 功能 bridged use decide 一、綜述 現在很多的應用都會在多種平臺上發布,所以很多程序猿們都開始使用Hybrid App的設計模式。就是在app上嵌入網頁,只要寫一份網頁代碼,就可以跑在不同的系統
Python中深拷貝與淺拷貝區別
分配 img 地址 append 淺拷貝 pen image pre 內容 淺拷貝, list值是可變的,str值不可變,只能重新賦值 a=b=c=‘wjx‘print(a,b,c)c= ‘jmy‘#重新賦值了,所以內存分配了新的地址print(a,b,c)print(id
jQuery中this與$(this)的區別總結
fun 方法 spa attr .get 就是 click 裏的 box 這裏就談談this與$(this)的區別。 1、jQuery中this與$(this)的區別 $("#textbox").hover( function() {
java 中string與bytes的轉換總結
set post java lan bsp nbsp 發現 blog fff 最近在和導航設備的通訊服務,和設備通訊時,需要將字符串以UTF-16編碼傳遞。 那如何將string,轉換為byte[]?其實Java提供了現成的實現:java.lang.string.getby
python中的淺拷貝與深拷貝
post 相同 pre body python and aos deep light 淺拷貝可以拿丈夫與媳婦公用一張銀行卡來舉例 # python >>> husband = [‘liang‘,123,[10000,6000]] #丈夫的銀行卡信息
js中的深拷貝與淺拷貝
nbsp 中一 局限性 深拷貝與淺拷貝 ext bsp post body extend 對於字符串類型,淺拷貝是對值的拷貝,對於對象來說,淺拷貝是對對象地址的拷貝,並沒有開辟新的棧,也就是拷貝的結果是兩個對象指向同一個地址,修改其中一個對象的屬性,則另一個對象的屬性也會改
【總結】C++與C#中的static靜態修飾符
this sea const 數據 一切都 UC 限制 有一個 字段 重點 靜態類(sealed+abstract) 靜態構造函數(無參,無限制符,自動執行一次) 靜態變量(類級別,實例無關,靜態存儲區中) 靜態方法(不能被重寫) 靜態局部變量(始終存在) 靜態本質是實
SQL Server 與MySQL中排序規則與字符集相關知識的一點總結
bubuko col https 中文字符集 目前 創建 har 運算 進制 原文:SQL Server 與MySQL中排序規則與字符集相關知識的一點總結 字符集&&排序規則 字符集是針對不同語言的字符編碼的集合,比如UTF-8字符集,GBK字符集,G
Python中list的復制及深拷貝與淺拷貝探究
python3 接下來 after mic 怎樣 tro 重要 技術 循環 在Python中,經常要對一個list進行復制。對於復制,自然的就有深拷貝與淺拷貝問題。深拷貝與淺拷貝的區別在於,當從原本的list復制出新的list之後,修改其中的任意一個是否會對另一個造成影