带你快速了解IOS开发之Swift语言

2,268 阅读14分钟

前言

阅读耗时15分钟

参考资料:
Swift教程
官网Swift介绍
中文翻译手册 官方手册 Swift编译流程

目录

带你快速了解IOS开发之Swift语言.png

一、Swift

时间线

  • 2014年6月3日,苹果在WWDC开发者大会发布Swift
  • 2014年8月18日,Swift 1.0
  • 2014年10月16日,Swift 1.1
  • 2015年4月8日,Swift 1.2
  • 2015年9月16日,Swift 2.0
  • 2015年10月20日,Swift 2.1
  • 2015年12月4日,苹果宣布Swift编程语言开放源代码
  • 2016年3月21日,Swfit 2.2
  • 2016年9月13日,Swift 3.0
  • 2016年10月27日,Swift 3.0.1
  • 2017年3月27日,Swift 3.1
  • 2017年9月19日,Swift 4.0
  • 2018年3月29日,Swift 4.1
  • 2018年9月17日,Swift 4.2
  • 2019年3月25日,Swift 5.0
  • 2019年9月10日,Swift 5.1
  • 2020年3月24日,Swift 5.2
  • 2020年9月16日,Swift 5.3
  • 2021年4月26日,Swift 5.4
  • 2021年9月20日,Swift 5.5
  • 2022年3月14日,Swift 5.6
  • 2022年9月12日,Swift 5.7

按照苹果的节奏,下次更新将会在20233月份。以上各个版本的差异可以看Swift官方手册
现在已经是XCode 14 了,我用的是XCode 11, 本文介绍的语法,都支持的。或者快速简单了解可以在线运行试试在线运行Swift
有条件的可以使用XCode,玩玩Playground,可以实时显示结果,包扩图形、列表。
编译流程
详细参考

swift code -> swift ast -> raw swift IL -> Canonical Swift IL ->LLVM IR ->Assembly -> Executable

生成语法树 swiftc -dump-ast main.swift -o main.ast
生成最简洁的SIL代码 swiftc -emit-sil main.swift
生成LLVM IR代码 swiftc -emit-ir main.swift -o main.ll
生成汇编代码 swift -emit-assembly main.swift -o main.s

二、基本语法

常量与变量

import Cocoa
//常量
let maxNum = 1000
//变量
var index = 0

基础类型操作

//1. 声明一个String类型
var test: String = "xxx"
//等价于
var test2 = "xxx"
//Int Double String Float
//2. 不同类型的表示方法 十进制、二进制、八进制等
var decimalInt:Int = 17
var binaryInt: Int = 0b10001
let octalInt:Int = 0o21
//3.
let float1 = 0.012
//等价于
let float2 = 1.2e-2
//4.
let bignum = 1000000
//等价于
let bignum2 = 1_000_000
//5. boolean类型
let testBool = true
if testBool
{
    println("I'm true")
}

赋值问题

let float_num: Float = 1
let num_int:Int = 1.2// --> 转换后是1,所以定义的时候按规范来
let a:Int = 3
let b:Double = 0.14
//let sum:Double = a + b //cannot invoke '+' with an argument list of type(int ,Double)
let sum:Double = Double(a) + b

元组

fallthrough 配合switch使用,Swiftswitch语句默认在每个case执行完后自动break,不像其他语言,必须写break,想要多个case都执行到,就需要使用fallthrough
但是对于case中使用了let语句的语法,不允许使用fallthrough

let tupple1 = (true, "hello", "world")
//通过元组匹配来访问
let (a , b ,c ) = tupple1
//或者通过以下形式来访问,可以理解为下标
tupple1.0
tupple1.1
tupple1.2
let tupple2 = (x: true, y:"hello", z:"world")
tupple2.x
tupple2.y
tupple2.z

var coordinate = (1, 1)
switch coordinate 
{
    case (0,0):
    	println("0,0")
    case (1,1):
    	println("1,1")
    	fallthrough //接下来会进入到下个case的判断中
    case (-2..2, -2..2):
    	println("在这个区间内")
    case (let x, 0):
    	println("x,0坐标命中")
    case let(x, y) where x == y:
    	println("x,y坐标,x和y相等")
    	//fallthrough 这里不能写,写了会报错,语法不允许
    default:
    	println("default")
}

Option

