swift基本语法
YES
- 2014WWDC发布
常量和变量使用注意
- 在实际过程中,建议先定义常量,如果需要修改再改变为变量(更加安全)
- 可以在一行中声明多个常量或者多个变量,用逗号隔开
var x = 0.0, y = 0.0, z = 0.0
var red, green, blue: Double
类型标注
- 如果要添加类型标注,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称
- 如果你需要使用与Swift保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方式将其作为名字使用。无论如何,你应当避免使用关键字作为常量或变量名,除非你别无选择
整数范围
- 你可以访问不同整数类型的 min 和 max 属性来获取对应类型的最小值和最大值:
- 即使是在32位平台上,Int 可以存储的整数范围也可以达到 -2,147,483,648 ~ 2,147,483,647 ,大多数时候这已经足够大了
浮点数
- Double精确度很高,至少有15位数字,而Float只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围,在两种类型都匹配的情况下,将优先选择 Double。
- 整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量:
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
- 通常来讲,即使代码中的整数常量和变量已知非负,也请使用Int类型
- 当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说 4.75 会变成 4,-3.9 会变成 -3。
类型推倒
- swift属于强类型语言,任何一个标示符都有明确的类型
- 如果定义一个标示符时有直接进行赋值,那么标示符后面的类型可以省略,因为swift有类型推导,会自动根据后面的赋值来决定前面标示符的数据类型
- option + 鼠标左键 可以查看变量类型
类型别名
- 类型别名(type aliases)就是给现有类型定义另一个名字。你可以使用typealias关键字来定义类型别名
输出常量和变量
- Swift 用字符串插值(string interpolation)的方式把常量名或者变量名当做占位符加入到长字符串中,Swift 会用当前常量或变量的值替换这些占位符。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义
基本运算
- 没有隐式转换,必须保证类型一致
逻辑分支
- if else
- if 后面的小括号可以省略
- 判断句必须有明确的真假
- 三目运算符 与OC 形同
- switch 语句
- switch后面的小括号可以省略
- case语句结束后的break也可以省略
- 如果需要穿透可以在case 语句后面跟上fallthrough
- case后面可以判断多个条件,用 逗号 分隔
- switch可以判断浮点型 字符串
- switch 可以判断区间 开区间 0..<10 不包括 10 0...10 包括10
for循环
- forin
for i in 0..<10{
print(i)
}
for _ in 0..10{
}
while 循环
var c = 10
while c > 11 {
// 自增自减?????
print("大于0")
}
dowhile
repeat {
statements
} while condition
字符串
- String 是一个结构体,性能更高
- String支持直接遍历
- swift提供了String 和 NSString之间的无缝转换
let age = 28
let info = "my name is \(name),my age is \(age)"
// 拼接字符串时的格式化
let min = 2
let sec = 3
let timestring = String.init(format: "%02d:%02d", arguments:[min,sec])
let substr = (timestring as NSString).substring(to: 3)
数组
- 创建一个空数组 var someInts = Int
- 创建一个带有默认值的数组
var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0]
- 通过两个数组相加创建一个数组 +
- 使用布尔属性isEmpty作为一个缩写形式去检查count属性是否为0:
- 也可以使用append(_:)方法在数组后面添加新的数据项
- 如果我们同时需要每个数据项的值和索引值,可以使用enumerated()方法来进行数组遍历。enumerated()返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历:
for (index, value) in shoppingList. enumerated() {
print("Item \(String(index + 1)): \(value)")
}
集合
- 集合(Set)用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组
- var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres 被构造成含有三个初始值的集合 - Swift 的Set类型没有确定的顺序,为了按照特定顺序来遍历一个Set中的值可以使用sorted()方法,它将返回一个有序数组,这个数组的元素排列顺序由操作符'<'对元素进行比较的结果来确定.
- 集合运算
- 使用intersection(_:)方法根据两个集合中都包含的值创建的一个新的集合。
- 使用symmetricDifference(_:)方法根据在一个集合中但不在两个集合中的值创建一个新的集合。
- 使用union(_:)方法根据两个集合的值创建一个新的集合。
- 使用subtracting(_:)方法根据不在该集合中的值创建一个新的集合。
let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits. intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits. symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]
let houseAnimals: Set = ["🐶", "🐱"]
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
let cityAnimals: Set = ["🐦", "🐭"]
houseAnimals.isSubset(of: farmAnimals)
// true
farmAnimals.isSuperset(of: houseAnimals)
// true
farmAnimals.isDisjoint(with: cityAnimals)
// true
字典
- 创建字典
var dict = [String : AnyObject]()
- 和数组一样,我们可以通过字典的只读属性count来获取某个字典的数据项数量
- 使用布尔属性isEmpty作为一个缩写形式去检查count属性是否为0
- ,字典的updateValue(_:forKey:)方法可以设置或者更新特定键对应的值
- 如果我们只是需要使用某个字典的键集合或者值集合来作为某个接受Array实例的 API 的参数,可以直接使用keys或者values属性构造一个新数组
let airportCodes = [String](airports.keys)
// airportCodes 是 ["YYZ", "LHR"]
let airportNames = [String](airports.values)
// airportNames 是 ["Toronto Pearson", "London Heathrow"]
- 遍历字典 forin keys values
- 遍历所有键值
for (key , value) in dictm{
}
- 合并字典:即使类型一致也不能相加合并
元组
*元组的基本写法
let info = ("lili",18,1.88)
info.0
info.1
- 给元组的每个元素起一个别名
let info = (name :"lili",age : 18,height : 1.88)
info.name
info.age
- 元组中元素的别名,就是元组的名称
let (name,age,height) = ("lili",18,1.88)
forin
- 使用 stride(from:to:by:) 函数跳过不需要的标记。
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
// 每5分钟呈现一个刻度线 (0, 5, 10, 15 ... 45, 50, 55)
}
可以在闭区间使用 stride(from:through:by:) 起到同样作用:
let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
// 每3小时呈现一个刻度线 (3, 6, 9, 12)
}
可选类型
定义可选类型
- 方法一 不常用
* var name : Optional<String> = nil
* var name : String? = nil
- 如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 nil
var surveyAnswer: String?
// surveyAnswer 被自动设置为 nil
- Swift 的 nil 和 Objective-C 中的 nil 并不一样。在 Objective-C 中,nil 是一个指向不存在对象的指针。在 Swift 中,nil 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 nil,不只是对象类型。
- 注意:强制解包是非常危险的,如果可选类型为nil,强制解包系统会崩溃
建议:在前置解包前,先对可选类型进行判断,判断是否nil
if name != nil {
print(name!)
}```
* 可选绑定
* 判断name是否有值,如果没有值,直接不执行{}
* 如果name有值,系统会自动将name进行强制解包,并且将解包后的结果赋值给name
if let name = name {
print(name)
}
* 你可以在可选绑定中使用常量和变量。如果你想在if语句的第一个分支中操作 actualNumber 的值,你可以改成 if var actualNumber,这样可选类型包含的值就会被赋给一个变量而非常量。
### 隐式解析可选类型
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感叹号来获取值
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要感叹号
### 错误处理
* 当一个函数遇到错误条件,它能报错。调用函数的地方能抛出错误消息并合理处理。
func makeASandwich() throws {
// ...
}
do {
try makeASandwich()
eatASandwich()
} catch SandwichError.outOfCleanDishes {
washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
buyGroceries(ingredients)
}
NO
### 字符串
* 判断是否为""
if emptyString.isEmpty {
print("Nothing to see here")
}
* 字符串插值:您插入的字符串字面量的每一项都在以反斜线为前缀的圆括号中:
let multiplier = 3
let message = "(multiplier) times 2.5 is (Double(multiplier) * 2.5)"
// message 是 "3 times 2.5 is 7.5"
* 如果想要获得一个字符串中Character值的数量,可以使用字符串的characters属性的count属性:
*
greeting[greeting.endIndex] // error
greeting.index(after: endIndex) // error
## Array
let oddNumbers = [1,3,5,7]
* 创建空数组
* var emptyDoubles : [Double] = []
* The full type name is also allowed
var emptyFloats: Array<Float> = Array()
* 如果你需要一个使用默认值提前初始化的数组
let digitCounts = Array(repeatElement(0, count: 10))
### 比较运算符
* 注意: Swift 也提供恒等(===
)和不恒等(!==
)这两个比较符来判断两个对象是否引用同一个对象实例。
### 空合运算符
* a ?? b 表述的意思:a != nil ? a! : b
###元组比较
* Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。
* 因为 Int 和 String 类型的值可以比较,所以类型为 (Int, String) 的元组也可以被比较。
### 区间运算符
* 闭区间运算符: 闭区间运算符(a...b)定义一个包含从 a 到 b(包括 a 和 b)的所有值的区间。a 的值不能超过 b
* 半开区间运算符:半开区间运算符(a..<b)定义一个从 a 到 b 但不包括 b 的区间。 之所以称为半开区间,是因为该区间包含第一个值而不包括最后的值。
### 逻辑组合运算符
* Swift 逻辑操作符 && 和 || 是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。
* 数组遍历
let streets = ["mahadun","newyork"]
for street in streets {
print("I don't live on \(street).")
}
* 判断数组是否为空 isEmpty
if oddNumbers.isEmpty {
print("数组为空")
}else{
print("i know(oddNumbers.count) odd Numbers")
}
* 取到数组的第一个 最后一个元素 ,如果数组为空,返回nil
if let firstElement = oddNumbers.first , let lastelement = oddNumbers.last {
print(firstElement, lastelement, separator: ", ")
}
* 利用角标访问数组元素,不在 0..<count 会报数组越界错误
* 增加或者删除数组元素
var students = ["ben","ivy","jodell"]
// 添加单个元素 append(_:) 一次添加多个元素 append(contentsOf:)
students.append("maxi")
students.append(contentsOf: ["shaki","william"])
// 插入一个或多个元素
students.insert("liam", at: 3)
// 删除元素
students.remove(at: 0)
// 删除最后一个
students.removeLast()
* 修改元素
students[0] = "dddd"
**当数组元素的个数超过初始容量,数组会将分配更大的内存区域并将其元素复制到新存储中。新存储空间是旧存储容量的倍数**
* 修改数组的副本copy
* 如果数据类型为int 结构体类型,修改原数组内容不会影响副本的内容
var numbers = [1, 2, 3, 4, 5]
var numbersCopy = numbers
numbers[0] = 100
print(numbers)
// Prints "[100, 2, 3, 4, 5]"
print(numbersCopy)
// Prints "[1, 2, 3, 4, 5]"
* 如果数组元素是对象
class InergerReference{
var value = 10
}
var firstIntegers = [InergerReference(),InergerReference()]
var secondInergers = firstIntegers
firstIntegers[0].value = 100
print(firstIntegers[0].value,secondInergers[0].value)
// 100 100
firstIntegers[0] = InergerReference()
print(firstIntegers[0].value,secondInergers[0].value)
// 10 100
* Array 与 NSArray的转换 用as
## Dictionary
* 如何创建一个空字典
var emptyDict: [String : String] = [:]
* 通过key来取值 取出来的值我可选项
print(dictionary[200] ?? "")
if let message = dictionary[500]{
print(message)
}else{
print("meiyou改制")
}```
- 添加/修改键值对
dictionary[100] = "text" - 对已经存在的键赋值nil,改键值对会被移除
var interstingNumbers = ["primes" : [2,3,5,5,11,13],
"trianguler" : [1,3,4,56,6]]
for key in interstingNumbers.keys {
interstingNumbers[key]?.sort(by: >)
}
print(interstingNumbers["primes"] ?? "")
- 遍历字典 每个字典都是无序的键值对的集合
var imagePaths = ["star": "/glyphs/star.png",
"portrait": "/images/content/portrait.jpg",
"spacer": "/images/shared/spacer.gif"]
for (key,value) in imagePaths {
print("\(key) : \(value)")
}
- 创建字典
var dict : [String : String] = Dictionary.init(minimumCapacity: 10)
dict["name"] = "lili"
dict.isEmpty
dict.count
- 访问字典的键值对
imagePaths["star"]
imagePaths.index(forKey: "spacer")
imagePaths.first
imagePaths.keys
imagePaths.values
- 添加键值对
imagePaths.updateValue("falsh.png", forKey: "xxx")
imagePaths
- 移除键值对
imagePaths.removeValue(forKey: "flash")
imagePaths.removeAll()
- 遍历字典
imagePaths.enumerated()
imagePaths.makeIterator()
imagePaths.underestimatedCount
imagePaths.first
可选项 Optional
- 可选项 ?
let shortForm: Int? = Int("33")
// 可选项包含两个分支 Optional.some
let number: Int? = Optional.some(44)
let nonumber: Int? = Optional.none
print(nonumber == nil)
- 在很多情况下使用可选项都必须进行解包
let imagePaths = ["star": "/glyphs/star.png",
"portrait": "/images/content/portrait.jpg",
"spacer": "/images/shared/spacer.gif"]```
* 解包方式一: if let
if let starPath = imagePaths["star"] {
print("The star image is at '(starPath)'")
} else {
print("Couldn't find the star image")
}```
- ?? 当为nil时提供一个默认值
let defaultiamgepath = "/iamges/default.png"
let heartpath = imagePaths["heart"] ?? defaultiamgepath
- ?? 可以多个一起使用
let shapePath = imagePaths["cir"] ?? imagePaths["squ"] ?? defaultiamgepath
- 强制解包 解包失败会崩溃
let num = Int("42")!
let isPng = imagePaths["star"]! .hasSuffix(".png")
switch 语句
- 不存在隐式的贯穿,不需要在 case 分支中显式地使用break语句。
- 每一个 case 分支都必须包含至少一条语句。像下面这样书写代码是无效的,因为第一个 case 分支是空的
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a": // 无效,这个分支下面没有语句
case "A":
print("The letter A")
default:
print("Not the letter A")
}
// 这段代码会报编译错误
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
print("The letter A")
default:
print("Not the letter A")
}
// 输出 "The letter A
- 区间匹配 case 分支的模式也可以是一个值的区间。
let approximateCount = 62
let countedThings = "moons orbiting Saturn"
var naturalCount: String
switch approximateCount {
case 0:
naturalCount = "no"
case 1..<5:
naturalCount = "a few"
case 5..<12:
naturalCount = "several"
case 12..<100:
naturalCount = "dozens of"
case 100..<1000:
naturalCount = "hundreds of"
default:
naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).")
// 输出 "There are dozens of moons orbiting Saturn."
- case 分支的模式可以使用where语句来判断额外的条件
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
print("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
print("(\(x), \(y)) is on the line x == -y")
case let (x, y):
print("(\(x), \(y)) is just some arbitrary point")
}
// 输出 "(1, -1) is on the line x == -y"
- 复合匹配 当多个条件可以使用同一种方法来处理时,可以将这几种可能放在同一个case后面,并且用逗号隔开。当case后面的任意一种模式匹配的时候,这条分支就会被匹配。并且,如果匹配列表过长,还可以分行书写
let someCharacter: Character = "e"
switch someCharacter {
case "a", "e", "i", "o", "u":
print("\(someCharacter) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
print("\(someCharacter) is a consonant")
default:
print("\(someCharacter) is not a vowel or a consonant")
}
// 输出 "e is a vowel"
控制转移语句
- continue continue语句告诉一个循环体立刻停止本次循环,重新开始下次循环
- break语句会立刻结束整个控制流的执行。当你想要更早的结束一个switch代码块或者一个循环体时,你都可以使用break语句
*Switch 语句中的 break
当在一个switch代码块中使用break时,会立即中断该switch代码块的执行,并且跳转到表示switch代码块结束的大括号(})后的第一行代码
- 贯穿
let integerToDescribe = 5
var description = "The number \(integerToDescribe) is"
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
description += " a prime number, and also"
fallthrough
default:
description += " an integer."
}
print(description)
// 输出 "The number 5 is a prime number, and also an integer."
提前退出
- 像if语句一样,guard的执行取决于一个表达式的布尔值。我们可以使用guard语句来要求条件必须为真时,以执行guard语句后的代码。不同于if语句,一个guard语句总是有一个else从句,如果条件不为真则执行else从句中的代码
func greet(person: [String: String]) {
guard let name = person["name"] else {
return
}
print("Hello \(name)")
guard let location = person["location"] else {
print("I hope the weather is nice near you.")
return
}
print("I hope the weather is nice in \(location).")
}
检测API的可用性 最后一个参数,*,是必须的,用于指定在所有其它平台中,如果版本号高于你的设备指定的最低版本,if语句的代码块将会运行
if #available(iOS 10, macOS 10.12, *) {
// 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
} else {
// 使用先前版本的 iOS 和 macOS 的 API
}
函数
- 多个相同类型参数 (可变参数一个函数最多只能拥有一个可变参数。)
func sumof(numbers:Int...) ->Int{
var sum = 0
for num in numbers {
sum += num
}
return sum
}
sumof(numbers: 1,2,3,5)```
* swift中的默认参数
func makeCoffee(coffeename: String = "蓝山"){
print("我要喝(coffeename)咖啡")
}
makeCoffee()
makeCoffee(coffeename: "猫屎")
* 有参有返回值
func greet(name: String,day: String,number: Int) -> String{
return "Hello \(name),Today is\(day),\(number)"
}
print(greet(name: "zhangsan", day: "33", number: 23))
* 函数返回值为元组
可选元组类型如 (Int, Int)? 与元组包含可选类型如 (Int?, Int?) 是不同的.可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。
func calculate(scores: [Int]) -> (max: Int,min: Int,sum: Int){
var max = scores[0]
var min = scores[0]
var sum = 0
for num in scores {
if num > max {
max = num
}else if num < min{
min = num
}
sum += num
}
return (max,min,sum)
}
let statistics = calculate(scores: [3,4,5])
print(statistics.max)
print(statistics.0)
* 输入输出函数
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now (someInt), and anotherInt is now (anotherInt)")
// 打印 "someInt is now 107, and anotherInt is now 3"
* 函数作为返回类型
func makeIncrementer() -> ((Int,Int) -> Int){
func addNumber(num1: Int,num2: Int) -> Int{
return num1 + num2
}
return addNumber
}
let add = makeIncrementer()
add(3,4)
* **函数作为参数(重点)**
* 平方
func square(a: Float) -> Float{
return a * a
}
square(a: 2.0)```
- 立方
func cube(a: Float) -> Float{
return a * a * a
}
cube(a: 2.0)```
* 平均值
func averageSumm(num1: Float,num2: Float,function:(Float -> Float)) -> Float{
return (function(num1) + function(num2))/2
}```
- 函数的嵌套使用
func test(){
func demo(){}
demo()
}
test()
- 排序
var numbers = [1,3,2,5,63,7,0]
let sortNmbers = numbers.sorted(){
$0 < $1
}
print(sortNmbers)
类
class Strudent : NSObject{
// 定义存储属性
var name: String?
var age: Int = 0
var mathScore: Double = 0
var chineseScore: Double = 0
// 定义计算型属性:通过别的方式计算得到结果的属性
var averageScore: Double{
// 在swift中如果使用当前对象的属性和方法不需要添加self
return (mathScore + chineseScore) * 0.5
}
// 定义类属性: 类属性是和整个类相关的属性,而且是通过类名来进行访问
static var courseCount: Int = 0
}
// 给类属性赋值
Strudent.courseCount = 2
let stu = Strudent()
//stu.age = 20
stu.name = "zhangsan"
stu.mathScore = 50
stu.chineseScore = 60
stu.averageScore
// 解包
if let name = stu.name {
print(name)
}
// 可以利用KVC赋值
stu.setValue("lisi", forKey: "name")
- 监听属性变化
class Person: NSObject{
// var name: String?
var name: String?{
// 监听属性的变化
willSet {// 属性即将改变时进行监听
print("willsetlllll")
}
didSet {//属性已经改变时进行监听
print("didsettttt")
}
}
// 在构造函数中,如果没有明确super.init(),那么系统会帮助调用super.init()
override init() {
super.init()
print("=====")
}
// 自定义构造函数
init(name: String) {
self.name = name
}
// 字典转模型
init(dict: [String: AnyObject]) {
super.init()
setValuesForKeys(dict)
}
override func setValue(_ value: Any?, forUndefinedKey key: String) {
}
}
//let p1 = Person.init()
//let p2 = Person.init(name: "lisi")
let p3 = Person.init(dict: ["name": "wangwu" as AnyObject,
"age": 24 as AnyObject
])
print(p3.name ?? "")
便利构造函数
1.遍历构造函数通常都是写在extension里面
2.遍历构造函数init前面需要加载convenience
3.在遍历构造函数中需要明确的调用self.init()
extension UIButton {
convenience init(imageName: String,backGroundName: String) {
self.init()
setImage(UIImage.init(named:imageName), for:UIControlState())
setImage(UIImage.init(named: imageName + "_highlighted"), for: .highlighted)
setBackgroundImage(UIImage.init(named: backGroundName), for: .normal)
setBackgroundImage(UIImage.init(named: backGroundName + "highlighted"), for: .highlighted)
sizeToFit()
}
}
重写一个类的description属性
// MARK:- 重写description
override var description: String{
return dictionaryWithValues(forKeys: ["access_token", "expires_in", "uid"]).description
}
点击事件需要添加@objc fileprivate 非对外方法添加fileprivate
GCD 的使用
- 调度组:
let group = DispatchGroup.init()
// 入组
group.enter()
// 出租
group.leave()
// 通知
group.notify(queue: DispatchQueue.main) {
// 执行code
}
- 延迟执行
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) {
// 执行code
}
let queue = DispatchQueue.init(label: "")
queue.async {
<#code#>
}
队列相关
// 创建队列
let queue = DispatchQueue(label: "com.appcoda.myqueue")
// 指定优先级
let queue1 = DispatchQueue.init(label: "com.appcoda.myqueue1", qos:.background)
// 创建一个并发队列
let queue1 = DispatchQueue.init(label: "com.appcoda.myqueue1",attributes:.concurrent)
//并发执行
queue1.async {
for i in 100 ..< 110{
print("😎",i)
}
}
// 获取全局队列
DispatchQueue.global()
// 主队列
DispatchQueue.main
workitem
// workitem
func useWorkItem() {
var value = 10
// 方法1
let workItem = DispatchWorkItem {
value += 5
}
workItem.perform()
// 方法2
let queue = DispatchQueue.global(qos: .utility)
queue.async(execute: workItem)
//通知
workItem.notify(queue: DispatchQueue.main) {
print("value = ", value)
}
}
闭包
- 闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作。
{ (parameters) -> returnType in
statements
}
枚举
- 应该以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字
与 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的CompassPoint例子中,north,south,east和west不会被隐式地赋值为0,1,2和3。相反,这些枚举成员本身就是完备的值,这些值的类型是已经明确定义好的CompassPoint类型。
enum CompassPoint {
case north
case south
case east
case west
}
- 多个成员值可以出现在同一行上,用逗号隔开:
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
- 使用switch语句匹配枚举值
directionToHead = .south
switch directionToHead {
case .north:
print("Lots of planets have a north")
case .south:
print("Watch out for penguins")
case .east:
print("Where the sun rises")
case .west:
print("Where the skies are blue")
}
// 打印 "Watch out for penguins”
判断一个枚举类型的值时,switch语句必须穷举所有情况。如果忽略了.west这种情况,上面那段代码将无法通过编译,因为它没有考虑到CompassPoint的全部成员。强制穷举确保了枚举成员不会被意外遗漏。
- 当不需要匹配每个枚举成员的时候,你可以提供一个default分支来涵盖所有未明确处理的枚举成员
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// 打印 "Mostly harmless”
- 原始值的隐式赋值
- 在使用原始值为整数或者字符串类型的枚举时,不需要显式地为每一个枚举成员设置原始值,Swift 将会自动为你赋值。
例如,当使用整数作为原始值时,隐式赋值的值依次递增1。如果第一个枚举成员没有设置原始值,其原始值将为0 - 当使用字符串作为枚举类型的原始值时,每个枚举成员的隐式原始值为该枚举成员的名称。
- 使用枚举成员的rawValue属性可以访问该枚举成员的原始值:
let earthsOrder = Planet.earth.rawValue
// earthsOrder 值为 3
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection 值为 "west"
- 递归枚举
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
类和结构体
- 结构体总是通过被复制的方式在代码中传递,不使用引用计数。
class SomeClass {
// 在这里定义类
}
struct SomeStructure {
// 在这里定义结构体
}
- 构造器最简单形式是在结构体或者类型名称后跟随一对空括号
- 与 Objective-C 语言不同的是,Swift 允许直接设置结构体属性的子属性。上面的最后一个例子,就是直接设置了someVideoMode中resolution属性的width这个子属性,以上操作并不需要重新为整个resolution属性设置新值。
- 结构体类型的成员逐一构造器所有结构体都有一个自动生成的成员逐一构造器,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中
let vga = Resolution(width:640, height: 480)
- 结构体和枚举是值类型
值类型被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝。 - 恒等运算符
- 因为类是引用类型,有可能有多个常量和变量在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在被赋予到常量、变量或者传递到函数时,其值总是会被拷贝。)
- 等价于(===)
不等价于(!==)
- 字符串、数组、和字典类型的赋值与复制行为
- Swift 中,许多基本类型,诸如String,Array和Dictionary类型均以结构体的形式实现。这意味着被赋值给新的常量或变量,或者被传入函数或方法中时,它们的值会被拷贝。Objective-C 中NSString,NSArray和NSDictionary类型均以类的形式实现,而并非结构体。它们在被赋值或者被传入函数或方法时,不会发生值拷贝,而是传递现有实例的引用。
属性
- 存储属性只能用于类和结构体,计算属性可以用于类结构体和枚举.
- 如果创建了一个结构体的实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使有属性被声明为变量也不行:
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 该区间表示整数0,1,2,3
rangeOfFourItems.firstValue = 6
// 尽管 firstValue 是个变量属性,这里还是会报错
这种行为是由于结构体(struct)属于值类型。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。
属于引用类型的类(class)则不一样。把一个引用类型的实例赋给一个常量后,仍然可以修改该实例的变量属性
- 懒加载属性lazy
必须将延迟存储属性声明成变量(使用 var 关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。注意
如果一个被标记为 lazy 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。
- 计算型属性:
struct AlternativeRect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
- 只读计算属性只有 getter 没有 setter 的计算属性就是只读计算属性。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值
注意
必须使用 var 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let 关键字只用来声明常量属性,表示初始化后再也无法修改的值
- 只读计算属性的声明可以去掉 get 关键字和花括号:
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// 打印 "the volume of fourByFiveByTwo is 40.0"
- 属性观察器
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps
全局变量和局部变量
- 全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。
注意全局的常量或变量都是延迟计算的,跟[延迟存储属性]相似,不同的地方在于,全局的常量或变量不需要标记lazy
修饰符。局部范围的常量或变量从不延迟计算。
方法
- 类、结构体、枚举都可以定义实例方法;类、结构体、枚举也可以定义类型方法
结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。
-
实际上,你不必在你的代码里面经常写self。不论何时,只要在一个方法中使用一个已知的属性或者方法名称,如果你没有明确地写self,Swift 假定你是指当前实例的属性或者方法。
-
在实例方法中修改值类型
-
结构体和枚举是值类型。默认情况下,值类型的属性不能在它的实例方法中被修改。
但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择可变(mutating)行为,然后就可以从其方法内部改变它的属性;并且这个方法做的任何改变都会在方法执行结束时写回到原始结构中。方法还可以给它隐含的self属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。
要使用可变方法,将关键字mutating 放到方法的func关键字之前就可以了
struct Point {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveByX(2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// 打印 "The point is now at (3.0, 4.0)"
注意,不能在结构体类型的常量(a constant of structure type)上调用可变方法,因为其属性不能被改变,即使属性是变量属性,
let fixedPoint = Point(x: 3.0, y: 3.0)fixedPoint.moveByX(2.0, y: 3.0)// 这里将会报告一个错误
- 在可变方法中给 self 赋值
可变方法能够赋给隐含属性self一个全新的实例
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
类型方法
- 方法的func关键字之前加上关键字static,来指定类型方法。类还可以用关键字class来允许子类重写父类的方法实现。
注意
在 Objective-C 中,你只能为 Objective-C 的类类型(classes)定义类型方法(type-level methods)。在 Swift 中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显式包含
下标
Swift 的Dictionary类型的下标接受并返回可选类型的值。上例中的numberOfLegs字典通过下标返回的是一个Int?或者说“可选的int”。Dictionary类型之所以如此实现下标,是因为不是每个键都有个对应的值,同时这也提供了一种通过键删除对应值的方式,只需将键对应的值赋值为nil即可。
继承
- 在 Swift 中,继承是区分「类」与其它类型的一个基本特征。
- 可以为类中继承来的属性添加属性观察器,这样一来,当属性值改变时,类就会被通知到。可以为任何属性添加属性观察器,无论它原本被定义为存储型属性还是计算型属性
- 子类
class SomeClass: SomeSuperclass {
// 这里是子类的定义
}
重写
- 如果要重写某个特性,你需要在重写定义的前面加上override关键字。这么做,你就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少override关键字的重写都会在编译时被诊断为错误。
在方法someMethod()的重写实现中,可以通过super.someMethod()来调用超类版本的someMethod()方法。
在属性someProperty的 getter 或 setter 的重写实现中,可以通过super.someProperty来访问超类版本的someProperty属性。
在下标的重写实现中,可以通过super[someIndex]来访问超类版本中的相同下标。
注意
你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供willSet或didSet实现是不恰当。
此外还要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的 setter,那么你在 setter 中就可以观察到任何值变化了。
防止重写
- 你可以通过把方法,属性或下标标记为final来防止它们被重写,只需要在声明关键字前加上final修饰符即可(例如:final var,final func,final class func,以及final subscript)。
构造过程
-
构造过程是使用类、结构体或枚举类型的实例之前的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个存储型属性的初始值和执行其他必须的设置或初始化工作。
-
类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。
注意
当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者。
- 不带外部名的构造器参数如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线(_)来显式描述它的外部名,以此重写上面所说的默认行为
*构造过程中常量属性的修改 你可以在构造过程中的任意时间点给常量属性指定一个值,只要在构造过程结束时是一个确定的值。一旦常量属性被赋值,它将永远不可更改。
注意
对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
- 默认构造器 ()
- 结构体的逐一成员构造器 如果结构体没有提供自定义的构造器,它们将自动获得一个逐一成员构造器,即使结构体的存储型属性没有默认值。
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
类的继承和构造过程
- 类里面的所有存储型属性——包括所有继承自父类的属性——都必须在构造过程中设置初始值。
- 构造器的继承和重写跟 Objective-C 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,并被错误地用来创建子类的实例。
注意
子类可以在初始化时修改继承来的变量属性,但是不能修改继承来的常量属性。
�### 析构 deinit
- 在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数
deinit {
// 执行析构过程
}
扩展
- 添加计算型属性和计算型类型属性
- 定义实例方法和类型方法
- 提供新的构造器
- 定义下标
- 定义和使用新的嵌套类型
- 使一个已有类型符合某个协议
extension SomeType {
// 为 SomeType 添加的新功能写到这里
}
extension SomeType: SomeProtocol, AnotherProctocol {
// 协议实现写到这里
}
注意
扩展可以添加新的计算型属性,但是不可以添加存储型属性,也不可以为已有属性添加属性观察器。
闭包
- 闭包表达式语法
{ (parameters) -> returnType in
statements
}
- 闭包的函数体部分由关键字in引入.该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始.
- 单表达式闭包隐式返回单行表达式闭包可以通过省略 return 关键字来隐式返回单行表达式的结果
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
- 尾随闭包
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
- 值捕获闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
- 闭包是引用类型无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用
- 逃逸闭包:当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。
- 逃逸闭包中需要显式的引用self
- 在非逃逸闭包中可以隐式的引用self