带你快速了解IOS开发之Swift语言
前言
阅读耗时15分钟
参考资料: Swift教程 官网Swift介绍 中文翻译手册 官方手册 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
按照苹果的节奏,下次更新将会在2023
年3
月份。以上各个版本的差异可以看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
使用,Swift
的switch
语句默认在每个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-finally
在finally
里做资源释放等处理的话,可以使用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
(同一模块源文件中都可访问),总共有open
、public
、internal
、fileprivate
、private
五种。见访问级别
析构
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
个字节,4
个Int
32
个字节存储,然后剩余的case
记录需要1
个字节,所以实际占用33个字节,但是要8
字节对齐,所以占用内存40
个字节
转载自:https://juejin.cn/post/7167655928622383141