100 Days of SwiftUI - Day 8&9 Structs 結(jié)構(gòu)體

結(jié)構(gòu)體【Part 1】

1.創(chuàng)建一個(gè)結(jié)構(gòu)體

Swift 使您可以通過兩種方式設(shè)計(jì)自己的類型,最常見的稱struct.你可以給結(jié)構(gòu)體賦予它們變量及函數(shù),然后根據(jù)需要?jiǎng)?chuàng)建和使用它們。

簡(jiǎn)單的示例開始:

struct Sport {
    var name: String
}

創(chuàng)建使用它的實(shí)例:

var tennis = Sprot(name: "Tennis")
print(tennis.name)

還可改變它

tennis.name = "Lawn tennis"

屬性可以像常規(guī)變量一樣具有默認(rèn)值,還可以依靠用Swift的類型推斷。

使用結(jié)構(gòu)體還是元組?

struct User {
    var name: String
    var age: Int
    var city: String
}

使用元組可以這樣創(chuàng)建

var user:(name: String, age: Int, city: String)

元組非常適合一次性的使用,但當(dāng)它們一次又一次的使用時(shí),卻變得煩人。
想想看:如果你有幾個(gè)可以處理用戶信息的功能,你愿意怎樣寫:

func authenticate(_ user: User) { ... }
func showProfile(for user: User) { ... }
func signOut(_ user: User) { ... }

還是

func authenticate(_ user: (name: String, age: Int, city: String)) { ... }
func showProfile(for user: (name: String, age: Int, city: String)) { ... }
func signOut(_ user: (name: String, age: Int, city: String)) { ... }

新屬性添加到User struct中非常容易,而添加在每個(gè)元組中時(shí),變得辛苦而且容易出錯(cuò)。

因此,當(dāng)你想從一個(gè)函數(shù)返回多個(gè)或任意值時(shí),使用元組,當(dāng)要多次發(fā)送接收固定數(shù)據(jù)時(shí),選結(jié)構(gòu)體。

2.計(jì)算屬性

上面創(chuàng)建的可以存儲(chǔ)string的name屬性,稱之為存儲(chǔ)屬性,Swift具有另一種稱為計(jì)算屬性的屬性,計(jì)算屬性在代碼運(yùn)行時(shí)確定值。

struct Sport {
    var name: String
    var isOlympicSport: Bool
    
    var olympicStatus: String {
        isOlympicSport ? "\(name) is an olympic sport" : "\(name) is not an olympic sport"
    }
}

創(chuàng)建一個(gè)實(shí)例

let chess = Sport(name: "Chessboxing", isOlympicSport: false)
print(chess.olympicStatus)

使用計(jì)算屬性還是存儲(chǔ)屬性?

計(jì)算屬性每次調(diào)用時(shí)都將重新計(jì)算,實(shí)際上是一個(gè)函數(shù)調(diào)用。
當(dāng)屬性值不變,而且定期讀取時(shí),你應(yīng)該使用存儲(chǔ)屬性。
當(dāng)計(jì)算屬性依賴于其它屬性時(shí),使用計(jì)算屬性可以確保最新的狀態(tài)。
lazy可以解決很少讀取的存儲(chǔ)屬性帶來的性能問題,屬性觀測(cè)器還可以緩解依賴帶來問題,我們馬上會(huì)研究它。

3.屬性觀察器

我們可以跟蹤屬性的變化。

var name: String {
        // 屬性更改之前
        willSet {
            print(newValue)
        }
        // 屬性更改之后
        didSet {
            print(oldValue)
        }
    }

每次name變化,都將打印一條信息。

在屬性觀察器中添加函數(shù),意味著我們可以在屬性變更后,立即執(zhí)行函數(shù)。
但是如果在其中放置緩慢的工作,可能會(huì)帶來一些問題。

通常我們用didSet, 而willSet不常用,willSet使我們可以在更改之前了解程序的狀態(tài)。

4.Method方法

struct可以定義函數(shù),函數(shù)還可以使用struct的屬性。而構(gòu)造體內(nèi)的函數(shù)被稱為方法,也使用func關(guān)鍵字。

struct City {
    var population: Int
    func collectTaxes() -> Int {
        return population * 1000
    }
}
let london = City(population: 9_000_000)
london.collectTaxes()

