Swift--結(jié)構(gòu)體和類

結(jié)構(gòu)體

  • 在Swift標(biāo)準(zhǔn)庫中,絕大多數(shù)的公開類型都是結(jié)構(gòu)體,而枚舉和類只占很小一部分
  • 比如BoolInt、DoubleString、Array、Dictionary等常見類型都是結(jié)構(gòu)體
1、struct Data {
2、    var year:Int
3、    var month:Int
4、  var day:Int
5、}
6、var date = Data(year: 2019, month: 6, day: 23)
  • 所有的結(jié)構(gòu)體都有一個編譯器自動生成的初始化器(initializer,初始化方法,構(gòu)造器,構(gòu)造方法)
  • 在第6行調(diào)用的,可以傳入所有成員值,用以初始化所有成員(存儲屬性,Stored Property)
  • 編譯器會根據(jù)情況,可能會為結(jié)構(gòu)體生成多個初始化器,宗旨是:保證所有成員都有初始值



struct Point {
    var x:Int?
    var y:Int?
}
var p1 = Point(x: 10, y: 10)
var p2 = Point(y:10)
var p3 = Point(x:10)
var p4 = Point()
  • 上述代碼可以編譯通過,因為可選項都有個默認(rèn)值nil
  • 一旦在定義結(jié)構(gòu)體時自定義了初始化器,編譯器就不會再幫它自動生成其他初始化器


初始化器

  • 所有的結(jié)構(gòu)體都有一個編譯器自動生成的初始化器(initializer,初始化方法)
  • 類需要自己生成有成員值的初始化器,Swift中創(chuàng)建類和結(jié)構(gòu)體的實例時必須為所有的存儲屬性設(shè)置一個合適的初始值。所以類必須要提供對應(yīng)的指定初始化器,同時我們也可以為當(dāng)前的類提供便捷初始化器(注意:便捷初始化器必須從相同的類里調(diào)用另一個初始化器)
class LGPerson {
    var age:Int
    var name:String
    init(_ age:Int,_ name:String) {
        self.age = age
        self.name = name
    }
    convenience init(_ age:Int){//便捷初始化器
        self.init(18,"kody")
        self.age = age
        self.name = ""
    }
}

注意點(diǎn)

1、指定初始化器必須保證在向上委托給父類初始化器之前,其所在類引入的所有屬性都要初始化完成
2、指定初始化器必須先向上委托父類初始化器,然后才能為繼承的屬性設(shè)置新值,如果不這樣做,指定初始化器賦予的新值將被父類中的初始化器所覆蓋
3、便捷初始化器必須先委托同類中的其他初始化器,然后再為任意屬性賦新值(包括同類里定義的屬性)。如果沒這么做,便捷初始化器賦予的新值將被自己類中其它指定初始化器所覆蓋
4、初始化器在第一階段初始化完成之前,不能調(diào)用任何實例方法、不能讀取任何實例屬性的值,也不能引用self作為值
class LGPerson {
    var age:Int
    var name:String
    init(_ age:Int,_ name:String) {
        self.age = age
        self.name = name
    }
    convenience init(_ age:Int){//便捷初始化器
        self.init(18,"kody")
        self.age = age
        self.name = ""
    }
}
class LGTeacher:LGPerson{
  var subjectName:String
  init (_ subjectName: String){
    self.subjectName = subjectName
    super.init(18,"awe")
  }
}
  • 可失敗初始化器:當(dāng)前因為參數(shù)的不合法或者外部條件的不滿足,存在初始化失敗的情況。這種Swift中可失敗初始化器寫return nil語句,來表明可失敗初始化器在何種情況下會觸發(fā)初始化失敗。寫法如下:
class LGPerson {
    var age:Int
    var name:String
    init?(age:Int,name:String) {
        if age<18 {return nil}
        self.age = age
        self.name = name
    }
}
  • 必要初始化器:在類的初始化器前添加required修飾符來表明所有該類的子類都必須實現(xiàn)該初始化器

初始化器的本質(zhì)

struct Point {
    var x:Int = 0
    var y:Int = 0
}
var p1 = Point()
struct Point {
    var x:Int
    var y:Int
    init(){
      self.x = 0
      self.y = 0
    }
}
var p1 = Point()

上面兩段代碼完全等效

結(jié)構(gòu)體內(nèi)存結(jié)構(gòu)

struct Point {
    var x:Int = 0
    var y:Int = 0
    var origin:Bool = false
    
}
var p1 = Point()
print(MemoryLayout<Point>.size)//17
print(MemoryLayout<Point>.stride)//24
print(MemoryLayout<Point>.alignment)//8

