前言

阅览耗时15分钟

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

目录

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

一、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个字节