Method與function的區(qū)別?
方法與函數(shù)的唯一區(qū)別,方法屬于某個(gè)類型,像結(jié)構(gòu)體,枚舉和類一樣,而函數(shù)不屬于。
屬于某個(gè)類型時(shí),意味著方法獲得一種超能力,可以引用該類型內(nèi)的其它屬性和方法。
方法還有一個(gè)優(yōu)點(diǎn),方法避免了“命名空間污染”。當(dāng)編寫100個(gè)函數(shù)時(shí),你會(huì)獲得100個(gè)保留名稱,但如果放到方法中,這會(huì)減少污染,我們就不會(huì)發(fā)生名稱沖突。

5. mutating method

Swift默認(rèn)不允許方法更改屬性,如果想在方法內(nèi)部更改屬性時(shí),需要使用mutating 關(guān)鍵字對(duì)其進(jìn)行標(biāo)記。

struct Person {
    var name: String
    mutating func makeAnonymous() {
        name = "Anonymous"
    }
}
var person = Person(name: "Ed")
person.makeAnonymous()

為什么要使用mutating標(biāo)記?
當(dāng)我們創(chuàng)建一個(gè)結(jié)構(gòu)體實(shí)例時(shí),結(jié)構(gòu)體內(nèi)部是無法分辨是變量還是常量,所以Swift在結(jié)構(gòu)體方法試圖更改屬性時(shí),需要標(biāo)記為mutating。

注意
1.標(biāo)記為mutating的方法,結(jié)構(gòu)體實(shí)例為常量時(shí),無法調(diào)用它。
2.未標(biāo)記為mutating的方法,無法調(diào)用mutating?方法。

6.String

String是一個(gè)結(jié)構(gòu)體,它們有自己的方法和屬性,可以查詢操作字符串。

let string = "Do or do not, there is no try."
print(string.count)
print(string.hasPrefix("Do"))
print(string.uppercased())
print(string.sorted())

為什么swift中的字符串是結(jié)構(gòu)體?
實(shí)際上Swift中很多類型都是結(jié)構(gòu)體,像字符串,整數(shù),數(shù)組,字典,布爾。
結(jié)構(gòu)體在Swift中是一種簡(jiǎn)單、快捷、高效的類型,所以我們可以隨意的創(chuàng)建銷毀它而不必的擔(dān)心性能問題。
由于語言很復(fù)雜,String要解決一個(gè)特別復(fù)雜的問題,比如英語英語26個(gè)大寫,26個(gè)小寫,超過50000個(gè)的漢字,還有很多的其它語言。還有非常復(fù)雜的表情符號(hào),表情中包含了膚色,性別等。一個(gè)表情可能有七個(gè)“字母”來存儲(chǔ)。
所以,Swift將處理字符串的功能存儲(chǔ)到字符串中,你可以很簡(jiǎn)單的使用諸如count屬性之類的功能,而不必?fù)?dān)心錯(cuò)誤的計(jì)算問題。

7.數(shù)組的屬性和方法

數(shù)組同樣是結(jié)構(gòu)體。

var toys = ["Woody"]
print(toys.count)
toys.append("Buzz")
print(toys.firstIndex(of: "Buzz"))
print(toys.sorted())
print(toys.remove(at: 0))

字符串實(shí)際是多個(gè)字符放在一起,為什么不能和數(shù)組一樣使用下標(biāo)呢?

由于表情符號(hào)還有其它復(fù)雜字符等,由多個(gè)特殊字符組成,所以如果你的字符串包含四個(gè)表情符號(hào),字符串很可能包含10個(gè)或20個(gè)特殊符號(hào)。
所以假設(shè)下標(biāo)可以使用,我們要找到第50000個(gè)字符,swift需要一個(gè)一個(gè)字符檢查是否是單個(gè)字符,還是多個(gè)特殊字符組合在一起,它的效率會(huì)變得很慢。
因此Swift將下標(biāo)功能不可用,而不讓開發(fā)人員隨意使用它時(shí),代碼變得慢起來。
而數(shù)組由于每個(gè)元素是存儲(chǔ)在大小相同的盒子中,所以問題不大。

另外提一個(gè)效率問題,當(dāng)檢查字符串是否為空時(shí)。

你應(yīng)該這樣寫

if myString.isEmpty {
    // code
}

