简单值
- 多行字符串,使用三个双引号(""")
var name = """
"1111
2222222222
333333333333333"
""";
- 要创建空数组或字典,请使用初始化语法。
let emptyArray = [String]()
let emptyDictionary = [String: Float]()
控制流
for-in
// 普通for循环
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
// 通过为每个键值对提供一对名称,可以使用for-in迭代字典中的项。字典是一个无序的集合,因此它们的键和值将以任意顺序迭代。
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
// 您可以使用...在循环中保存索引,以生成索引范围。
var total = 0
for i in 1...3 {
total += i
}
print(total) // Prints "6"
// 忽略其上值的范围
var total = 0
for i in 1..<3 {
total += i
}
print(total) // Prints "2"
guard
一种控制流的语法糖,简化代码作用,用来表达 “提前退出”的意图
// 这里表示如果filePath是nil,则执行else里的代码
guard let filePath: String = FilePath() else {
return
}
// TODO,filePath不为nil时执行的代码
函数
- 函数使用它们的参数名作为参数的标签。在参数名前写一个自定义参数标签,或写_来不使用参数标签。
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
- 设置默认参数
func makecoffee(name :String = "卡布奇诺") -> String {
return "制作一杯\(type)咖啡。"
}
let coffee1 = makecoffee("拿铁") // 拿铁
let coffee2 = makecoffee() // 卡布奇诺
- 可变参数
func sum(numbers:Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total
}
sum(100.0, 20, 30)
sum(30, 80)
- 引用类型(指针的传递)
默认情况下,函数的参数是值传递.如果想改变外面的变量,则需要传递变量的地址
必须是变量,因为需要在内部改变其值
Swift提供的inout关键字就可以实现
// 函数二:指针的传递
func swap1(inout a : Int, inout b : Int) {
let temp = a
a = b
b = temp
print("a:\(a), b:\(b)")
}
swap1(&a, b: &b)
print("a:\(a), b:\(b)")
- 构造函数
- 在自定义构造函数调用super.init之前,必须保证所有的属性被初始化,否则报 Property self.titles not initialized... 错误
- 函数是一类类型。这意味着一个函数可以返回另一个函数作为它的值。
// sum函数返回值是((Int, Int) -> Int),这是一个函数
func sum(a: Int, b: Int) -> ((Int, Int) -> Int) {
func inner(innerA: Int, innerB: Int) -> Int {
return innerA + innerA
}
return inner
}
var cal = sum(a: 2, b: 3)
let c = cal(3, 4);
- 一个函数可以接受另一个函数作为它的参数之一
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
- 闭包
func loadData(id: String, callback: (_ data: AnyObject?, _ error: NSError?)->()) {
}
loadData(id: "1") { (data: AnyObject?, error: NSError?) in
//
}
- 逃逸闭包:当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后还可能被使用,我们称该闭包从函数中逃逸
- 逃逸闭包需要加
@escaping,用来指明这个闭包是允许“逃逸”出这个函数的 - 将一个闭包标记为
@escaping意味着你必须在闭包中显式地引用self,而非逃逸闭包则不用。这提醒你可能会一不小心就捕获了self,注意循环引用。
- 逃逸闭包需要加
var completions: [() -> Void] = []
func testEscapingClosure(completion: @escaping () -> Void) {
completions.append(completion)
}
对象、类、结构体
class 和 struct 的区别
相同点: 我们可以使用完全使用相同的语法规则来为 class 和 struct 定义属性、方法、下标操作、构造器,也可以通过extension 和 protocol 来提供某种功能。
不同点:
- 与 struct 相比,class 还有如下功能:1、继承允许一个类继承另一个类的特性。2、类型转换允许在运行时检查和解释一个类实例的类型。3、引用计数允许对一个类的多次引用。4、析构器允许一个类实例释放任何其所被分配的资源。
- 在 swift 中 struct 是值,而 class 是引用。所谓值,struct 一旦生成是不能变的,如果有一个方法改变了自己的属性,这个方法需要标为 mutating,修改时会产生一个新值去替代旧值(这一步可能会被编译器优化,并不一定会真的产生一个新 struct)。而 class 无论怎么传递都是同一个对象。
Extension
- 在 extension 扩充构造函数,必须扩充便利构造函数
- 必须在 init 前,加上convenience
- 必须调用 self.init() 原有的某一个构造函数
convenience init() {
self.init()
}
面向协议
-
面向对象的问题: 不能实现多继承,且会导致继承关系变得复杂
-
为了避免循环引用, 代理用 weak 修饰, 但是weak 修饰会报错, 因为 weak 只修饰class, 所以协议需要继承自 class
-
struct enum 都可以遵守协议
-
面向协议的事例:
protocol SportProtocol {
var hot: Double {get set} // {get set}表示可读可写
func playFootBall()
@objc optional func playBasketBall() // 前面的 @objc optional 表示方法是可选的
}
// 在协议中, 属性/方法可以在 extension 中默认实现
extension SportProtocol {
var price: Double { // 此时属性只读, 想要修改, 必须覆盖
return 100.0
}
func playFootBall() {
print("T足球")
}
}
- 实现统一加载 nib的 协议
import Foundation
import UIKit
protocol NibLoadProtocol {
}
// where Self: UIView 表示调动的必须是 UIView 及其子类
extension NibLoadProtocol where Self: UIView {
// 协议中 static 表示类方法
static func loadFromNib() -> Self {
return Bundle.main.loadNibNamed("\(self)", owner: nil, options: nil)?.first as! Self
}
}
枚举
- 枚举的语法,enum开头,每一行成员的定义使用case关键字开头,一行可以定义多个关键字
enum CompassPoint {
case North
case South
case East
case West
}
enum CompassPoint {
case North, South, East, West
}
// 上例中North,South,East,West的值并不等于0,1,2,3,而是他们本身就是自己的值,且该值的类型就是CompassPoint
var directionToHead = CompassPoint.West
// directionToHead是一个CompassPoint类型,可以被赋值为该类型的其他值
// 当设置directionToHead的值时,他的类型是已知的,因此可以省略East的类型
directionToHead = .East
- 使用switch分开枚举的值,以进行的不同的操作。switch内的case必须包含枚举的所有分支,否则编译出错。当然,列举所有枚举值不太方便时,可以使用default
directionToHead = .South
switch directionToHead {
case .North:
println("Lots of planets have a north")
case .South:
println("Watch out for penguins")
case .East:
println("Where the sun rises")
case .West:
println("Where the skies are blue")
}
- 枚举的元素可以是结合值(associated value),下面通过一个可以存储一维条形码(由3个整数组成)和二维条形码(由字符串组成)的枚举条形码实例来说明
enum Barcode {
case UPCA(Int, Int, Int)
case QRCode(String)
}
// 定义一个变量。该变量即可被赋值为3个整数,又可被赋值为一个字符串,但都是Barcode类型的枚举值
var productBarcode = Barcode.UPCA(8, 85909_51226, 3)
productBarcode = .QRCode("ABCDEFGHIJKLMNOP")
// 使用switch时,case内可区分条形码种类,可使用变量或常量获得结合值
switch productBarcode {
case .UPCA(let numberSystem, let identifier, let check):
println("UPC-A with value of \(numberSystem), \(identifier), \(check).")
case .QRCode(let productCode):
println("QR code with value of \(productCode).")
}
// 打印 "QR code with value of ABCDEFGHIJKLMNOP."
*在case内部,如果其类型都为let或var,则该关键字可提前到case和枚举类型中间,如:
case let .UPCA(numberSystem, identifier, check):
- 原始值类型的枚举在枚举名后紧跟数据类型,其枚举的成员在定义时已经赋予了初始值,且不能改变,与结合值类型的枚举相比,结合值是在将枚举值赋予一个变量时,才设置了那个枚举的值。
//原始值枚举的类型紧跟枚举名后,其成员的原始值的数据类型都是这个指定的类型
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
//Int类型的原始值枚举成员的原始值是递增的,比如Venus的值是2,Earth的值是3
enum Planet: Int {
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
//可以通过toRaw方法获得枚举成员的原始值
let earthsOrder = Planet.Earth.toRaw()
// earthsOrder 的值是 3,数据类型是Int
//可以通过fromRaw方法获得原始值对应的枚举成员
let possiblePlanet = Planet.fromRaw(7)
// possiblePlanet 的数据类型 Planet? 值是 Planet.Uranus
//因为fromRaw的原始值可能没有对应的枚举成员,所以返回的类型是一个可选变量值
let positionToFind = 9
if let somePlanet = Planet.fromRaw(positionToFind) {
switch somePlanet {
case .Earth:
println("Mostly harmless")
default:
println("Not a safe place for humans")
}
} else {
println("There isn't a planet at position \(positionToFind)")
}
// 枚举定义中没有原始值为9的成员,所以打印 "There isn't a planet at position 9"
枚举函数
//枚举函数
enum mobileLanguageFun{
case IOS (String, String)
case Android (String)
//定义枚举函数
var description: String{
switch self {
case mobileLanguageFun.IOS(let language1, let language2):
return "language1 = \(language1), language2 = \(language2)"
case mobileLanguageFun.Android(let temp):
return temp
default:
return ("NO")
}
}
}
var myMobile: mobileLanguageFun = mobileLanguageFun.IOS("objc", "swift")
println(myMobile.description) //language1 = objc, language2 = swift
##关键词
访问控制
在swift中,访问修饰符有五种,分别是:private、fileprivate、internal、public、open。其中fileprivate和open是swift 3 新添加的。由于之前的访问控制符是基于文件的,不是基于类的。这样会有问题,故swift 3 增加了两个修饰符,对原来的private、public进行了细分。
从高到低的排序如下:
open > public > interal > fileprivate > private
private:修饰的属性或者方法只能在当前类且在一个 extension才可访问。fileprivate:修饰的属性或者方法只能在当前文件中访问。如果一个文件中含有多个类,也是可以访问的。internal:默认访问级别,可忽略不写。- 修饰的属性和方法在源代码的整个模块都可以访问。
- 如果是框架或库代码,则在整个框架都可以访问,框架以外是不可以访问的。
- 如果是App代码,则在整个App内部都是可以访问的。
public:可以被任何代码访问。但其他模块不可以被override和继承,在模块内是可以被override和继承的。- 像构造函数等,只能用 public 不能用 open。enum、protocol 的声明只能用 public。
open:可以被任何模块的代码访问,包括override和继承。
final:
- 声明该类、函数、属性不可被继承或重写。
#####filter: 条件满足
- 用于选择数组元素中满足某种条件的元素
intArray = [1, 3, 5]
let filterArr = intArray.filter {
// $0表示第一个参数、一次类推
return $0 > 2
}
// [3, 5]
#####map: 转换
- map 是Array类的一个方法,我们可以使用它来对数组的每个元素进行转换
let intArray = [1, 3, 5]
let stringArr = intArray.map {
return "\($0)"
}
// ["1", "3", "5"]
#####reduce: 计算
- 把数组元素组合计算为一个值
let intArray = [1, 3, 5]
let result = intArray.reduce(0) {
return $0 + $1
}
//9
错误处理与异常抛出
主动退出程序的几种情况
- Fatal Errors(致命的错误)
- 使用fatalError()函数可以立即终止你的应用程序,在fatalError()中可以给出终止信息。使用fatalError()函数,会毫无条件的终止你的应用程序,用起来也是比较简单的,就是一个函数的调用。
fatalError("致命错误,调用我程序终止")
- Assertions(断言)
- 在断言中的提示条件是可选的。断言会在Debug模式下起作用,但是在Release版本中就会被忽略。
// @param: condition, 为true时,断言不执行,相应的断言信息不打印。为false时,断言执行,并且打印相应的断言信息。
assert(<#T##condition: Bool##Bool#>, <#T##message: String##String#>)
- 先决条件(Preconditions)
- Preconditions的用法和断言一样,不过有一点需要注意,Preconditions在debug和release模式下都会被执行,除非使用–Ounchecked进行编译。
precondition(<#T##condition: Bool##Bool#>, <#T##message: String##String#>)
Swift中的错误处理
- 在Swift中,如果你要定义你自己的错误类型,你只需要实现ErrorType协议即可。声明完错误类型后,就可以在处理错误抛出异常时使用自定义的错误类型了。
1.使用枚举创建错误类型
- 1.1遵循ErrorType协议,自定义错误类型。下方定义了一个错误类型枚举,该枚举遵循了ErrorType协议,在接下来的代码中我们将会使用这个MyCustomErrorType枚举
// 定义错误类型
enum MyErrorType: ErrorType {
case ErrorReason1
case ErrorReason2
case ErrorReason3
}
- 1.2在我们的函数定义时可以使用throws关键字,以及在函数中使用
throw关键字对错误进行抛出,抛出的错误类型就可以使用上面我们自己定义的错误类型。
func throwError() throws {
let test: Int? = nil
guard test != nil else {
throw MyErrorType.ErrorReason2
}
}
- 1.3上面函数的功能是对错误进行抛出,接下来就该使用
do-catch来处理抛出的错误。使用try对错误进行捕捉,使用do-catch对错误进行处理。
do {
try throwError()
} catch MyErrorType.ErrorReason1 {
print("错误原因: 1")
}catch MyErrorType.ErrorReason2 {
print("错误原因: 2")
}catch {
print("错误原因: 3")
}
do {
try throwError()
} catch MyErrorType.ErrorReason1, MyErrorType.ErrorReason2 {
print("错误原因: ")
}catch {
print("错误原因: 3")
}
2.使用结构体为错误处理添加Reason
- 使用结构体来遵循ErrorType协议, 可以在抛出错误时,给自定义错误类型提供错误原因。
- 2.1使用结构体创建错误类型,下方名为MyErrorType的结构体遵循了ErrorType协议,并且在MyErrorType结构体中,声明了一个reason常量,该reason常量中存储的就是错误原因
struct MyErrorType2: ErrorType {
let reason1: String
let reason2: String
}
- 2.2上面定义完错误类型结构体后,在错误抛出中就可以使用了。在错误抛出时,可以传入一个错误原因
func throwError2() throws {
let test: Int? = nil
guard test != nil else {
throw MyErrorType2(reason1: "原因1", reason2: "原因2")
}
}
- 2.3最后要对抛出的错误进行do-catch处理,在处理时,可以对错误原因进行打印,错误原因存储在error中
do {
try throwError2()
} catch {
print(error)
}
3.使String类型遵循ErrorType协议,直接使用String提供错误原因
- 这种方式最为简单
extension String: ErrorType {
}
func throwError3() throws {
let test: Int? = nil
guard test != nil else {
throw "error"
}
}
do {
try throwError3()
} catch {
print(error)
}
try? 和 try!是什么意思
- try?: 表示忽略异常,如果后面的表达式有异常抛出,会忽略,并且返回 nil。
- try!: 断言这里不会抛异常。如果后面表达式有异常抛出,直接崩溃。
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}