Swift 入门/Swift 进阶
// basic syntax
let add = {(int1: Int, int2: Int) -> Int in return int1 + int2}
var result = add(20,5) // 25
如果闭包被分配给函数类型,则可以使用简写语法,因为 Swift 可以从上下文中推断数据类型。在最简短的版本中,您只需要告诉 Swift 如何处理参数。$0
是第一个传入参数的简写语法,$1
是第二个参数的简写语法,依此类推。
let subtract: (Int, Int) -> Int = { int1, int2 in return int1 - int2 }
let multiply: (Int, Int) -> Int = { $0 * $1 }
result = subtract(20,5) // 15
result = multiply(20,5) // 100
闭包可以作为参数传递给函数。尾随闭包可以用作函数的最后一个参数。它不是写在函数的括号内,而是写在括号之后。在下面的代码片段中,您可以看到如何将闭包传递给 Swift 的 map 函数。map
函数可以作用于数组,然后对数组的每个项目执行闭包。在此示例中,map 函数检查年份是否为闰年。所有年份都转换为字符串,如果满足先决条件,则附加“是闰年”。
// create an Array with values from 1950 to 2020
var years = [Int]()
for year in stride(from: 1950, to: 2020, by: 1){
years.append(year)
}
let leapYears = years.map{ (year) -> String in
var output = ""
let year = year
if(year%400 == 0){
output.append(String(year)+" is a leap year")
}
else if(year % 100 != 0 && year % 4 == 0){
output.append(String(year) + " is a leap year")
}
else{
output.append(String(year) + " is not a leap year")
}
return output
}
for year in leapYears{
print(year)
}
在 Swift 中,主要有两种类型的属性。存储属性用于存储与类或结构关联的变量和常量的值。计算属性不用于存储,而是用于计算值。
这些属性是类或结构实例的一部分。它可以具有变量或常量值。
struct Animal{
let name: String
let legs: Int
var weight: Double
}
var sloth = Animal(name: "slothy", legs: 4, weight: 8.5)
print("Hi my name is \(sloth.name) and i have \(sloth.weight) kilos!")
sloth.weight = 9.2
print("Put one some weight... now i have \(sloth.weight) kilos :) !")
这些属性提供 getter 和 setter 来检索或设置变量的值。在此示例中,结构Circle
有一个名为 diameter 的计算属性。它有一个 getter,返回 radius 的两倍值。setter 将 radius 的值更改为新diameter
的一半。area
是一个只读属性,这意味着它没有 setter。
struct Circle {
var radius: Double
var diameter: Double{
get {
let diameter = radius * 2
return diameter
}
set(newDiameter){
radius = newDiameter/2
}
}
var area: Double{
get{
return radius * radius * Double.pi
}
}
}
var smallCircle = Circle(radius: 3)
let initialDiameter = smallCircle.diameter
smallCircle.diameter = 10
print("The circle now has a diameter of \(smallCircle.diameter), a radius of \(smallCircle.radius) and a area of \(smallCircle.area)")
// prints "The circle now has a diameter of 10.0, a radius of 5.0 and a area of 78.53"
属性观察器可用于观察属性的状态并响应更改。每当属性的值被设置时,就会调用它们。观察器有两种类型。willSet
在存储值之前被调用,didSet
在存储值之后被调用。
class EctsCounter{
var ectsCount: Int = 0{
willSet(newCount){
print("About to set your count to \(newCount) points!")
}
didSet{
print("Added \(ectsCount - oldValue) points!")
}
}
}
let counter = EctsCounter()
counter.ectsCount += 10
// About to set your count to 10 points!
// Added 10 points!
counter.ectsCount += 4
// About to set your count to 14 points!
// Added 4 points!
调度框架包含许多语言特性、运行时库和系统增强功能,这些增强功能提高了对在具有多个内核的硬件上并发代码执行的支持。Grand Central Dispatch (GCD) 将工作提交到系统管理的调度队列。
并行是描述在多核处理器上同时执行两个或多个线程的概念的术语。具有单个内核的设备可以通过时间切片实现并发。这是通过上下文切换在多个线程之间切换的过程。GCD 管理一个线程池。代码块可以添加到调度队列中,GCD 决定执行什么。
DispatchQueue 代表 GCD 提供的调度队列[1]。您提交到队列的任务按照 FIFO 顺序执行,这意味着第一个提交的任务将始终是第一个开始的任务。队列有两种类型。串行队列在任何时间只能执行一个任务。并发队列可以同时启动多个任务。它们按照添加的顺序启动,可以以任何顺序完成。调度由 GCD 管理,这意味着它控制任务何时启动。GCD 提供一个主队列,它是一个串行队列,并在主线程上运行。主队列上的任务会立即执行。将所有更改或更新 UI 的任务都放在主队列上是一个好习惯。这确保了 UI 始终具有响应能力。四个全局队列是并发的,由整个系统共享,并分为优先级 - 高、默认、低和后台。队列也可以由用户创建,并且可以是串行或并发。
优先级不是直接指定的,而是通过使用服务质量 (QoS) 类指定的。
.user-interactive
描述必须立即完成的任务,否则用户体验会很糟糕。此 QoS 用于 UI 更新和事件处理。
.user-initiated
表示可以异步执行并从 UI 启动的任务。这些任务被映射到高优先级队列,因为它用于用户等待立即结果或任务需要用户交互才能继续时。
.utility
表示长时间运行的任务。此类用于 I/O、网络和计算。通常使用进度指示器来确保用户知道正在发生的事情。
.background
描述了用户通常意识不到的任务。它用于不需要用户交互且不紧急的任务,例如维护或预取。
以下代码片段可以在 Github 上找到,并且可以作为 Xcode 项目导入。一旦按下“开始”按钮,函数 download(size: Int, label: UILabel, timeout: Int)
就会被调用两次,并使用不同的输入参数。在将它们放入全局队列后,它们会异步执行模拟。每次迭代后,数量都会增加 1。接下来,需要更新显示下载进度的标签。为此,需要将任务放回主队列,以确保其立即执行。短暂超时后,下一轮迭代开始。
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var task1: UILabel!
@IBOutlet weak var task2: UILabel!
@IBAction func start(_ sender: UIButton) {
download(size: 70, label: task1, timeout: 10)
download(size: 50, label: task2, timeout: 7)
}
func download(size: Int, label: UILabel, timeout: Int) -> Void{
DispatchQueue.global(qos: .userInitiated).async {
// puts the download simulation on a global queue
var amount = 0
for _ in stride(from: 0, to: size, by: 1){
amount += 1
DispatchQueue.main.async {
/* All actions which change the UI have to be put back on the main queue. */
label.text = String(amount)
}
// sets a timeout
usleep(useconds_t(timeout*10000))
}
}
}
}
在代码执行过程中,有很多情况下可能会发生错误。例如,当尝试从硬盘读取文件时,由于权限不足或文件不存在,可能会发生错误。Swift 提供了几种[2]处理代码执行期间错误的方法。
- 函数中的错误可以传播到调用该函数的代码。
- do-catch 语句
- 可选值
- 断言错误不会发生
关键字 throws
用于函数声明中参数列表之后。此函数被称为“抛出函数”。在下面的示例中,使用枚举定义了两种可能的错误类型。每当调用 makeCoffee()
函数时,机器中的咖啡豆数量就会减少,并且计数器会增加。一旦咖啡豆用完,就会抛出 outOfBeans
错误。如果已经服务了特定数量的杯子,则会抛出 needsMaintenance
错误。
enum CoffeeMachineError: Error {
case outOfBeans
case needsMaintenance
}
class CoffeeMachine{
var beans = 20
var count = 1
func makeCoffee() throws{
if(count < 6){
if(beans > 0){
print("Enjoy your cup of coffee!")
beans -= 1
count += 1
} else{
throw CoffeeMachineError.outOfBeans
}
} else{
throw CoffeeMachineError.needsMaintenance
}
}
}
var machine = CoffeeMachine()
for _ in stride(from: 0, to: 7, by: 1){
try machine.makeCoffee()
}
Do-Catch 语句用于根据抛出的错误类型执行不同的语句。例如,这里捕获了 needsMaintenance
错误,机器会提示需要维护,并将计数器重置为 0。
var coffeeMachine = CoffeeMachine()
for run in stride(from: 0, to: 25, by: 1){
do{
try coffeeMachine.makeCoffee()
} catch CoffeeMachineError.outOfBeans{
print("Out of Beans!")
} catch CoffeeMachineError.needsMaintenance{
print("Machine needs Maintenance!")
// Machine is maintained, counter gets set back to 0
coffeeMachine.count = 0
}
}
使用 try?
关键字,错误会被转换为可选值。每当在执行过程中抛出错误时,表达式的值为 nil
。在下面的代码片段中,一旦要返回的数字不再大于零,就会抛出错误。
enum DigitError: Error{
case outOfDigitsError(String)
}
var currentDigit = 9
func getDigit() throws -> Int {
if(currentDigit > 0){
let tmp = currentDigit
currentDigit -= 1
return tmp
}
else{
throw DigitError.outOfDigitsError("Sorry, no digits left...")
}
}
for _ in stride(from: 0, to: 10, by: 1){
if let digit = try? getDigit(){
print(digit)
}
}
Apple 的移动设备包含许多传感器,包括加速计、气压计、环境光传感器和计步器。iOS 开发人员可以使用 Swift 在他们的项目中访问这些传感器数据。
例如,下面的代码片段显示了如何访问计步器以及如何检索数据[3]。例如,此传感器用于统计人的步数。此项目可以从 GitHub 下载。
import UIKit
import CoreMotion
@IBDesignable
class ViewController: UIViewController {
@IBOutlet weak var stepsDisplay: UILabel!
@IBOutlet weak var stepsToComplete: UILabel!
let calendar = Calendar.current
let todayDate = Date()
var stepsToday: Int = 0
let pedometer = CMPedometer()
@IBInspectable
var targetSteps = 10000
func getStartOfDay(from date: Date) -> Date{
return Calendar.current.startOfDay(for: date)
}
func handler (_ data: CMPedometerData?, _ error: Error?) -> Void{
let steps = data?.numberOfSteps
stepsToday = steps as! Int
DispatchQueue.main.async(execute: {
// puts the closure into the Main Queue so it will be executed immediatly
self.stepsDisplay.text = String(self.stepsToday)
self.stepsToComplete.text = String(self.getStepsToGoal(target: self.targetSteps, actual: self.stepsToday))
})
}
func getStepsToGoal(target steps: Int, actual count: Int) -> Int{
return steps - count
}
@IBAction func getSteps(_ sender: UIButton){
if CMMotionActivityManager.isActivityAvailable(){
//checks if Activity Data is available
pedometer.queryPedometerData(from: getStartOfDay(from: todayDate), to: todayDate, withHandler: handler)
//queries data from the pedometer.
}
}
}
在本节中,我们将了解如何在 Swift 中实现简单的单元测试[4]。为此,我们将测试一个包含三个函数的简单类。该类包含两个整数值的变量以及三个可以对这些值进行加、减或乘运算的函数。
import Foundation
class Calculator {
var a: Int
var b: Int
init(a:Int, b:Int){
self.a = a
self.b = b
}
func add(a:Int, b:Int) -> Int {
return a + b
}
func sub(a:Int, b:Int) -> Int {
return a - b
}
func mul(a:Int, b:Int) -> Int {
return a * b
}
}
接下来,让我们看看如何测试这个类。首先,我们必须导入 XCTest
测试框架和 Calculator。在测试函数 testAdd()
、testSub()
和 testMul()
中,使用 Calculator 类的实例来调用 add、subtract 和 multiply 方法,并将结果与预期值进行比较。
import XCTest
@testable import Calculator
class CalculatorTests: XCTestCase {
let calc = Calculator(a:0, b:0)
override func setUp() {
// called before every test method
super.setUp()
}
override func tearDown() {
// called at the end of every test method
super.tearDown()
}
func testAdd() {
XCTAssertEqual(calc.add(a: 1, b: 1), 2)
XCTAssertEqual(calc.add(a: 1, b: 2), 3)
XCTAssertEqual(calc.add(a: 5, b: 4), 9)
}
func testSub(){
XCTAssertEqual(calc.sub(a: 5, b: 2), 3)
XCTAssertEqual(calc.sub(a: 3, b: 3), 0)
XCTAssertEqual(calc.sub(a: 6, b: 7), -1)
}
func testMul(){
XCTAssertEqual(calc.mul(a: 2, b: 4), 8)
XCTAssertEqual(calc.mul(a: 9, b: 9), 81)
XCTAssertEqual(calc.mul(a: 0, b: 4), 0)
}
}
- ↑ Kodeco | 2017 | Grand Central Dispatch 教程 | [在线][访问日期:2017 年 9 月 18 日] | https://www.kodeco.com/5370-grand-central-dispatch-tutorial-for-swift-4-part-1-2
- ↑ Apple Inc. | 2017 | 错误处理 | [在线][访问日期:2017 年 9 月 18 日] | https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID508
- ↑ Apple Inc. | 2017 | [在线][访问日期:2017 年 9 月 18 日] | https://developer.apple.com/documentation/coremotion/cmpedometer
- ↑ Nolan, G. (2017), Agile Swift: 使用敏捷工具和技术的 Swift 编程,Springer Science+Business Media New York,纽约