因为Swift是强类型语言,必然会和Rust或者KT一样,有可选型,对空就行处理
访问值的时候需要加!,或者直接使用if let 语句解包

var options1:Int? = 12
let userInput = "abc"
var age = userInput.toInt() //要么是nil要么是int, 此时是nil
if(age != nil){
    println("age is \(age!)")
}
//等价于
if let age = userInput.toInt()
{
    println("age is \(age)")
}

表达式

var a:Int?
a = 10
var b = 20
var c = a != nil ? a! : b//a不为空,有值,则为a的值a!,否则b
//等价于
var c = a ?? b

区间运算符

a..b //对应 [a,b]
a..<b//对应 [a,b)
for index int 1..10
{
    index  //输出 1 到 10
}
for i in -99...99
{
    i * i
}

字符串

终于可以直接拼接了,用习惯了Java

var s = ""
var str = String()
var ch:Character = "."
str.append(ch)
var str2 = "test"
str += str2
countElements(str)//str的长度

let str_a = "abc"
let str_b = "abc"
str_a == str_b

var str = "welcome to study swift! First Course is Hello World!"
let startIndex:String.Index = str.startIndex
let endIndex:String.Index = advance(str.startIndex, 10)
let searchRange = Range<String.Index>(start:startIndex, end:endIndex)
//从 0 到 10 之间 找到World,返回Option
str.rangeOfString("World", options:NSStringCompareOptions.CaseInsensitiveSearch, range:searchRange)

数组

var array = ["A", "B"]
var array1 = [Int]()
var array2 = Array<String>()
for (index, item) in enumerate(array)
{
    println("\(index) - \(item)")
}
//二维数组
var board = Array<Array<Int>>()
for i in 0..10{
    board.append(Array(count:10, repeatedValue:0))
}
var i = 0, j = 0
mainloop: for i = 0;i < 10;i ++
{
    for j = 0;j < 10; j++
    {
        if board[i][j] == 1
        {
            break mainloop  //这块也是一个语法糖,直接回到了最外层
        }
    }
}

字典

var dict = [1:"A", 2:"B"]
var dict2 = [Int:String]()
var dict3 = Dictionary<Int,String>()
if( dict[3] == nil){
    println("is nil")
}
for (key, value) in dict
{
    println("\(key): \(value)")
}
for key in dict.keys
{
    println(key)
}
for value in dict.values
{
    println(value)
}

Set

无序数据集合
var setDemo = Set<String>()
var str = "name"
if !setDemo.contains("test"){
    setDemo.insert(str)
}
var B:Set<Int> = [2, 3, 4]
var C = Set<Int>([2,3,4, 6, 8])
B.intersect(C) //结果是[2,3,4] 取两者交集
B.union(C) //取的是[2,3,4,,6,8]
B.subtract(C) //[]
B.exclusiveOr(B)//异或操作 [6,8]
B.isSubsetOf(C)//B集合是不是C集合的子集,返回true
//如何定义一个元素是不同的值
var arr:[AnyObject] = ["test", 1, 2]

错误处理

自定义错误

enum VendingMachineError: Error {
    case invalidSelection                     //选择无效
    case insufficientFunds(coinsNeeded: Int) //金额不足
    case outOfStock                             //缺货
}

通过throws抛错误

 func vend(itemNamed name: String) throws {
        guard let item = inventory[name] else {
            throw VendingMachineError.invalidSelection
        }
        ....
        print("Dispensing (name)")
    }
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
    let snackName = favoriteSnacks[person] ?? "Candy Bar"
    try vendingMachine.vend(itemNamed: snackName)
}

通过do-catch处理错误

//语法如下
do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
} catch {
    statements
}

//如
do {
    //尝试执行方法
    try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
    print("Success! Yum.")
} catch VendingMachineError.invalidSelection {
    print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
    print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
    print("Insufficient funds. Please insert an additional (coinsNeeded) coins.")
} catch {
    print("Unexpected error: (error).")
}

如果想实现其他语言里try-catch-finallyfinally里做资源释放等处理的话,可以使用defer关键字
defer 语句将代码的执行延迟到当前的作用域退出之前

//写在任何方法体内,捕获异常前
defer{
    close(file)
}

三、方法

有返回值

func sayHello(name: String?) -> String
{
    let result = "Hello, " + (name ?? "Guest") + "!"
    return result
}

