Swift 基础

684 阅读12分钟

简单值

  • 多行字符串,使用三个双引号(""")
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内部,如果其类型都为letvar,则该关键字可提前到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。其中fileprivateopen是swift 3 新添加的。由于之前的访问控制符是基于文件的,不是基于类的。这样会有问题,故swift 3 增加了两个修饰符,对原来的privatepublic进行了细分。

从高到低的排序如下:

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
}