而不是這樣

if myString.count == 0 {
    // code
}

第一種只檢查字符串第一個(gè)字符是否為空,便得出結(jié)果
而第二種需要檢查完全部字符串的數(shù)量后,然后與0比較得出結(jié)果,我們知道當(dāng)字符數(shù)量非常多時(shí),把字符串每個(gè)字符檢查一遍并不是一種特別快的方式。
所以第一種的性能上更高一些。

======================== Part 1分割 ========================

結(jié)構(gòu)體【Part 2】

下面是一些更高級(jí)的東西,這些使得結(jié)構(gòu)體更強(qiáng)大,比如訪問控制,靜態(tài)屬性,lazy。
比爾蓋茨曾說:“I choose a lazy person to do a hard job, Because a lazy person will find an easy way to do it.”
在Swift中,lazy是一項(xiàng)重要的性能優(yōu)化。

下面的部分在SwiftUI中得到了廣泛的使用。

8.Initializers 初始化方法

默認(rèn)情況下,結(jié)構(gòu)體自帶了一個(gè)成員初始化的初始化方法,要求在創(chuàng)建結(jié)構(gòu)體時(shí)為每個(gè)屬性提供一個(gè)值。

struct User {
    var username: String
}

當(dāng)我們創(chuàng)建一個(gè)實(shí)例時(shí),必須給username 賦值

var user = User(username: "twostraws")

我們可以用自己的初始化方法來替換默認(rèn)方法:

struct User {
    var username: String

    init() {
        username = "Anonymous"
        print("Creating a new user!")
    }
}

你必須確保所有的屬性都進(jìn)行賦值。

使用自定義初始化方法時(shí),默認(rèn)的初始化方法將無法使用。
但是我們也可以在自定義方法中放一些重要的工作。
如果你想保留原有的初始化方法,你可以將自定義方法,放到結(jié)構(gòu)體的extension中。

struct Employee {
    var name: String
    var yearsActive = 0
}

extension Employee {
    init() {
        self.name = "Anonymous"
        print("Creating an anonymous employee…")
    }
}
// creating a named employee now works
let roslin = Employee(name: "Laura Roslin")
// as does creating an anonymous employee
let anon = Employee()

這樣你便可以使用兩種方式來創(chuàng)建實(shí)例。

9.引用實(shí)例

在方法內(nèi)部,你會(huì)得到一個(gè)特殊常量self,常量指向當(dāng)前使用結(jié)構(gòu)的任何實(shí)例。當(dāng)創(chuàng)建了具有與屬性相同的參數(shù)名字時(shí),self非常有用。

struct Person {
    var name: String

    init(name: String) {
        print("\(name) was born!")
        self.name = name
    }
}

什么時(shí)候需要用self?
除了具有同名參數(shù)時(shí),通常我們可以不用寫self,使用self的主要原因是我們處于closure時(shí),Swift要求我們寫self。但從Swift 5.3開始,閉包中也不必寫了。

10.Lazy屬性

當(dāng)你盡在需要時(shí)才創(chuàng)建的一些屬性,比如下面的例子:

struct FamilyTree {
    init() {
        print("Creating family tree!")
    }
}
struct Person {
    var name: String
    var familyTree = FamilyTree()
    init(name: String) {
        self.name = name
    }
}
var ed = Person(name: "Ed")
lazy var familyTree = FamilyTree()

當(dāng)屬性加上lazy時(shí),在訪問屬性時(shí),才會(huì)出現(xiàn)表示創(chuàng)建成功的打印消息。

ed.familyTree

什么時(shí)候該使用lazy?
lazy使得屬性創(chuàng)建延遲到使用之前,它們和計(jì)算屬性一樣。不同的是,lazy后續(xù)的訪問不會(huì)再重復(fù)工作,因?yàn)樗鼈兊闹当痪彺媪恕?br> 所以,我們是否應(yīng)該使每個(gè)屬性都變?yōu)閘azy呢,實(shí)際上大多數(shù)應(yīng)該是標(biāo)準(zhǔn)的存儲(chǔ)屬性。
有以下幾個(gè)原因不使用lazy。例如:

1.使用lazy時(shí),可能會(huì)在你不想期望的地方進(jìn)行工作。比如,游戲程序內(nèi),一些會(huì)造成卡頓的操作,最好放到啟動(dòng)時(shí)。
2.lazy屬性始終存儲(chǔ)結(jié)果,可能是不必要的,當(dāng)需要重新計(jì)算時(shí)變得無意義。
3.惰性屬性會(huì)更改它們附加的基礎(chǔ)對(duì)象,在不可變結(jié)構(gòu)體不能使用。

11.靜態(tài)屬性和方法

靜態(tài)屬性使得所有實(shí)例共享特定的屬性和方法。
靜態(tài)屬性屬于結(jié)構(gòu)體本身而不屬于實(shí)例,所以讀取時(shí)如下方式。

struct Student {
    static var classSize = 0
    var name: String

    init(name: String) {
        self.name = name
        Student.classSize += 1
    }
}
print(Student.classSize)

Swift中的靜態(tài)屬性和方法有什么意義?

靜態(tài)屬性和方法常用用法是存儲(chǔ)整個(gè)應(yīng)用程序中的常用功能。
如下我想存儲(chǔ)一些常用信息,如一個(gè)app的URL,因此我可以在應(yīng)用程序任何地方引用它,這樣存儲(chǔ)它。

struct Unwrap {
    static let appURL = "https://itunes.apple.com/app/id1440611372"
}

使用Unwrap.appURL這樣有助于其它人訪問。而不必創(chuàng)建一個(gè)實(shí)例,來讀取url。

看下面這個(gè)例子:

enum Unwrap {
    private static var entropy = Int.random(in: 1...1000)
    static func getEntropy() -> Int {
        entropy += 1
        return entropy
    }
}

print(Unwrap.getEntropy())

這里使用枚舉而沒有使用結(jié)構(gòu)體,我們不需要?jiǎng)?chuàng)建實(shí)例,使用枚舉更合適。
還有一點(diǎn),因?yàn)橛辛?code>getEntropy()方法,所以我想限制Swift對(duì)entropy的訪問,這被稱為訪問控制。

12.訪問控制

訪問控制使你可以限制哪些代碼使用屬性和方法。有時(shí)候逆向阻止其它用戶直接讀取你的信息。

struct Person {
    private var id: String

    init(id: String) {
        self.id = id
    }

    func identify() -> String {
        return "My social security number is \(id)"
    }
}

使用了private關(guān)鍵字后id被設(shè)為私有,你無法從結(jié)構(gòu)體外部讀取它,只有Person內(nèi)部的方法可以讀取id屬性。
另一個(gè)選項(xiàng)public,它允許所有其它代碼使用屬性或方法。

訪問控制的重點(diǎn)是什么?

訪問控制遵循我們自己的規(guī)則,我們可以刪除它,繞過這些限制。所以這到底有什么用呢?
1.在不屬于自己的代碼中使用訪問控制,你無法刪除這些限制。比如Apple的API,它限制你可以做什么和不能做什么,你必須遵守這些限制。
2.訪問控制可以控制其他人如何看待我們的代碼,例如,我提供了一個(gè)結(jié)構(gòu)體給你使用,它有30個(gè)公共屬性和方法,你可能不確定可以使用,哪些僅供內(nèi)部使用,如果我將25個(gè)標(biāo)記為私有,那么,你不能在外部使用它們。

13.總結(jié)

1.你可以使用結(jié)構(gòu)體創(chuàng)建自己的類型,結(jié)構(gòu)體可以具有自己的屬性和方法。
2.你可以使用存儲(chǔ)屬性或使用計(jì)算屬性。
3.如果方法要更改結(jié)構(gòu)體內(nèi)的屬性,必須將其標(biāo)記為mutating。
4.初始化方法是結(jié)構(gòu)體的特殊方法。默認(rèn)情況,你可以獲得一個(gè)成員初始化方法,創(chuàng)建自己的初始化方法時(shí),必須為所有屬性賦一個(gè)值。
5.在結(jié)構(gòu)體內(nèi),可以使用self來訪問結(jié)構(gòu)體的當(dāng)前實(shí)例.
6.lazy關(guān)鍵字告訴Swift在屬性第一次使用時(shí),再進(jìn)行創(chuàng)建。
7.使用static關(guān)鍵字可以在所有結(jié)構(gòu)體的實(shí)例之間共享屬性和方法。
8.訪問控制可以限制你使用某些屬性和方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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