1. 程式人生 > >Swift與OC中拷貝與可變性總結

Swift與OC中拷貝與可變性總結

為了解釋方便,定義兩個類:PersonMyObject,它們都繼承自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指標地址和pobject指標地址不同。

但是至於data會不會被拷貝,這取決於MyObject類如何設計,如果MyObjectcopy方法只是淺拷貝,就會形成如上圖所示的情況。如果MyObjectcopy方法也是深拷貝,那麼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
)
*/

可變性

容器物件分為可變容器與不可變容器,NSDataNSArrayNSString等都是不可變容器,以NSMutable開頭的則是它們的可變版本。下面統一用NSArrayNSMutableArray舉例說明。

因為NSMutableArrayNSArray的子類,所以NSArray物件不能強制轉換成NSMutableArray,否則在呼叫addObject方法時會崩潰。反之,NSMutableArray可以轉換成它的父類NSArray,這麼做會導致它失去可變性。

容器拷貝的難點在於可變性的變化。容器有兩種方法:copymutableCopy,再次強調這兩者都是淺拷貝。它們的區別在於,返回值是否是可變的。前者返回不可變容器,後者返回可變容器。

這也就是說,返回值的可變性與被拷貝物件的可變性無關,僅取決於呼叫了何種拷貝方法。比如:

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地址相同

這是因為既然array1array2都不能再修改,那麼兩者共用同一塊記憶體也是無所謂的,所以OC做了這樣的優化。

字串拷貝

字串也可以被當做容器來理解。它有NSStringNSMutableString兩個版本。

於是為什麼字串屬性要定義成@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好得多。它的可變性非常簡單,完全通過letvar控制,這也是Swift相比於OC的一個優點吧,畢竟高階的語言應該儘可能封裝底層實現。

簡書部落格地址

相關推薦

SwiftOC拷貝可變性總結

為了解釋方便,定義兩個類: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]; 對象通過設置器

python2python3編碼解碼的區別

需要 顯示 數據類型 在屏幕上 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

MockitoSpring@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開發之swiftOC混編出現的坑,oc不能對swift的代理進行呼叫,不能訪問swift的代理,swift的回撥方法

1. swift與oc混編譯具體怎麼實現,這兒我就不重複講出了,網上有大把的人講解。 2. 在swift與OC混編的編譯環境下, oc類不能訪問swift建立類中的代理? 解決方法如下: 在代理的頭部加上 @objc(代理名字),這樣就在外部就可以訪問了,如下圖。 然

js的innerText、innerHTML、屬性值、valuejQuery的text()、html()、屬性值、val()總結

att text color btn col class 屬性 fun value js與jQuery獲取text、html、屬性值、value的方法是不一樣的。 js與jQuery,text與innerText獲取(<!---->中為結果) html:

敏捷轉型whyhow的總結

技術分享 統計 增量 實現 this 球隊 append log 動軟 敏捷轉型參考框架: 為了成功順暢地推行敏捷開發。下面將對整個敏捷轉型參考框架作個整體說明。為企業進行敏捷轉型提供基本方法參考。整個敏捷轉型參考框架主要包括5個步驟,前兩個步驟主要

UIWebViewJSOC交互 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

jQuerythis$(this)的區別總結

fun 方法 spa attr .get 就是 click 裏的 box 這裏就談談this與$(this)的區別。 1、jQuery中this與$(this)的區別 $("#textbox").hover( function() {

java stringbytes的轉換總結

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

Pythonlist的復制及深拷貝拷貝探究

python3 接下來 after mic 怎樣 tro 重要 技術 循環   在Python中,經常要對一個list進行復制。對於復制,自然的就有深拷貝與淺拷貝問題。深拷貝與淺拷貝的區別在於,當從原本的list復制出新的list之後,修改其中的任意一個是否會對另一個造成影