結(jié)構(gòu)體和類的主要相同點(diǎn)

  • 定義存儲值的屬性
  • 定義方法
  • 定義下標(biāo)以使用下標(biāo)語法提供對其值的訪問
  • 定義初始化值
  • 使用extension來拓展功能
  • 遵循協(xié)議來提供某種功能

結(jié)構(gòu)體和類的主要不同點(diǎn)

  • 類有繼承的特性,而結(jié)構(gòu)體沒有
  • 類型轉(zhuǎn)換使您能夠在運(yùn)行時檢查和解釋類實例的類型
  • 類有析構(gòu)函數(shù)用來釋放其分配的資源
  • 引用計數(shù)允許對一個類實例有多個引用

結(jié)構(gòu)體與類的本質(zhì)區(qū)別

  • 類是引用類型(指針類型),也就意味著一個類類型的變量并不直接存儲具體的實例對象,是對當(dāng)前存儲具體實例內(nèi)存地址的引用
  • 結(jié)構(gòu)體是值類型(枚舉也是值類型),相比較類類型的變量中存儲的是地址,那么值類型存儲的就是具體的實例(或者說具體的值)

類舉例

  • 類的定義和結(jié)構(gòu)體類似,但編譯器并沒有為類自動生成可以傳入成員值的初始化器



  • 如果類的所有成員都在定義的時候指定了初始值,編譯器會為類生成無參的初始化器
  • 成員的初始化是在這個初始化器中完成的
class Point {
    var x:Int = 10
    var y:Int = 20
}
let p1 = Point()
class Point {
    var x:Int
    var y:Int
    init() {
        x = 10
        y = 20
    }
}
let p1 = Point()

上面兩段代碼完全等效

  • 上圖都是針對64bit環(huán)境
    lldb指令:
    po/p:po和p的區(qū)別在于使用po只會輸出對應(yīng)的值,而p則會返回值的類型以及命令結(jié)果的引用名
    x/8g:讀取內(nèi)存中的值(8g:8字節(jié)格式輸出)
class LGTeacher {
    var age:Int
    var name:String
    init(age:Int,name:String) {
        self.age = age
        self.name = name
    }
}
var t = LGTeacher(age: 18, name: "Kody")
var t1 = t;

print("end")//打斷點(diǎn)

lldb

(lldb) po t
<LGTeacher: 0x10064e3d0>

(lldb) po t1
<LGTeacher: 0x10064e3d0>

(lldb) x/8g 0x10064e3d0
0x10064e3d0: 0x0000000100008180 0x0000000600000003
0x10064e3e0: 0x0000000000000012 0x0000000079646f4b
0x10064e3f0: 0xe400000000000000 0x0000000000000000
0x10064e400: 0x00000009a0080001 0x00007fff817025c8

(lldb) po withUnsafePointer(to: &t, {print($0)})  //獲取t的地址
0x0000000100008218
(lldb) po withUnsafePointer(to: &t1, {print($0)})  //獲取t1的地址
0x0000000100008220
  • 靜態(tài)的存儲屬性(static),也就是類型存儲屬性,他在程序運(yùn)行過程中,只初始化一次,因為本質(zhì)就是全局變量,全局變量在運(yùn)動過程中只初始化一次,而且static修飾的屬性默認(rèn)是lazy

結(jié)構(gòu)體舉例

struct LGTeacher {
    var age:Int
    var name:String
    init(age:Int,name:String) {
        self.age = age
        self.name = name
    }
}
var t = LGTeacher(age: 18, name: "Kody")
var t1 = t;

print("end")

lldb

(lldb) po t
? LGTeacher
  - age : 18
  - name : "Kody"

(lldb) po t1
? LGTeacher
  - age : 18
  - name : "Kody"

值類型

  • 值類型賦值給var、let或者給函數(shù)傳參,是直接將所有內(nèi)容拷貝一份
  • 類似于對文件進(jìn)行copy、paste操作,產(chǎn)生了全新的文件副本。屬于深拷貝(deep copy)
  • 值類型存儲在棧上


struct Point {
    var x:Int
    var y:Int
}

func test()  {
    var p1 = Point(x: 10, y: 20)
    var p2 = p1
    p2.x = 11
    p2.y = 22
//p1.x是10,p1.y是20
}

值類型的賦值操作

var s1 = "jack"
var s2 = s1
s2.append("_Rose")
print(s1)//jack
print(s2)//jack_Rose

var a1 = [1,2,3]
var a2 = a1
a2.append(4)
a1[0] = 2
print(a1)//[2,2,3]
print(a2)//[1,2,3,4]