无返回值

func say(){   }

有多个返回值

返回多个值
func test2(data:[Int]) -> (max:Int, min:Int)?{
	if data.isEmpty
	{
        return nil
	}
    retrun (1,1)
}
//使用
var data:[Int]? = [1,2,3,4,5]
data = data ?? [] //如果data为nil,则新建一个空数组
if let result = test2(data!) //返回的数据已经解包了
{
    println("ok!")
}

方法的参数使用

//1.在声明的时候加上#,说明外部参数名和内部参数名一样。 如果不加#,例如age 就是外部参数名,myage就是内部参数名。这种用法只是为了更加语义化,方便维护
func sayHello(#nickname:String, age myage:Int) -> String{
    let result = nickname + "   \(myage)"
    return result
}
sayHello(nickname: "xxx", age: 10)

默认值

有默认值的参数,如果不指定外部参数名,那么外部参数名 == 内部参数名
非默认值的参数一定要放在前面,并且按顺序,否则编译器识别不出来。

fun sayHello2(nickage:String, greeting:String = "Hello") -> String
{
    return "ok"
}
sayHello2("xxxx")
sayHello2("xxxx", greeting: "Good")
fun sayHello2(nickage:String, _ greeting:String = "Hello") -> String
{
    return "ok"
}
sayHello2("xxxx","Good")

可变参数

可变参数
func add(a:Int, b:Int, other:Int ...) -> Int{
    var result = a + b
    for number in other
    {
        result += number
    }
    return result
}
var res = add(2,3)
res = add(2, 3, 4, 5)

变量参数

我们之前的方法都是默认值传递的,参数默认是常量参数,不能被方法内部使用的

func changeValue(num:Int) {
    num = 2 //不能改的,报错
}
func changeValue2(var num:Int){
    num = 3
}

值传递

var num = 4;
changeValue2(num)
num //还是4 ,不会改变值
func tryToChangeValue(var x:Int){x++}
var a:Int = 2
tryToChanageValue(a)
a //还是2

引用传递

func changeValue3(inout a: Int){
    a = 6
}
var x = 4
changeValue3(&x)
x //此时就是6了

函数作为返回值

这个功能大多数语言都是支持的,在开发C++项目中,当时都看懵圈了。建议注释API搞起来。

func testFree1(weight:Int )->Int
{
    return weight;
}
func testFree2(weight:Int )-> Int
{
    return 2*weight
}
func choose(weight:Int ) -> (Int) -> Int
{
    return weight <= 10 ? testFree1 : testFree2
}

闭包

func compare(a:Int, b:Int) -> Bool{
    return a > b
}
var arr:[Int] = [1,3,5,7,9]
sorted(arr, compare)
//等价于(sorted后面这个就是闭包)
sorted(arr, {a:Int, b:int} -> Bool in 
				return a > b
			})
sorted(arr, {a, b in return a>b})//可以自动会推导出入参和返回值
sorted(arr, {a, b in a > b}) //也可以简写成这样,去掉参数类型定义和return
sorted(arr,{$0 > $1})//$o代表第一个参数,$1代表第二个参数
sorted(arr , > )//甚至可以缩写成这样,但是不建议

函数引用类型

闭包+函数的结合体,返回的是函数

func calc(today: Int) -> () -> Int{
    var total = 0;
    return {total += today; return total;}
}
var daily = calc(2); 
daily()//结果是2
daily()//结果是4

var todayPlan = daily
todayPlan() //结果是6
daily() //结果是10

内联函数

func test(){
    println("test")
}
test()//申请栈空间,用完释放
release直接优化成
println("test")//避免申请了占空间,debug默认没有优化,在XCode中自己设置
函数体比较长的时候,递归调用的时候,动态派发的时候


永远不了内联
@inline(never) func test(){
    println("test")
}

@inline(__always) func test(){
    println("test")
}

四、类与结构体、枚举

定义类似,和其他语言都类似

struct Resolution {
    var width = 0
    var height = 0
}
class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
    //构造器
    init(){}
    init(resolution resolution: Resolution){
        selft.resolution = resolution
    }
    init(frameRate: Double){
        selft.frameRate = frameRate
    }
     init(_ name: String){
        selft.name = name
    }
}

如何访问?(点语法)

