-
六、第六部分:Structures
-
1.Structures(结构体)
举个栗子
let restaurantLocation = (2, 4)
let restaurantRange = 2.5
// Pythagorean Theorem # $
func distance(from source: (x: Int, y: Int), to target: (x: Int, y: Int))
-> Double {
let distanceX = Double(source.x - target.x)
let distanceY = Double(source.y - target.y)
return sqrt(distanceX * distanceX + distanceY * distanceY)
}
func isInDeliveryRange(location: (x: Int, y: Int)) -> Bool {
let deliveryDistance = distance(from: location, to: restaurantLocation)
return deliveryDistance < restaurantRange
}
let restaurantLocation = (2, 4)
let restaurantRange = 2.5
let otherRestaurantLocation = (7, 8)
let otherRestaurantRange = 1.5
// Pythagorean Theorem # $
func distance(from source: (x: Int, y: Int), to target: (x: Int, y: Int))
-> Double {
let distanceX = Double(source.x - target.x)
let distanceY = Double(source.y - target.y)
return sqrt(distanceX * distanceX + distanceY * distanceY)
}
func isInDeliveryRange(location: (x: Int, y: Int)) -> Bool {
let deliveryDistance =
distance(from: location,
to: restaurantLocation)
let secondDeliveryDistance =
distance(from: location,
to: otherRestaurantLocation)
return deliveryDistance < restaurantRange ||secondDeliveryDistance < otherRestaurantRange
}
-
Your first structure
struct Location {
let x: Int
let y: Int
}
-
Your second structure
struct DeliveryArea {
var range: Double
let center: Location
}
-
完整示例:
struct Location {
let x: Int
let y: Int
}
struct DeliveryArea {
var range: Double
let center: Location
}
func distance(from source: (x: Int, y: Int), to target: (x: Int, y: Int))
-> Double {
let distanceX = Double(source.x - target.x)
let distanceY = Double(source.y - target.y)
return sqrt(distanceX * distanceX + distanceY * distanceY)
}
let areas = [
DeliveryArea(range: 2.5, center: Location(x: 2, y: 4)),
DeliveryArea(range: 4.5, center: Location(x: 9, y: 7))
]
func isInDeliveryRange(_ location: Location) -> Bool {
for area in areas {
let distanceToStore =
distance(from: (area.center.x, area.center.y),
to: (location.x, location.y))
if distanceToStore < area.range {
return true
}
}
return false
}
let customerLocation1 = Location(x: 8, y: 1)
let customerLocation2 = Location(x: 5, y: 5)
print(isInDeliveryRange(customerLocation1)) // false
print(isInDeliveryRange(customerLocation2)) // true
-
七、第七部分:Properties
1.stored properties(存储属性)
struct Contact {
var fullName: String
var emailAddress: String
}
var person = Contact(fullName: "Grace Murray",
emailAddress: "[email protected]")
let name = person.fullName // Grace Murray
let email = person.emailAddress // [email protected]
-
Default values
struct Contact {
var fullName: String
var emailAddress: String
var type = "Friend"
}
-
2.Computed properties(计算属性)
-
Getter,举个栗子
struct TV {
var height: Double // 存储属性
var width: Double // 存储属性
// 1
var diagonal: Int { // 计算属性
// 2
let result = sqrt(height * height + width * width)
// 3
let roundedResult = result.rounded()
// 4
return Int(roundedResult)
}
}
var tv = TV(height: 53.93, width: 95.87)
let size = tv.diagonal // 110
tv.width = tv.height // 改变width,diagonal通过计算发生变化
let diagonal = tv.diagonal // 76
-
Getter and setter
var diagonal: Int {
// 1
get { // 2
let result = sqrt(height * height + width * width)
let roundedResult = result.rounded(.toNearestOrAwayFromZero)
return Int(roundedResult)
} set {
// 3
let ratioWidth = 16.0
let ratioHeight = 9.0
// 4
height = Double(newValue) * ratioHeight /
sqrt(ratioWidth * ratioWidth + ratioHeight * ratioHeight)
width = height * ratioWidth / ratioHeight
}
}
tv.diagonal = 70 // 通过设置tv.diagonal,自动获得tv.height和tv.width
let height = tv.height // 34.32...
let width = tv.width // 61.01...
- ###说明:
1。因为你想要包括一个setter,你现在必须明确哪些计算组成的getter和setter,所以你周围每个代码块和花括号之前得到或设置。这不是只读属性,计算所需的单一代码块隐式getter。
2。您可以使用与前面相同的代码来获得计算值。
3。对于setter,你通常需要做出这样的假设。在这种情况下,您为屏幕比提供了一个合理的默认值。
4。考虑到一个高和宽,给定一个对角和一个比,面积有点深。你可以用一点时间来解决它们,但我已经为你做了一些脏活,并在这里提供了它们。关注的重点是:
- newValue常量允许您使用在赋值期间传递的任何值。
- 记住,对角线是一个整数,所以在计算中使用它时,你必须先把它转换成双精度。
- 一旦你做了计算,你就会分配电视结构的高度和宽度。
-
Type properties(类型属性)
struct Level {
static var highestLevel = 1 // 类型属性
let id: Int
var boss: String
var unlocked: Bool
}
let level1 = Level(id: 1, boss: "Chameleon", unlocked: true)
let level2 = Level(id: 2, boss: "Squid", unlocked: false)
let level3 = Level(id: 3, boss: "Chupacabra", unlocked: false)
let level4 = Level(id: 4, boss: "Yeti", unlocked: false)
-
Property observers(属性观察器)
struct Level {
static var highestLevel = 1 // 类型属性
let id: Int
var boss: String
var unlocked: Bool {
didSet {
if unlocked && id > Level.highestLevel {
Level.highestLevel = id
}
}
}
}
现在,当玩家解锁一个新级别时,如果级别是新的高度,它将更新highestLevel类型属性。这里有几件事需要注意:
您可以从didSet观察器中访问未锁定的值。请记住,在设置了值之后,didSet就会被调用。
即使您是在类型的一个实例中,您仍然需要访问类型属性,它们的全名都是级别的。高水平的,而不是仅仅是高水平的。
另外,请记住,在初始化期间设置属性时,不会调用willSet和didSet观察器;只有当您为完全初始化的实例分配一个新值时,才会调用它们。这意味着属性观察器只对可变属性有用,因为常量属性只在初始化期间设置。
-
Limiting a variable(限制一个变量)
struct LightBulb {
static let maxCurrent = 40
var current = 0 {
didSet {
if current > LightBulb.maxCurrent {
print("Current too high, falling back to previous setting.")
current = oldValue
}
}
}
}
- ###测试:
var light = LightBulb()
light.current = 50
var current = light.current // 0
light.current = 40
current = light.current // 40
// 你试着把灯泡设置为50安培,但是灯泡拒绝了输入。很酷!
-
Lazy properties(懒加载属性
struct Circle {
// 这里,您不相信从标准库中可以获得的pi值;您需要自己计算它
lazy var pi = {
return ((4.0 * atan(1.0 / 5.0)) - atan(1.0 / 239.0)) * 4.0
}()
var radius = 0.0
var circumference: Double {
mutating get {
return pi * radius * 2
}
}
init (radius: Double) {
self.radius = radius
}
}
// 您可以使用它的初始化器创建一个新的圆,而pi计算将不会运行:
var circle = Circle(radius: 5) // got a circle, pi has not been run
let circumference = circle.circumference // 31.42 // also, pi now has a value
在前一章中,我们学习了属性,它们是常量和变量,它们是结构的一部分。方法,正如您已经看到的,仅仅是驻留在结构中的函数。
在本章中,您将更深入地了解方法和初始化器。与属性一样,您将开始设计更复杂的结构。在本章中学习的内容将适用于所有命名类型的方法,包括类和枚举,您将在后面的章节中看到。
-
八、第八部分:Methods
通过计算属性,您可以在最后一章看到,您可以从一个结构中运行代码。这听起来很像一种方法。有什么区别呢?这确实取决于风格,但有一些有用的想法可以帮助你做出决定。属性保存您可以得到和设置的值,而方法执行工作。有时,当方法的唯一目的是返回一个值时,这种区别就会变得模糊。
问问自己,你是否想要能够设定一个价值,并获得价值。计算属性可以在内部设置一个setter组件来编写值。另一个需要考虑的问题是,计算是否需要进行大量的计算或从数据库中读取数据。即使是一个简单的值,一个方法也可以帮助您向未来的开发人员指出,这个调用在时间和计算资源方面是昂贵的。如果调用很便宜,就使用计算属性。
Turning a function into a method(将一个函数转换为一个方法)
let months = ["January", "February", "March",
"April", "May", "June",
"July", "August", "September",
"October", "November", "December"]
struct SimpleDate {
var month: String
}
func monthsUntilWinterBreak(from date: SimpleDate) -> Int {
return months.index(of: "December")! - months.index(of: date.month)!
}
print(monthsUntilWinterBreak(from:SimpleDate(month: "May"))) // 7
如果把方法定义在Struct中会不会更容易理解呢
struct SimpleDate {
var month: String
func monthsUntilWinterBreak1(from date: SimpleDate) -> Int {
return months.index(of: "December")! - months.index(of: date.month)!
}
// 推荐写法
func monthsUntilWinterBreak() -> Int {
return months.index(of: "December")! - months.index(of: self.month)!
}
}
let date = SimpleDate(month: "October")
let monthsLeft = date.monthsUntilWinterBreak(from: date) // 2
-
mutating methods()
结构中的方法不能改变实例的值,而不被标记为“ mutating”
。变异关键字标记了一个改变结构值的方法。由于一个结构是一个值类型,每次它在一个应用程序中传递时,系统就会复制它。如果一个方法改变了其中一个属性的值,那么原来的实例和复制的实例就不再是等价的了。
通过将一个方法标记为mutating
,您告诉快速编译器这个方法不应该在常量上调用。这就是Swift知道在编译时允许哪些方法和拒绝哪些方法。如果您在一个常量结构上调用一个mutating
方法,编译器会将其标记为一个错误,在您运行程序之前必须纠正这个错误。
struct SimpleDate {
var month: String
var day: Int
init(month: String, day: Int) {
self.month = month
self.day = day
}
mutating func advance() {
day += 1 // 没有加mutating修饰func的话,会编译错误,不能在结构体提直接修改day的值
}
}
var current = SimpleDate(month: "December", day: 30)
current.advance()
let currentMonth = current.month // December; should be January!
let currentDay = current.day // 31;
-
Type methods(类方法)
与类型属性一样,您可以使用类型方法来访问所有实例中的数据。您在类型本身上调用类型方法,而不是在实例上调用。要定义类型方法,您需要使用静态修饰符来对其进行前缀。
类型方法对于一般情况下的类型是有用的,而不是特定的实例。
struct Math {
// 1 类方法,关键字static修饰
static func factorial(of number: Int) -> Int {
// 2
return (1...number).reduce(1, *)
}
}
// 3
let factorial = Math.factorial(of: 6) // 720
-
extensions(扩展)
struct SimpleDate {
var month: String
var day: Int
}
extension SimpleDate {
init() {
month = "March"
day = 1
}
}
let defaultDay = SimpleDate()
let childrensDay = SimpleDate(month: "May", day: 5)
您学习了结构体,它是一个命名类型,它允许您定义自己的类型。在本章中,您将熟悉类,它们与结构非常相似,它们被命名为类型,具有属性,并且可以定义方法。
但是,您将学习的类是引用类型,而不是值类型,它们的功能和优点与它们的结构相比具有很大的不同。虽然您经常在应用程序中使用结构来表示值,但通常使用类来表示对象。
-
九、第九部分:Classes
-
结构体和类的区别
- Reference types(引用类型)class
-
class Person {
let name: String
init(name: String) {
self.name = name
}
}
var var1 = Person(name: "John")
如果您要创建一个新的变量var2并将其赋值为var1的值:
var var2 = var1
-
value types(值类型)struct
struct Person {
let name: String
}
var1 = Person(name: "John")
如果您要创建一个新的变量var2并将其赋值为var1的值,然后,var1的值将被复制到var2:
var var2 = var1
-
help(帮助理解)
当您创建诸如类之类的引用类型时,系统将实际的实例存储在称为堆的内存区域中。一个值类型的实例,例如一个结构体,驻留在一个名为堆栈的内存区域中,除非该值是类实例的一部分,在这种情况下,该值被存储在类实例的其余部分中。
堆和堆栈都在执行任何程序时都具有重要的作用。对它们是什么以及它们的工作原理有一个大致的了解,可以帮助你理解类和结构之间的功能差异:
该系统使用堆栈来存储执行的即时线程上的任何内容;它受到CPU的严格管理和优化。当函数创建一个变量时,堆栈会存储该变量,然后在函数退出时销毁它。因为这个堆栈组织得很好,所以它非常高效,而且非常快。
当您创建一个类的实例时,您的代码请求堆上的一个内存块来存储实例本身;这是图表右侧的实例的第一个名称和最后一个名称。它将该内存的地址存储在堆栈的命名变量中;这是存储在图左侧的引用。
当您创建一个struct时,值本身就存储在堆栈中,而堆永远不会涉及到。
-
示例代码
值类型的工作
struct Location {
var x: CGFloat
var y: CGFloat
}
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 不会变为4.0
引用类型
class 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) // 4.0 不会再是2.5
-
Object identity(OC中的isEqual:)
在Swift中,===操作符让您检查一个对象的标识是否等于另一个对象的标识:
-
Preventing inheritance防止继承(final关键字)
防止类继承
class Person {
//...
}
final class Student: Person {
//...
}
// Build error!
class StudentAthlete: Student {
//...
}
防止方法重写
class Student: Person {
final func recordGrade(_ grade: Grade) {
//...
} }
class StudentAthlete: Student {
// Build error!
override func recordGrade(_ grade: Grade) {
//...
}
}
-
Required and convenience initializers
-
Required(必须实现)
-
class Student {
let firstName: String
let lastName: String
required init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
//... 创建Student对象必须实现 required init(firstName: String, lastName: String),否则编译错误
}
Student(firstName: "留", lastName: "明")
-
convenience(便利构造)
class Student {
convenience init(transfer: Student) {
self.init(firstName: transfer.firstName, lastName: transfer.lastName)
}
}
十、第十部分:Enumerations
e.g:举个例子
enum Month {
case january
case february
case march
case april
case may
case june
case july
case august
case september
case october
case november
case december
}
func semester(for month: Month) -> String {
switch month {
case Month.august, Month.september, Month.october,
Month.november, Month.december:
return "Autumn"
case Month.january, Month.february, Month.march, Month.april,
Month.may:
return "Spring"
default:
return "Not in the school year"
}
}
-
Raw values
enum Month: Int {
case january = 1, february = 2, march = 3, april = 4, may = 5,
june = 6, july = 7, august = 8, september = 9,
october = 10, november = 11, december = 12
}
// 第一个赋值即可,后面的赋值可以省略,自动+1
enum Month: Int {
case january = 1, february, march, april, may, june, july,
august, september, october, november, december
}
func monthsUntilWinterBreak(from month: Month) -> Int {
return Month.december.rawValue - month.rawValue
}
monthsUntilWinterBreak(from: .april) // 8
-
Initializing with the raw value
let fifthMonth = Month(rawValue: 5)
monthsUntilWinterBreak(from: fifthMonth) // Error: value not unwrapped,可选值,必须明确类型解包
monthsUntilWinterBreak(from: fifthMonth!) // Error: value not
let fifthMonth = Month(rawValue: 5)!
monthsUntilWinterBreak(from: fifthMonth) // 7
-
String raw values
// 1
enum Icon: String {
case music
case sports
case weather
var filename: String {
// 2
return "\(rawValue.capitalized).png"
}
}
let icon = Icon.weather
icon.filename // Weather.png
icon.rawValue // Weather
icon.hashValue // 2
-
Enumeration as state machine
交通灯
enum TrafficLight {
case red, yellow, green
}
let trafficLight = TrafficLight.red
轻轻点击,关注我
轻轻点击,关注我
轻轻点击,关注我微博
浏览我的GitHub
扫一扫,关注我