var d1 = ["max":10,"min":2]
var d2 = d1
d1["other"] = 7
d2["max"] = 12
print(d1)//["other":7,"max":10,"min":2]
print(d2)//["max":12,"min":2]
  • 在Swift標(biāo)準(zhǔn)庫中,為了提升性能,String、Array、Dictiionary、Set采取了Copy On Write技術(shù),以String為例
var s1 = "Jack"
var s2 = s1
如果后續(xù)的代碼,沒有對s1和s2做修改,則s1和s2指向同一地址,即淺拷貝,如果后續(xù)有對s1或者s2做數(shù)據(jù)修改,則s1和s2會開出不同的地址做存儲,則深拷貝
  • 比如僅當(dāng)有“寫”操作時,才會真正執(zhí)行拷貝操作
  • 對于標(biāo)準(zhǔn)庫值類型的賦值操作,Swift能確保最佳性能,所以沒必要為了保證最佳性能來避免賦值
  • 建議:不需要修改的,盡量定義成let
struct Point {
    var x:Int
    var y:Int
}
var p1 = Point(x: 10, y: 20)
p1 = Point(x: 11, y: 22)
存儲地址是一樣的,只是替換值

引用類型

  • 引用賦值給var、let或者給函數(shù)傳參,是將內(nèi)存地址拷貝一份
  • 類似于制作一個文件的替身(快捷方式、鏈接),指向的是同一個文件,屬于淺拷貝
  • 引用類型存儲在堆上
class Size {
    var width:Int
    var height:Int
    init(width:Int,height:Int) {
        self.width = width
        self.height = height
    }
}

func test() {
    var s1 = Size(width: 10, height: 20)
    var s2 = s1
}
內(nèi)存區(qū)域
棧區(qū)(Stack):局部變量和函數(shù)運(yùn)行過程中的上下文
堆區(qū)(Heap):存儲所有對象
全局區(qū)(Global))(全局區(qū)也可以細(xì)分為全局區(qū)、常量區(qū)、text指令區(qū)):存儲全局變量、常量、代碼區(qū)
Segment&Section:Mach-o文件有多個段(segment),每個段有不同的功能,然后每個段又分為很多小的section
TEXT.text :機(jī)器碼
TEXT.cstring:硬編碼的字符串
TEXT.const:初始化過的常量
DATA.data:初始化過的可變的(靜態(tài)/全局)數(shù)據(jù)
DATA.const:沒有初始化過的常量
DATA.bss:沒有初始化的(靜態(tài)/全局)變量
DATA.common:沒有初始化過的符號聲明

引用類型的賦值操作

class Size {
    var width:Int
    var height:Int
    init(width:Int,height:Int) {
        self.width = width
        self.height = height
    }
}
var s1 = Size(width: 10, height: 20)
s1 = Size(width: 11, height: 22)

值類型、引用類型的let

struct Point {
    var x:Int
    var y:Int
}

class Size {
    var width:Int
    var height:Int
    init(width:Int,height:Int) {
        self.width = width
        self.height = height
    }
}

let p = Point(x: 10, y: 20)
p = Point(x: 11, y: 22)//error:Cannot assign to value: 'p' is a 'let' constant
p.x = 33//error:Cannot assign to property: 'p' is a 'let' constant
p.y = 44//error:Cannot assign to property: 'p' is a 'let' constant

let s = Size(width: 10, height: 20)
s = Size(width: 11, height: 22)//error:Cannot assign to value: 's' is a 'let' constant
s.width = 33
s.height = 44
let str = "Jac"
str.append("ss")//error:Cannot use mutating member on immutable value: 'str' is a 'let' constant

let arr = [1,2,3]
arr[0] = 11//error:Cannot assign through subscript: 'arr' is a 'let' constant
arr.append(4)//error:Cannot use mutating member on immutable value: 'arr' is a 'let' constant

嵌套類型

struct Poker {
    enum Suit:Character {
        case spades = "??",hearts = "??",diamonds = "??",clubs = "??"
    }
    enum Rank:Int {
        case two = 2,three,four,five,six,seven,eight,nine,ten
        case jack,queen,king,ace
    }
}
print(Poker.Suit.spades)//spades
var suit = Poker.Suit.spades
suit = .diamonds
var rank = Poker.Rank.five
rank = .king

枚舉、結(jié)構(gòu)體、類都可以定義方法

  • 一般把定義在枚舉、結(jié)構(gòu)體、類內(nèi)部的函數(shù),叫做方法
