Value Type vs Reference Type in Swift

Mitsui_發表於2018-01-02

前言 值型別 vs 引用型別

1、什麼是值型別

值型別就是值直接儲存在變數中。例如:

int a = 10;
a = 20;
複製程式碼

值型別賦新值的時候會直接覆蓋舊值。

Value Type vs Reference Type in Swift

// 1
int b = a;
// 2
b = 30;
複製程式碼

按註釋:

1.這段程式碼首先宣告瞭一個int型別的變數b,然後將a中儲存的值賦值給b

2.給b賦新值,不會影響a中儲存的值。

Value Type vs Reference Type in Swift

2、什麼是引用型別

引用型別,變數中儲存的是地址,地址指向實際的物件。例如:

NSString *foo = @"hello";
// NSString *foo = [[NSString alloc] initWithString:@"hello"];
foo = @"world";
// foo = [[NSString alloc] initWithString:@"world"];
複製程式碼

引用型別變數重新賦值的時候會改變變數中儲存的地址,新的地址會覆蓋舊的地址,並且新的地址指向(引用)新建立的物件。

Value Type vs Reference Type in Swift

NSMutableString *foo = [NSMutableString stringWithString:@"helllo, "];
// 1
NSMutableString *bar = foo;
// 2
[bar appendString:@"OC"];
NSLog(@"%@", foo);
// print "helllo, OC"
複製程式碼

1、宣告瞭一個新的變數barbar中儲存的地址等於foo中儲存的地址,因此它們指向同一個物件。

Value Type vs Reference Type in Swift

2、當修改bar指向的物件時,foo指向的物件也被修改,因為它們倆引用的是同一個物件。

Value Type vs Reference Type in Swift

一、Swift中的值型別和引用型別

相對於OC,Swift 的優點主要有安全、快速、簡潔。Swift的安全性主要體現在,它會在編譯時就確定要呼叫的方法和屬性(靜態優化),而不是像OC一樣到執行時才確定呼叫哪個方法。Swift會在編譯時就告訴你,你的程式碼是否有bug(比如訪問了野指標,陣列越界等),而OC會在執行時才發現這些錯誤並造成APP的crash。

類似於OC中的類,Swift中的類都是引用型別,結構體、列舉、元組都是值型別。

值型別的特點就是拷貝,假如我們建立了一個值型別的例項,當它作為引數傳遞給函式或者賦值給常量或變數時,這個例項就會生成一份它自身的拷貝(unique copy),當對這個例項進行賦值、引數傳遞等操作時,實際上被賦值或者傳遞的是拷貝的那一份,無論怎麼修改都不會改變該例項本身。而引用型別的例項進行賦值、引數傳遞等操作時,實際上傳遞的就是它本身,我們在函式中修改的就是它自身。類似於 C 函式中的值傳遞和引用傳遞。

1、引用型別

// Reference type example
class C { 
	var data: Int = -1 
}
let x = C()
let y = x			    // x is copied to y
x.data = 42				// changes the instance referred to by x (and y)
println("\(x.data), \(y.data)")	// prints "42, 42"
複製程式碼

上述程式碼的記憶體如下圖所示,變數xy指向同一個類C的例項。

Value Type vs Reference Type in Swift

2、值型別

// Value type example
struct S { 
	var data: Int = -1
}
var a = S()
var b = a						// a is copied to b
a.data = 42						// Changes a, not b
println("\(a.data), \(b.data)")	// prints "42, -1"

複製程式碼

記憶體如下圖所示,對例項進行賦值操作時,b = a,會將a的拷貝賦值給b,而不是a本身,因此,ab儲存了兩個不同的例項,對其中一個的修改不會影響另一個(下圖箭頭並不是指標的意思,為了表示清楚一點用了箭頭)。

Value Type vs Reference Type in Swift

值型別拷貝操作的時間複雜度為基於被拷貝資料量大小的O(n)。當值型別的例項傳遞到函式中時,實際上是宣告瞭一個區域性變數,它的作用域僅僅是函式的內部,所以當出了函式時,這份拷貝的例項就會被釋放,因此不用擔心它的空間複雜度或者拷貝浪費記憶體的問題。

3、可變性

對於值型別和引用型別,關鍵字varlet是有區別的。對於引用型別來說,let的意思是,引用必須是常量,換句話你說,你不能修改常量指向其他例項,即不能修改常量中儲存的地址,但是你可以修改例項本身。