//创建
let someResolution = Resolution()
let someVideoMode = VideoMode()
//结构体访问
someResolution.width
//类访问
omeVideoMode.resolution.width
//创建2
let resolution1 = Resolution(width:180, height:220)
//创建3
let someVideoMode1 = VideoMode(resolution:someResolution)
let someVideoMode2 = VideoMode(frameRate:22.2)
let someVideoMode3 = VideoMode("Hello Vide Mode")

我们需要注意枚举和结构体都是值类型,也就是赋值的时候,会拷贝内部数据给对方,但是对象却不是同一个

let resolution1 = Resolution(width:180, height:220)
var resolution2 = resolution1
resolution2.width = 330
print("resolution1 is now  (resolution1.width) pixels wide")
// 打印 "resolution1 is now 180 pixels wide"
print("resolution2 is now  (resolution2.width) pixels wide")
// 打印 "resolution2 is now 330 pixels wide"

但是类是引用类型的,和上面相反

let testVideo = VideoMode()
testVideo.resolution = resolution1
testVideo.interlaced = true
testVideo.name = "test"
testVideo.frameRate = 211.1

let testVideo2 = testVideo
testVideo2.frameRate = 66.6
print("The frameRate property of testVideo is now (testVideo.frameRate)")
// 打印 "The frameRate property of testVideo is now 66.6"

指定构造器和便利构造器

便利构造器来调用同一个类中的指定构造器,并为部分形参提供默认值

class Food {
    var name: String
    //指定构造器
    init(name: String) {
        self.name = name
    }
    //便利构造器
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}
let namedMeat = Food(name: "Bacon")
// namedMeat 的名字是 "Bacon"
let mysteryMeat = Food()
// mysteryMeat 的名字是 [Unnamed]

子类重写遍历构造器

class RecipeIngredient: Food {
    var quantity: Int
    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    //`RecipeIngredient` 会自动继承父类的所有便利构造器。这里重写了父类的指定构造器
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

可失败的构造器,就是加个?

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty {
            return nil
        }
        self.species = species
    }
}

延迟加载

利用关键字lazy修饰,只有第一次用到才会去加载
注意,全局的常量或变量都是延迟计算的,跟 延时加载存储属性 相似,不同的地方在于,全局的常量或变量不需要标记 lazy 修饰符。

class DataManager {
    lazy var importer = DataImporter() 
    var data = [String]()
    // 这里会提供数据管理功能
}

Getter、Setter

OC一样,点语法调用的是get、set,但是结构体里还能这样写,我也是没有想到,就和Swift的方法能继续嵌套方法一样,出乎意料,到处都是语法糖
看下面这个例子主要是center不好存储值,是计算出来的

struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct Rect {
    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(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
    size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at ((square.origin.x), (square.origin.y))")
// 打印“square.origin is now at (10.0, 10.0)”

上面还可以继续简化语法

struct AlternativeRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
             Point(x: origin.x + (size.width / 2),
                  y: origin.y + (size.height / 2))

        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

如果center属性,你想改成只读,那么可以这样修改

struct AlternativeRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        return Point(x: origin.x + (size.width / 2),
                  y: origin.y + (size.height / 2))

    }
}

类型属性

类静态变量,Swift叫做类型属性
某个类型关联的静态常量和静态变量,是作为 global(全局)静态变量定义的。但是在 Swift 中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内。

struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 1
    }
}
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 6
    }
}
//`class`关键字可用来支持子类对父类的实现进行重写
class SomeClass {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 27
    }
    class var overrideableComputedTypeProperty: Int {
        return 107
    }
}
//访问
print(SomeStructure.storedTypeProperty)
// 打印“Some value.”
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 打印“Another value.”
print(SomeEnumeration.computedTypeProperty)
// 打印“6”
print(SomeClass.computedTypeProperty)
// 打印“27”

实例方法

class Counter {
    var count = 0
    func increment() {
        count += 1 //等价于self.count += 1
    }
    func increment(by amount: Int) {
        count += amount
    }
    func reset() {
        count = 0
    }
}
let counter = Counter()
// 初始计数值是0
counter.increment()
// 计数值现在是1
counter.increment(by: 5)
// 计数值现在是6
counter.reset()
// 计数值现在是0

可变的实例方法

