首页 iOS.& Swift Books Swift学徒

13
班级 由cosminpupăză撰写

结构将您推荐给命名类型在本章中,你会熟悉 班级这与结构很像 - 它们是具有属性和方法的命名类型。

你会学习课程 参考 类型,而不是 价值 类型,并且具有比其结构对应物的基本不同的能力和益处。虽然您通常会在应用程序中使用结构来表示值,但通常使用类来表示要表示 对象.

什么是 价值 vs. 对象 really mean, though?

创建课程

考虑Swift中的以下类定义:

class Person {
  var firstName: String
  var lastName: String

  init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }

  var fullName: String {
    "\(firstName) \(lastName)"
  }
}

let john = Person(firstName: "Johnny", lastName: "Appleseed")

That’s simple enough! It may surprise you that the definition is almost identical to its struct counterpart. The keyword class 是 followed by the name of the class, and everything in the curly braces is a member of that class.

But you can also see some differences between a class and a struct: The class above defines an initializer that sets both firstName and lastName to initial values. Unlike a struct, a class doesn’t provide a memberwise initializer automatically — which means you must provide it yourself if you need it. If you forget to provide an initializer, the Swift compiler will flag that as an error:

Default initialization aside, the initialization rules for classes and structs are very similar. Class initializers are functions marked init, and all stored properties must be assigned initial values before the end of init.

很多 更多到初始化类,但您必须等到第14章“高级类”,这将介绍概念 遗产 及其对初始化规则的影响。本章将坚持基本类初始化器,以便您可以在Swift中携带课程。

参考类型

在SWIFT中,结构的实例是一种不可变的值,而类的实例是可变对象。类是引用类型,因此类类型的变量不存储实际实例 - 它存储一个 参考 到存储实例的内存中的位置

class SimplePerson {
  let name: String
  init(name: String) {
    self.name = name
  }
}

var var1 = SimplePerson(name: "John")

var var2 = var1

struct SimplePerson {
  let name: String
}

堆与堆栈

创建诸如类的引用类型时,系统将实际实例存储在已知的内存区域中 。作为结构的值类型的实例驻留在一个名为“的内存区域”中 ,除非该值是类实例的一部分,在这种情况下,该值将存储在堆上与类实例的其余部分存储在堆上。

使用参考资料

In Chapter 10, “Structures”, you saw the copy semantics involved when working with structures and other value types. Here’s a little reminder, using the Location and DeliveryArea structures from that chapter:

struct Location {
  let x: Int
  let y: Int
}

struct DeliveryArea {
  var range: Double
  let center: Location
}

var area1 = DeliveryArea(range: 2.5,
                         center: Location(x: 2, y: 4))
var area2 = area1
print(area1.range) // 2.5
print(area2.range) // 2.5

area1.range = 4
print(area1.range) // 4.0
print(area2.range) // 2.5
var homeOwner = john
john.firstName = "John" // John wants to use his short name!
john.firstName // "John"
homeOwner.firstName // "John"

迷你练习

Change the value of lastName on homeOwner, then try reading fullName on both john and homeOwner. What do you observe?

对象标识

In the previous code sample, it’s easy to see that john and homeOwner are pointing to the same object. The code is short and both references are named variables. What if you want to see if the value behind a variable John?

john === homeOwner // true
let imposterJohn = Person(firstName: "Johnny", 
                          lastName: "Appleseed")

john === homeOwner // true
john === imposterJohn // false
imposterJohn === homeOwner // false

// Assignment of existing variables changes the instances the variables reference.
homeOwner = imposterJohn
john === homeOwner // false

homeOwner = john
john === homeOwner // true
// Create fake, imposter Johns. Use === to see if any of these imposters are our real John.
var imposters = (0...100).map { _ in
  Person(firstName: "John", lastName: "Appleseed")
}

// Equality (==) is not effective when John cannot be identified by his name alone
imposters.contains {
  $0.firstName == john.firstName && $0.lastName == john.lastName
} // true
// Check to ensure the real John is not found among the imposters.
imposters.contains {
  $0 === john
} // false

// Now hide the "real" John somewhere among the imposters.
imposters.insert(john, at: Int.random(in: 0..<100))

// John can now be found among the imposters.
imposters.contains {
  $0 === john
} // true

// Since `Person` is a reference type, you can use === to grab the real John out of the list of imposters and modify the value.
// The original `john` variable will print the new last name!
if let indexOfJohn = imposters.firstIndex(where: 
                                          { $0 === john }) {
  imposters[indexOfJohn].lastName = "Bananapeel"
}

john.fullName // John Bananapeel

迷你练习

Write a function memberOf(person: Person, group: [Person]) -> Bool that will return true if person can be found inside group, and false if it can not.

方法和可变性

如前所述,类的实例是可变对象,而结构的实例是不可变的值。以下示例说明了这种差异:

struct Grade {
  let letter: String
  let points: Double
  let credits: Double
}

class Student {
  var firstName: String
  var lastName: String
  var grades: [Grade] = []

  init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }

  func recordGrade(_ grade: Grade) {
    grades.append(grade)
  }
}

let jane = Student(firstName: "Jane", lastName: "Appleseed")
let history = Grade(letter: "B", points: 9.0, credits: 3.0)
var math = Grade(letter: "A", points: 16.0, credits: 4.0)

jane.recordGrade(history)
jane.recordGrade(math)

可变性和常数

The previous example may have had you wondering how you were able to modify jane even though it was defined as a constant. When you define a constant, the value of the constant cannot be changed. If you recall back to the discussion of value types vs reference types, it’s important to remember that, with reference types, the value is a 参考.

// Error: jane is a `let` constant
jane = Student(firstName: "John", lastName: "Appleseed")
var jane = Student(firstName: "Jane", lastName: "Appleseed")
jane = Student(firstName: "John", lastName: "Appleseed")

迷你练习

Add a computed property to Student that returns the student’s Grade Point Average, or GPA. A GPA is defined as the number of points earned divided by the number of credits taken. For the example above, Jane earned (9 + 16 = 25拍摄时的点(3 + 4 = 7)学分,让她的GPA(25/7 = 3.57)。

了解状态和副作用

由于类的本质是它们都被引用和变形,因此有许多可能性 - 以及程序员的许多问题。请记住:如果使用新值更新类实例,每个实例的每个引用也将看到新值。

var credits = 0.0
func recordGrade(_ grade: Grade) {
  grades.append(grade)
  credits += grade.credits
}
jane.credits // 7

// The teacher made a mistake; math has 5 credits
math = Grade(letter: "A", points: 20.0, credits: 5.0)
jane.recordGrade(math)

jane.credits // 12, not 8!

使用扩展扩展课程

当你用structs看到的时候,课程可以 重新开业 using the extension keyword to add methods and computed properties. Add a fullName computed property to Student:

extension Student {
  var fullName: String {
    "\(firstName) \(lastName)"
  }
}

何时使用类与结构

既然你知道班级和结构之间的差异和相似之处,你可能会想知道“我怎么知道哪个使用?”

值与对象

虽然没有艰难的规则,但您应该考虑值与参考语义,并使用结构 价值 和课程 具有身份的对象.

速度

速度考虑是一件事,因为结构依赖于更快的堆栈,而课程依赖于较慢的堆。如果您有更多的实例(数百和更大),或者如果这些实例只在内存中存在短时间 - 倾向于使用结构。如果您的实例在内存中具有更长的生命周期,或者如果您将创建相对较少的实例,则堆上的类实例不应该创建太多开销。

极简主义方法

另一种方法是仅使用您需要的东西。如果您的数据永远不会更改或需要一个简单的数据存储,则使用结构。如果您需要更新数据,并且需要它包含逻辑以更新其自己的状态,请使用类。通常,最好从结构开始。如果您在稍后需要班级的额外情况,那么您只需将struct转换为类。

结构与课程RECAP

结构

  • 有用的代表值。
  • 隐式复制值。
  • Becomes completely immutable when declared with let.
  • 快速内存分配(堆栈)。

班级

  • 用于代表具有身份的对象
  • 隐式共享对象。
  • Internals can remain mutable even when declared with let.
  • 较慢的内存分配(堆)。

挑战

在继续前进之前,这里有一些挑战来测试您的课程知识。如果您尝试自己解决这些问题,最好是,如果您陷入困境,则可以使用解决方案。这些随下载或在介绍中列出的印刷书的源代码链接中提供。

挑战1:电影列表

Imagine you’re writing a movie-viewing app in Swift. Users can create lists of movies and share those lists with other users. Create a User and a List class that uses reference semantics to help maintain lists between users.

挑战2:T恤商店

您的挑战是建立一组对象来支持T恤商店。确定每个对象是否应该是类或结构,为什么。

关键点

  • 像结构一样, 班级 是一个可以具有属性和方法的命名类型。
  • 课程使用 参考 这是在分配上共享的。
  • 调用类实例 对象.
  • 对象是 可变的.
  • 可变性推出 状态,在管理对象时增加了复杂性
  • 当您想要时使用类 参考语义;结构 价值语义.

有一个技术问题?想报告一个错误吗? 您可以在官方书籍论坛中向本书作者提出问题和报告错误 这里.

有反馈分享休息经验吗? 如果您有关于UI,UX,突出显示或我们在线阅读器的其他功能的反馈,您可以将其发送到设计团队,其中表格如下所示:

© 2021 Razeware LLC

您可以免费读取,本章的部分显示为 混淆了 文本。解锁这本书,以及我们整个书籍和视频目录,带有Raywenderlich.com的专业订阅。

现在解锁

要突出或记笔记,您需要在订阅或购买本书。