let x = C()
let m = C()
x = m //ERROR: note: change 'let' to 'var' to make it mutable
x.data = 55 // It's OK
複製程式碼

對於值型別來說,let的意思是例項必須是常量,而常量也是不變的,因此不能修改常量中儲存的值,也不能修改值的本身。

let a = S()
a.data = 2 //ERROR: note: change 'let' to 'var' to make it mutable
let b = S()
a = b //ERROR: note: change 'let' to 'var' to make it mutable
複製程式碼

因此,相對於引用型別來說,值型別能更好控制例項可變和不可變,例如NSStringNSMutableString,只需要將字串宣告為let a = " ",那麼它就是不可變型別的字串,宣告為var a: String?,它就是可變字串。(Swift中的String為值型別)

4、如何選擇

If you always get a unique, copied instance, you can trust that no other part of your app is changing the data under the covers. This is especially helpful in multi-threaded environments where a different thread could alter your data out from under you. This can create nasty bugs that are extremely hard to debug.

如果你一直希望持有一個例項的一份獨立的備份,即使APP在其他地方修改了這部分資料,你仍然能訪問一份原始資料。這在多執行緒的環境中特別有用,如果其他子執行緒在你不知道的情況下修改了這個例項,那麼你仍然持有一份最原始的資料的備份。

使用值型別的情況:

  • 使用==比較兩個例項時
  • 當你需要一份獨立的備份時
  • 多執行緒中可能會修改資料時

使用引用型別的情況:

  • 使用===比較兩個例項時
  • 你需要共享並且修改這個型別的例項時

==(Equal To)表示兩個例項的值相等(注意,值型別變數中儲存的是值不是地址);

===(Identical To)表示兩個例項完全相等,包括它們在記憶體中的地址也相同。

在Swift中,Array ``String ``Dictionary ``Int``Bool等資料型別都是值型別,換句話說都是struct而不是class。因此在對它們賦值、傳參等操作時,切記它是值傳遞,而不是引用傳遞。

func foo(a: [String])
{
    var b = a
    b.append("haha")
}

var arr: [String] = ["hello", "world"]
foo(a: arr)
print("\(arr)")
// ["hello", "world"]
複製程式碼

二、enum、struct、class

Swift的型別系統:

Value Type vs Reference Type in Swift

Named type(命名型別)是指在定義時,可以使用特定的名字去命名的型別。例如:

let foo: Double = 0.0
// Double: 特定的命名型別
複製程式碼

Named model type(命名模型型別)是指可以在宣告的時候可以重寫setter或者getter的型別。例如:

假如你有一個儲存半徑的變數,然後要宣告一個儲存直徑的變數:

var radius = 5.0;
var diameter: Double {
	get {
		return radius * 2
	}
	set {
		radius = newValue / 2
	}
}
複製程式碼

Compound type(複合型別)是指沒有特定命名的型別。例如:

// declare a tuple 
let foo: (String, [String]) = ("foo", ["bar"])
// not
let foo: Tuple = ("foo", ["bar"]) 
// error
複製程式碼

1、enum

(1)、初始值

列舉型別的每一個選項都可以帶有一個初始值,和OC中的列舉型別一樣,不同的是,Swift允許你指定這個初始值的型別,並且給每一個選項賦值。

enum Color: String {
    case black = "black"
    case gray  = "gray"
    case blue  = "blue"
    case white = "white"
}

// 省略寫法
//enum Color: String {
//    case black ,gray, blue, white
//}

let black = Color.black.rawValue
print(black)
// black
複製程式碼

(2)關聯值

Swift中的列舉型別的每一個選項都允許你傳入一個變數作為它的關聯值,你可以在switch它的時候使用這個變數。例如:

enum CSColor {
// 宣告關聯值的型別
    case named(Color)
    case rgb(UInt8, UInt8, UInt8) // 0-255
}
// CustomStringConvertible:這個協議的作用就是用字面量來描述一個類、結構體或者列舉
extension CSColor: CustomStringConvertible {
    var description: String {
        get {
            switch self {
            case .named(let name):
                return name.rawValue
            case .rgb(let r, let g, let b):
                return String.init(format: "0x%02X%02X%02X", r, g, b)
            }
        }
    }
}
let color = CSColor.rgb(255, 255, 255)
// 0xFFFFFF
複製程式碼