class Size {
    var width = 10
    var height = 10
    func show() {
        print("width = \(width),height = \(height)")
    }
}
let s = Size()
s.show()//width = 10,height = 10

struct Point {
    var x = 10
    var y = 10
    func show() {
       print("x = \(x),y = \(y)")
    }
}
let p = Point(x: 10, y: 10)
p.show()//x = 10,y = 10

enum PokerFace:Character {
    case spades = "??",hearts = "??",diamonds = "??",clubs = "??"
    func show() {
        print("face is \(rawValue)")
    }
}
let pf = PokerFace.hearts
pf.show()//face is ??
  • 方法不占用對象的內(nèi)存
  • 方法的本質(zhì)就是函數(shù),方法、函數(shù)都存放在代碼段

異變方法

Swift中classstruct都能定義方法,但是有一點(diǎn)區(qū)別的是默認(rèn)情況下,值類型屬性不能被自身的實例方法修改

struct Point {
    var x = 0.0,y = 0.0
    func moveBy(x deltaX:Double,y deltaY:Double)  {
        x += deltaX//error:Left side of mutating operator isn't mutable: 'self' is immutable
        y += deltaY//error:Left side of mutating operator isn't mutable: 'self' is immutable
    }
}
var p = Point()
p.moveBy(x:10.0, y:20.0)

想要修改,需要在func前邊添加mutating,如下:

struct Point {
    var x = 0.0,y = 0.0
    mutating func moveBy(x deltaX:Double,y deltaY:Double)  {
        x += deltaX
        y += deltaY
    }
}
var p = Point()
p.moveBy(x:10.0, y:20.0)

異變方法的本質(zhì):對于異變方法,傳入的self被標(biāo)記為inout參數(shù)。無論在mutating方法內(nèi)部發(fā)生什么,都會影響外部依賴類型的一切。

輸入輸出參數(shù):如果我們想函數(shù)能夠修改一個形式參數(shù)的值,而且希望這些改變在函數(shù)結(jié)束之后依然生效,那么就需要將形式參數(shù)定義為輸入輸出形式參數(shù)。在形式參數(shù)定義開始的時候在前邊添加一個inout關(guān)鍵字可以定義一個輸入輸出形式參數(shù)。

var age = 10
func modifyage(_ age:Int){
    age += 1//error:Left side of mutating operator isn't mutable: 'age' is a 'let' constant
報錯:因為函數(shù)的形式參數(shù)都是let類型的常量
}

如果想內(nèi)部修改age,并且影響外部age值,可以如下寫法
var age = 10
func modifyage(_ age:inout Int){
    age += 1
}
modifyage(&age)

方法調(diào)度

方式總結(jié):


影響函數(shù)派發(fā)方式

  • final:添加了final關(guān)鍵字的函數(shù)無法被重寫,使用靜態(tài)派發(fā),不會在vtable中出現(xiàn),且對objc運(yùn)行時不可見
  • dynamic:函數(shù)均可添加dynamic關(guān)鍵字,為非objc類和值類型的函數(shù)賦予動態(tài)性,但派發(fā)方式還是函數(shù)表派發(fā)
  • @objc:該關(guān)鍵字可以將Swift函數(shù)暴露給Objc運(yùn)行時,依舊是函數(shù)表派發(fā)
  • @objc+dynamic:消息派發(fā)的方式

內(nèi)聯(lián)函數(shù)

函數(shù)內(nèi)聯(lián)是一種編譯器優(yōu)化技術(shù),它通過使用方法的內(nèi)容替換直接調(diào)用該方法,從而優(yōu)化性能

  • 將確保有時內(nèi)聯(lián)函數(shù)。這是默認(rèn)行為,我們無需執(zhí)行任何操作,Swift編譯器可能會自動內(nèi)聯(lián)函數(shù)作為優(yōu)化
  • always- 將確保始終內(nèi)聯(lián)函數(shù),通過在函數(shù)前添加@inline(__always)來實現(xiàn)此行為
  • never - 將確保永遠(yuǎn)不會內(nèi)聯(lián)函數(shù)。這可以通過在函數(shù)前添加@inline(never)來實現(xiàn)
  • 如果函數(shù)很長并且想避免增加代碼段大小,請使用 @inline(never)
    如果對象只在聲明的文件中可見,可以用private或fileprivate進(jìn)行修飾。編譯器會對private或fileprivate對象進(jìn)行檢查,確保沒有其他繼承關(guān)系的情形下,自動打上final標(biāo)記,進(jìn)而使得對象獲得靜態(tài)派發(fā)的特性(fileprivate:只允許在定義的源文件中訪問,private:定義的聲明中訪問)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容