结构体【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.访问控制可以限制你使用某些属性和方法。