*(3)Optional

Optional在Swift中首先是個神奇的東西,其次是個非常重要的東西,理解它對於學習Swift重中之重。 它的宣告:

enum Optional<T> {
    case none      // 沒有值
    case some(T)   // 有型別為T的某個值
}
複製程式碼

Optional型別其實是一個列舉型別,它只有兩個選項,要麼是none,要麼是某個T型別的值some

(I)關於?

?的意思是宣告的變數是個Optional型別。例如:

var foo: Double?
複製程式碼

宣告一個Double型別的變數,可以不賦值或者賦初值為nil,否則必須初始化它,如果一個變數不是Optional型別,那它永遠不可能是nil

當我們在使用?解包的時候,它的執行過程應該為(虛擬碼):

// 1
var bar: String? = "BAR"
// 2
let x = bar?.lowercased
複製程式碼
// 1 虛擬碼
var bar: Optional<String> = "bar"
// 2 ?的作用
switch bar {
case .none:
    bar = nil
case .some(let xx):
    bar = xx
}

if bar == nil {
    return nil
} else {
    return bar.lowercased
}

複製程式碼

如果bar = nil,則返回nil,如果bar != nil,則返回bar的值,並且呼叫lowercased方法。當然返回值賦值給xx也是Optional型別,因為它也可能為nil

(II)關於!

!的作用是強制解包的意思(unwrap),即,將Optional型別的變數強制解包為某個型別的值。如果這個值為nil,仍然強制解包的話,應用將會在執行時crash。所以如果不能確定變數的值是一定不是nil的話,千萬不要用!解包。這種方式可以保證應用不會crash:

var bar: String?
var x: String?
if bar != nil {
    x = bar!.lowercased
}
print(x)
複製程式碼
(III)關於nil

OC中的nil代表空指標的意思,就是C中的NULL,它的定義為:

// MacType.h
#ifndef NULL
#define NULL    __DARWIN_NULL
#endif /* ! NULL */
#ifndef nil
  #if defined(__has_feature) 
    #if __has_feature(cxx_nullptr)
      #define nil nullptr // nullptr 空指標常量
    #else
      #define nil __DARWIN_NULL
    #endif
  #else
    #define nil __DARWIN_NULL
  #endif
#endif
複製程式碼
// C
#define NULL (void *)0
複製程式碼

在OC中我們可以向nil傳送訊息(呼叫方法),因為OC是一門動態的語言,在編譯期它並不關心一個物件是不是nil,它只關心這個型別的物件能不能接收這條訊息。它會在執行時判斷訊息的接收者是不是nil,如果是nil,它會根據訊息的返回型別返回對應的值,例如要求返回int型別,返回值就是0BOOL型別,返回值就是NOid型別,返回值就是nil,函式將直接返回。

Swift中的nil,並不是空指標的意思,因為Swift並不是一門純物件導向的語言,大大減少了對引用型別的依賴,使用較多的是值型別,因此空指標對值型別來說,並沒有什麼意義。因此,nil表示的是沒有值(the absence of a value)即,這塊記憶體的這個變數中沒有儲存任何值。所以它在Swift中的定義為:

typealias nil = Optional.none 
複製程式碼

2、struct & class

Swift中的類都是引用型別,結構體都是值型別。

Comparing Classes and Structures (類和結構體的異同點)

Classes and structures in Swift have many things in common. Both can (共同點):

Define properties to store values. (宣告屬性)

Define methods to provide functionality. (宣告方法)

Define subscripts to provide access to their values using subscript syntax. (使用點語法)

Define initializers to set up their initial state. (使用構造方法初始化)

Be extended to expand their functionality beyond a default implementation. (擴充套件預設實現以外的功能)

Conform to protocols to provide standard functionality of a certain kind. (使用協議)

For more information, see Properties, Methods, Subscripts, Initialization, Extensions, and Protocols. (更多資訊)

Classes have additional capabilities that structures do not (類比結構體多的能力):

Inheritance enables one class to inherit the characteristics of another. (類可以繼承)

Type casting enables you to check and interpret the type of a class instance at runtime. (型別轉換使您能夠在執行時檢查和解釋例項的型別)

Deinitializers enable an instance of a class to free up any resources it has assigned. (提供析構方法來釋放一個類佔用的資源)

Reference counting allows more than one reference to a class instance. (引用計數允許存在多個對例項的引用)

相關文章