100 Days of SwiftUI - Day 8&9 Structs 结构体

结构体【Part 1】

1.创建一个结构体

Swift 使您可以通过两种方式设计自己的类型,最常见的称struct.你可以给结构体赋予它们变量及函数,然后根据需要创建和使用它们。

简单的示例开始:

struct Sport {
    var name: String
}

创建使用它的实例:

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

还可改变它

tennis.name = "Lawn tennis"

属性可以像常规变量一样具有默认值,还可以依靠用Swift的类型推断。

使用结构体还是元组?

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

使用元组可以这样创建

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

元组非常适合一次性的使用,但当它们一次又一次的使用时,却变得烦人。
想想看:如果你有几个可以处理用户信息的功能,你愿意怎样写:

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中非常容易,而添加在每个元组中时,变得辛苦而且容易出错。

因此,当你想从一个函数返回多个或任意值时,使用元组,当要多次发送接收固定数据时,选结构体。

2.计算属性

上面创建的可以存储string的name属性,称之为存储属性,Swift具有另一种称为计算属性的属性,计算属性在代码运行时确定值。

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

创建一个实例

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

使用计算属性还是存储属性?

计算属性每次调用时都将重新计算,实际上是一个函数调用。
当属性值不变,而且定期读取时,你应该使用存储属性。
当计算属性依赖于其它属性时,使用计算属性可以确保最新的状态。
lazy可以解决很少读取的存储属性带来的性能问题,属性观测器还可以缓解依赖带来问题,我们马上会研究它。

3.属性观察器

我们可以跟踪属性的变化。

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

每次name变化,都将打印一条信息。

在属性观察器中添加函数,意味着我们可以在属性变更后,立即执行函数。
但是如果在其中放置缓慢的工作,可能会带来一些问题。

通常我们用didSet, 而willSet不常用,willSet使我们可以在更改之前了解程序的状态。

4.Method方法

struct可以定义函数,函数还可以使用struct的属性。而构造体内的函数被称为方法,也使用func关键字。

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

Method与function的区别?
方法与函数的唯一区别,方法属于某个类型,像结构体,枚举和类一样,而函数不属于。
属于某个类型时,意味着方法获得一种超能力,可以引用该类型内的其它属性和方法。
方法还有一个优点,方法避免了“命名空间污染”。当编写100个函数时,你会获得100个保留名称,但如果放到方法中,这会减少污染,我们就不会发生名称冲突。

5. mutating method

Swift默认不允许方法更改属性,如果想在方法内部更改属性时,需要使用mutating 关键字对其进行标记。

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

为什么要使用mutating标记?
当我们创建一个结构体实例时,结构体内部是无法分辨是变量还是常量,所以Swift在结构体方法试图更改属性时,需要标记为mutating

注意
1.标记为mutating的方法,结构体实例为常量时,无法调用它。
2.未标记为mutating的方法,无法调用mutating�方法。

6.String

String是一个结构体,它们有自己的方法和属性,可以查询操作字符串。

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

为什么swift中的字符串是结构体?
实际上Swift中很多类型都是结构体,像字符串,整数,数组,字典,布尔。
结构体在Swift中是一种简单、快捷、高效的类型,所以我们可以随意的创建销毁它而不必的担心性能问题。
由于语言很复杂,String要解决一个特别复杂的问题,比如英语英语26个大写,26个小写,超过50000个的汉字,还有很多的其它语言。还有非常复杂的表情符号,表情中包含了肤色,性别等。一个表情可能有七个“字母”来存储。
所以,Swift将处理字符串的功能存储到字符串中,你可以很简单的使用诸如count属性之类的功能,而不必担心错误的计算问题。

7.数组的属性和方法

数组同样是结构体。

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

字符串实际是多个字符放在一起,为什么不能和数组一样使用下标呢?

由于表情符号还有其它复杂字符等,由多个特殊字符组成,所以如果你的字符串包含四个表情符号,字符串很可能包含10个或20个特殊符号。
所以假设下标可以使用,我们要找到第50000个字符,swift需要一个一个字符检查是否是单个字符,还是多个特殊字符组合在一起,它的效率会变得很慢。
因此Swift将下标功能不可用,而不让开发人员随意使用它时,代码变得慢起来。
而数组由于每个元素是存储在大小相同的盒子中,所以问题不大。

另外提一个效率问题,当检查字符串是否为空时。

你应该这样写

if myString.isEmpty {
    // code
}

而不是这样

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