之前提到,结构体和枚举是值类型,赋值的时候,只是将值赋值给新的对象。在实例方法中,值类型的属性不能被修改。除非加上mutating关键字

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at ((somePoint.x), (somePoint.y))")
// 打印“The point is now at (3.0, 4.0)”

下面这种,利用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)
    }
}

类型方法

Swift 中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显式包含。理解为静态方法即可。

class SomeClass {
    class func someTypeMethod() {
        // 在这里实现类型方法
    }
}
SomeClass.someTypeMethod()

类继承

OC一样,属性和方法都会从父类继承过来。
语法如下

class A{
    var currentPrice = 11.1
    var description: String{
        return "hello world! price is \(currentPrice) from A"
    }
    func makeNoise(){
    }
    //下面的方法不能重写
    final func father(){
        println("called father func")
    }
}
class B: A{
    //重写父类属性
    override description:String{
        return super.description + " son can be free"
    }
    //重写父类方法
    override func makeNoise(){
        println("make nosise start....")
    }
}

扩展

和KT一样,可以为一个类添加一个扩展方法,也和OC的协议类似。
扩展详解

extension SomeType {
  // 在这里给 SomeType 添加新的功能,各种方法,包括构造函数
  func xxx(){}
}

协议

OC一样,只是不会在前面加@

protocol SomeProtocol {
    // 这里是协议的定义部分
    var mustBeSettable: Int { get set } //可读可写
    var doesNotNeedToBeSettable: Int { get }//可读
    static var someTypeProperty: Int { get set }
    func random() -> Double
    static func someTypeMethod()
}
struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 这里是结构体的定义部分
    var mustBeSettable: Int
    var doesNotNeedToBeSettable: Int
    static var someTypeProperty: Int
    func random() -> Double{
        println("实例方法")
        return 2.2
    }
    static func someTypeMethod(){
        println("类方法")
    }
}

泛型就不说了,和其他语言一样的概念,就是加个占位符,实现几乎一样。
你可能还会对修饰符有疑问,Swift修饰符默认为internal(同一模块源文件中都可访问),总共有openpublicinternalfileprivateprivate五种。见访问级别

析构

Swift会自动释放不需要的资源,也是通过ARC来进行管理的。同样对应调用的方法是

deinit{
    //执行析构
}

类引用

class Person{
    weak let person: Person? //弱引用
}
let p = Person();//强引用

无主引用
弱引用不同的是,无主引用在其他实例有相同或者更长的生命周期时使用。你可以在声明属性或者变量时,在前面加上关键字 unowned 表示这是一个无主引用。主要利用来解决循环引用过程中,这个对象不是nil的引用资源释放。

unowned let father: Father //例如你一定有父级

枚举

enum Direction{
    case East
    case West
    case South
    case North
}
var direct:Direction
switch direct
{
    case .East:println("your direction is East")
    case .West:println("your direction is West")
    case .South:println("your direction is South")
    case Direction.North:println("your direction is North")
   
}

enum Month: Int{
    case Jan=1, Feb, Mar, Apr, May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
}
let curMonth:Month = .Nov
curMonth.rawValue //11
let nextMonth:Month? = Month(rawValue: 12)
nextMonth!.rawValue
//原始值,关联的是Char类型,叫做关联枚举
enum Vowel:Character{
    case A = "a"
    case E = "e"
}
enum Code{
    case QR(Int, Int, Int)
    case PC(String)
}
let codeA = Code.Pc("https://www.xxx.com")
let codeB = Code.QR(1, 2, 3)

swtich codeA
{
    case Code.QR(let a, let b, let c):
    	println("value is \(a) , \(b) , \(c)")
    case Code.PC(let str):
    	println("value is \(str)")
}

//递归枚举
indirect enum ArithExpr{
    case number(Int)
    case sum(ArithExpr, ArithExpr)
    case difference(ArithExpr ,ArithExpr)
}

枚举内存

enum Month: Int{ case Jan=1, Feb, Mar, Apr, May,Jun,Jul,Aug,Sep,Oct,Nov,Dec }
enum Vowel:Character{ case A = "a" case E = "e" }

这种枚举只会占一个自己,只需要记录是哪个case

enum Code{ case QR(Int, Int, Int) case PC(String) }

这种枚举占40个字节,4Int 32个字节存储,然后剩余的case 记录需要1个字节,所以实际占用33个字节,但是要8字节对齐,所以占用内存40个字节