第一种只检查字符串第一个字符是否为空,便得出结果
而第二种需要检查完全部字符串的数量后,然后与0比较得出结果,我们知道当字符数量非常多时,把字符串每个字符检查一遍并不是一种特别快的方式。
所以第一种的性能上更高一些。

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

结构体【Part 2】

下面是一些更高级的东西,这些使得结构体更强大,比如访问控制,静态属性,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是一项重要的性能优化。

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

8.Initializers 初始化方法

默认情况下,结构体自带了一个成员初始化的初始化方法,要求在创建结构体时为每个属性提供一个值。

struct User {
    var username: String
}

当我们创建一个实例时,必须给username 赋值

var user = User(username: "twostraws")

我们可以用自己的初始化方法来替换默认方法:

struct User {
    var username: String

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

你必须确保所有的属性都进行赋值。

使用自定义初始化方法时,默认的初始化方法将无法使用。
但是我们也可以在自定义方法中放一些重要的工作。
如果你想保留原有的初始化方法,你可以将自定义方法,放到结构体的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()

这样你便可以使用两种方式来创建实例。

9.引用实例

在方法内部,你会得到一个特殊常量self,常量指向当前使用结构的任何实例。当创建了具有与属性相同的参数名字时,self非常有用。

struct Person {
    var name: String

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

什么时候需要用self?
除了具有同名参数时,通常我们可以不用写self,使用self的主要原因是我们处于closure时,Swift要求我们写self。但从Swift 5.3开始,闭包中也不必写了。

10.Lazy属性

当你尽在需要时才创建的一些属性,比如下面的例子:

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()

当属性加上lazy时,在访问属性时,才会出现表示创建成功的打印消息。

ed.familyTree

什么时候该使用lazy?
lazy使得属性创建延迟到使用之前,它们和计算属性一样。不同的是,lazy后续的访问不会再重复工作,因为它们的值被缓存了。
所以,我们是否应该使每个属性都变为lazy呢,实际上大多数应该是标准的存储属性。
有以下几个原因不使用lazy。例如:

1.使用lazy时,可能会在你不想期望的地方进行工作。比如,游戏程序内,一些会造成卡顿的操作,最好放到启动时。
2.lazy属性始终存储结果,可能是不必要的,当需要重新计算时变得无意义。
3.惰性属性会更改它们附加的基础对象,在不可变结构体不能使用。

11.静态属性和方法

静态属性使得所有实例共享特定的属性和方法。
静态属性属于结构体本身而不属于实例,所以读取时如下方式。

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

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

Swift中的静态属性和方法有什么意义?

静态属性和方法常用用法是存储整个应用程序中的常用功能。
如下我想存储一些常用信息,如一个app的URL,因此我可以在应用程序任何地方引用它,这样存储它。

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

使用Unwrap.appURL这样有助于其它人访问。而不必创建一个实例,来读取url。

看下面这个例子:

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

print(Unwrap.getEntropy())

这里使用枚举而没有使用结构体,我们不需要创建实例,使用枚举更合适。
还有一点,因为有了getEntropy()方法,所以我想限制Swift对entropy的访问,这被称为访问控制。

12.访问控制

访问控制使你可以限制哪些代码使用属性和方法。有时候逆向阻止其它用户直接读取你的信息。

struct Person {
    private var id: String

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

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

使用了private关键字后id被设为私有,你无法从结构体外部读取它,只有Person内部的方法可以读取id属性。
另一个选项public,它允许所有其它代码使用属性或方法。

访问控制的重点是什么?

访问控制遵循我们自己的规则,我们可以删除它,绕过这些限制。所以这到底有什么用呢?
1.在不属于自己的代码中使用访问控制,你无法删除这些限制。比如Apple的API,它限制你可以做什么和不能做什么,你必须遵守这些限制。
2.访问控制可以控制其他人如何看待我们的代码,例如,我提供了一个结构体给你使用,它有30个公共属性和方法,你可能不确定可以使用,哪些仅供内部使用,如果我将25个标记为私有,那么,你不能在外部使用它们。

13.总结

1.你可以使用结构体创建自己的类型,结构体可以具有自己的属性和方法。
2.你可以使用存储属性或使用计算属性。
3.如果方法要更改结构体内的属性,必须将其标记为mutating
4.初始化方法是结构体的特殊方法。默认情况,你可以获得一个成员初始化方法,创建自己的初始化方法时,必须为所有属性赋一个值。
5.在结构体内,可以使用self来访问结构体的当前实例.
6.lazy关键字告诉Swift在属性第一次使用时,再进行创建。
7.使用static关键字可以在所有结构体的实例之间共享属性和方法。
8.访问控制可以限制你使用某些属性和方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349