GeekBand Swift高级编程(第一周)

Swift语言简介

Swift是在Objective-C语言的基础上发展而来的一门现代高级语言。由苹果公司于2014年6月推出,目前已经是2.0版本。
Swift与Cocoa和Cocoa Touch框架高度集成,支持开发Mac OS X、iOS、watchOS应用。基于Objective-C运行时,支持与Objective-C语言双向互操作。

Swift语言有三种开发方式
Playground:交互式编写代码,适合快速学习、测试、可视化观察
REPL(Read-Eval-Print-Loop)命名行:生成.swift文件之后在在命名行下键入 xcrun swiftc 文件名.swift 来编译运行。适合调试、研究、微观探查。
Xcode项目:构建正规工程项目,使用大型框架,追求设计质量与代码组织。

Swift的编译过程
相较于Objective-C的编译过程,在前端和LLVM IR优化之间多了一步叫SIL Optimizer(Swift中间语言优化)的步骤。

编译过程

类型系统
值类型 value type有:
基础数值类型
结构 struct
枚举 enum
元组 tuple

特殊值类型有:
字符串 string
数组 Array
字典 Dictionary
集 Set

引用类型 reference type
类 class
闭包 closure

类型装饰
协议 protocol
扩展 extension
泛型 generics

基础数值类型

基础数值类型

学习资料
**Swift官方资源 **https://developer.apple.com/swift/resources/

值类型与引用类型

类型成员Type Member
属性 property:数据成员 data member,描述对象状态
方法 method:函数成员 function member,描述对象行为
初始化器 init
析构器 deinit
下标 subscript

class MyClass {
    
    //属性
    var x:Int
    var y:Int
    var datas:[Int]=[1,2,3,4,5]
    
    //初始化器
    init(x:Int, y:Int){
        self.x=x
        self.y=y
    }
    
    //方法
    func print(){
       println("\(x), \(y), \(datas)")
    }
    
    
    //下标
    subscript(index: Int) -> Int {
        get {
            return datas[index]
        }
        set(newValue) {
            datas[index]=newValue
        }
    }
    
    //析构器
    deinit{
        println("clear up resources")
    }
}

类与结构class and struct
类的实例叫对象(object),是一个引用类型,在栈上就是一个指针,指向一个位于堆上的实体对象。

引用类型的空间分析

结构的实例叫值(value),是一个值类型,实例直接位于栈中。
值类型的空间分析

对象和值再不同的内存空间(栈和堆)拷贝和传参过程分析


class RPoint{
    var x:Int
    var y:Int
    
    init(x:Int, y:Int){
        self.x=x
        self.y=y
    }
}

struct SPoint{
    var x:Int
    var y:Int
    
    init(x:Int, y:Int){
        self.x=x
        self.y=y
    }
}

var rp=RPoint(x:10,y:20)
var sp=SPoint(x:10,y:20)

func foo(){
    
    var rp1=RPoint(x:10,y:20)
    var rp2=rp1
    var sp1=SPoint(x:10,y:20)
    var sp2=sp1
    
    rp1.x++
    rp1.y++

    sp1.x++
    sp1.y++ 
}


func foo1(){
    var rp1=RPoint(x:10,y:20)
    var sp1=SPoint(x:10,y:20)
    
    foo2(rp1,sp1)
}

func foo2(var rp2: RPoint, var sp2: SPoint){
    rp2.x++
    rp2.y++
    
    sp2.x++
    sp2.y++
}

foo()
foo1()

根据以上代码运行后,foo函数的内存模型如下,sp1是value,经过复制sp2操作后,sp1和sp2都是value,存储在栈区域内。
rp1是object,复制rp2操作后,只是在栈区域复制了一个地址rp2,指向的还是和rp1同一块堆空间。


拷贝行为

根据以上代码运行后,foo1和foo2函数的内存模型如下,当一个value(sp1)做为参数传递时,接收的函数将value的值再复制一份到自己的栈中,当object(sp1)做为参数传递时,接收的函数只复制一份相同的地址存到自己的栈中,并且指向同一块堆空间。


传参行为

类型成员:属性

属性用来表达实例状态或类型状态。
属性按类别可以分为存储属性和计算属性,按照可变性可分为变量属性和常量属性,根据归属权可以分为实例属性和类型属性(静态属性)

为了更好的的理解属性的本质,我们要学会查看sil中间格式,在命令行输入
xcrun swift -emit-silgen point.swift -o point.sil
这样就生成了一个point.sil文件,可以通过文件编辑器查看其中的代码。

存储属性的本质构成是一个存储变量和两个访问器方法(ge方法和set方法)
计算属性没有一个存储的变量,但是他也有两个访问器方法,也可以简化为一个get方法。

struct SPoint{
    var x:Int
    var y:Int
}

class RPoint{
    var x:Int
    var y:Int
    static var max=100 //静态变量属性
    
    init(x:Int,y:Int){
        self.x=x
        self.y=y
    }
}

var sp=SPoint(x:10,y:20)
var rp=RPoint(x:10,y:20)
值存储属性的内存模型
struct SCircle{
    var center:RPoint
    var radius:Int
}

class RCircle{
    var center:RPoint
    var radius:Int
    
    init(center:RPoint, radius:Int){
        self.center=center
        self.radius=radius
    }
    
    static var min=RPoint(x:0,y:0)
}

var sc=SCircle(center:rp, radius:10)
var rc=RCircle(center:rp, radius:100)
引用存储属性的内存模型.jpg

存储属性的生存周期
实例存储属性的生命周期跟随实例本身,实例销毁了实例存储属性也一同销毁。
静态存储属性的值存储在静态数据区,生命周期从类型加载开始,到类型卸载结束(生存周期非常长)。

值存储属性直接“内嵌”在实例中。
引用存储属性通过指针“强引用”堆上的引用类型实例,ARC系统会对引用数进行管理,当引用数为0时销毁。

Lazy存储属性:如果有的类型对象占用内存空间很大,可以使用Lazy属性延迟初值计算到访问时。

class DataProcessor{
    lazy var myData=LargeData()
}

属性初始化:类中所有的属性必须初始化,要么提供默认值 或者 构造器初始化。结构中的存储属性可以不用程序员写初始化器,编译器会自动帮我们添加构造方法。
属性观察者:检测改变
willSet 在改变前调用
didSet 在改变后调用

class DataStorage {
    
    var data: Int = 0 {
        willSet {
            print("About to set data to \(newValue)")
        }
        didSet {
            print("Changed from \(oldValue) to \(data)")
        }
    }
}

类型成员:方法

函数:代码段上的可执行指令序列
全局函数

func add(data1:Int, data2:Int)-> Int{
    return data1+data2
}

成员函数
成员函数也叫方法,方法是类型的成员函数,表达行为,方法可以定义与以下类型:class,struct,enum

class MyClass {
    var instanceData=100
    static var typeData=10000
 
    //实例方法
    func instanceMethod(){
        ++instanceData
        ++MyClass.typeData  
        instanceMethod2()
        MyClass.typeMethod()
    }
    
    //静态方法
    static func typeMethod(){
        ++typeData
        //++instanceData
        typeMethod2()
        //instanceMethod()
    }
}

归属权
实例方法-反应的是一个实例行为,可以访问实例方法与属性、以及类型属性与方法。
类型方法-类型行为,只能访问类型属性与方法。

方法参数
func(参数1,参数2...)->返回值{ }
参数形式:外部参数名 本地参数名:类型
声明时可以省略外部参数名,这时外部参数名使用本地参数名。
调用时,第一个参数名可忽略,但后面的参数名必须显示标明。如果在声明时加_,调用时也可以忽略参数名
方法可以没有参数,也可以没有返回值
参数传递默认为传值方式

//显式内部参数名,显式外部参数名
    func sayGreeting(person name:String,greeting words:String)->String{
        return words+"! "+name
    }

//调用显式内部参数名,显式外部参数名函数
myObject.sayGreeting(person:"Jason", greeting:"You are welcome")

//显式内部参数名,省略外部参数名
    func sayGreeting(name:String, _ words:String)->String{
        return words+"! "+name
    }
//调用显式内部参数名,省略外部参数名函数,注意如果上面的_ words没有写下划线,就不能省略第二个内部参数名words
myObject.sayGreeting("Jason","Welcome")

//无参数,无返回值
func sayGreeting(){
        print("Hello!")
    }

更多参数与返回值功能
提供参数默认值

//设置参数的默认值为Jason
func sayGreeting(name:String="Jason")->String {
        return "Hello! "+name
    }

常量参数 VS 变量参数

//data1是常量参数 data2是变量参数
func changeParameter(data1:Int, var data2:Int){
        var dataTemp=data1
        data2++
        dataTemp++
    }

可变数目参数:实质上传递的是数组

//可变数目参数
    func averageNumber(numbers:Double...)->Double{
    
        var sum=0.0
        for item in numbers {
            sum+=item
        }
        return sum / Double(numbers.count)
    }

//可变参数个数函数的调用
myObject.averageNumber(10,90,30,80,50,100)

inout参数,可以改变外部实参,注意与引用类型参数区别

//inout参数
    func swap(inout a:Int,inout b:Int){
        let tempA=a
        a=b
        b=tempA
    }

//调用inout参数的函数
myObject.swap(&data1,b: &data2)

多个返回值:Tuple类型

//返回Tuple类型
    func minMax(array: [Int]) -> (min: Int, max: Int){
        var currentMin = array[0]
        var currentMax = array[0]
        for value in array[1..<array.count] {
            if value < currentMin {
                currentMin = value
            } else if value > currentMax {
                currentMax = value
            }
        }
        return (currentMin, currentMax)
    }

//返回Tuple类型的函数调用
let range=myObject.minMax([3,-9,23,15,-45,7])
print("max=\(range.max), min=\(range.min)")

类型成员:初始化器

认识初始化器
初始化器用于初始化类型实例,是一个特殊的函数,无返回值。初始化器以init开头

//直接赋初值的初始化器
init(){
        x=10
        y=10
    }
    
//自定义初值的初始化器
    init(x:Int, y:Int){
        self.x=x
        self.y=y
        
        print("init is invoked")
    }

var pt1=Point()
//调用初始化器,初始化器的第一个参数名也不能省略
var pt2=Point(x: 100,y:200)

类型实例的初始化过程
1.分配内存
2.调用舒适化器初始化内存

初始化器可以定义与以下类型:class, struct, enum
只有实例初始化器,没有类型初始化器

初始化实例属性
初始化器可以指定默认值,也可以自定义初始值。所有存储属性必须被初始化,实例存储属性可以使用初始化器初始化,类型存储属性不可以使用初始化器,所以必须指定默认值。
可选属性类型可以不初始化

//可选属性类型初始化时要在后面加上一个问号‘?’,实际上是编译器把数值初始化成了nil
class Product{
    var no:Int?
    var description: String?
}

存储属性被初始化时,不会调用属性观察者。

默认初始化器
一个类可以有多个初始化器,但至少需要一个初始化器。如果一个类没有提供初始化器,那么编译器会自动生成一个默认的初始化器,默认的初始化器是一个无参数的初始化器init()。

指定初始化器 VS 便捷初始化器
指定初始化器(Designated Initializer)为类的主初始化器,负责初始化所有属性。必须调用其父类的主初始化器。
便捷舒适化器(Convenience Initializer)为类的辅助初始化器。必须调用同类的指定初始化器。

初始化器
class Point3D{
    var x:Int
    var y:Int
    var z:Int
    //指定初始化器,也叫主初始化器
    init(x:Int, y:Int, z:Int){
        self.x=x
        self.y=y
        self.z=z
        
        //other processing
        print("other task")
    }
    //便捷初始化器,要加convenience关键字在函数前,函数内部就可以调用指定初始化器了
    convenience init(x:Int, y:Int){      
        self.init(x:x,y:y,z:0)
    }
    
    convenience init(){   
        self.init(x:0,y:0,z:0)
    }
}

类型成员:析构器

认识析构器
析构器(deinit)在实例内存被释放前调用,用于释放实例使用的非内存资源。 析构器仅可以定义于class,且只能定义一个。struct和enum不能定义析构器。
归属权:只有实例析构器,没有类型析构器
析构器的调用是由运行时根据ARC的释放规则调用,程序员无法手工调用。

class FileStream{
    init(){
        print("open file...")
    }
    
    func process(){
        print("process file...")
    }
    //deinit是析构器,由于析构器是一个特殊的函数,所以之前不需要加func,也不需要加参数
    deinit{
        print("close file...")
    }
}

var fs:FileStream?

fs=FileStream() //引用计数:1

fs!.process()

fs = nil //引用计数:0 ARC在这时将自动调用析构器

ARC简介
ARC(Automatic Reference Counting)自动引用计数,用于管理堆上的对象实例所分配的动态内存。在Swift语言中必须使用ARC管理机制,不可以关闭ARC来手动管理。ARC不负责管理栈上的内存。栈上的内存有运行时根据函数生存周期来自动管理栈的创建和销毁。ARC通过追踪“对象倍引用的计数”来确定对象是否还需要被访问,如果对象的引用计数为0,ARC会立即调用析构器,并随后释放对象内存。

引用计数管理
对象引用计数增加1:
新创建对象并赋值给变量

var fs:FileStream?

fs=FileStream() //引用计数:1

将对象引用拷贝给其他变量或常量
var fs2 = fs //引用计数:+1(等于2)
将对象赋值给其他属性(无论实例属性、还是类型属性)

class MyClass{
    var myFile:FileStream?
    
    init(myFile: FileStream?){
        self.myFile=myFile
    }
}

var mc: MyClass?
mc = MyClass(myFile:nil)
mc!.myFile=fs //引用计数:3

将对象传递给函数参数(非inout参数),或者返回值

func invoke(file: FileStream?){
    file!.process()
}

//调用时引用计数+1 : 4
invoke(fs)
//函数结束引用计数-1 : 3

对象引用计数减1:
将变量赋值为nil
fs = nil
将属性赋值为nil,或者属性所在的对象被释放(实例属性)
mc!.meFile = nil 或者 mc = nil
传值参数离开函数

图示引用计数

下标与操作符

下标(Subscripts)
下标支持使用索引的方式访问“集合式”实例,例如vector[index]
下标可以定义于class, struct, enum
下标可以类比为“含参的计算属性”,其本质是一对代索引参数的访问器方法(get/set)

访问下标
归属权:只能定义实例下标,不能定义类型下标(静态下标)
可以定义读写下标(get和set),或者只读下标(get)
下标的索引参数可以是任意类型,也可以设计多个参数
一个类型可以提供多个下标的重载版本(参数不同)


class Vector{
    
    var datas = [Int](count:100,repeatedValue:0)
    
    subscript(index:Int)-> Int{
        get{
            
            return datas[index]
        }
        set{
            
            datas[index]=newValue
        }
    }
}

var datas=Vector()
for i in 0..<10{
   datas[i]=i
}

for i in 0..<10{
    print(datas[i])
}

标准操作符
赋值操作符:= 不返回结果
算术操作符:+ - * / % 检测溢出错误
自增、自减操作符:++ --
复合赋值操作符:+= -=
比较操作符:> < >= <=
罗辑操作符:&& || !
位操作符:~ & | ^ << >>
三元操作符:a ? b : c
范围操作符:a..<b(a到小于b) a...b(a到b)
值相等:== !=
引用相等:=== !==

重载操作符
除了标准操作符之外,还可以针对自定义类型重载标准操作符。重载操作符又称“操作符函数”,其本质是全局函数。
可以重载前缀(prefix)、中缀(infix)、后缀(postfix)操作符。前缀和后缀操作符需要加关键词:prefix 或 postfix
也可以通过将参数设置为inout参数,重载复合赋值操作符,例如+=, -=
除了重载Swift标准操作符,也可以自定义新的操作符,并重载。

//中缀操作符
func + (left: Complex, right: Complex) -> Complex {
    
    return Complex(real: left.real + right.real,
        imag: left.imag + right.imag)
}

//前缀操作符
prefix func - (data: Complex) -> Complex {
    return Complex(real: -data.real, imag: -data.imag)
}

//复合赋值操作符
func += (inout left: Complex, right: Complex) {
    left = left + right
}

//自定义操作符
prefix operator +++ {}

prefix func +++ (inout data: Complex) -> Complex {
    data += data
    return data
}

相等操作符
引用相等:判断对象地址是否相等,仅针对引用类型,不适用于值类型。相等操作符===,不相等操作符!==
值相等:判断实例值是否相等,自定义类型需要重载提供“值比较语义”。相等操作符==,不相等操作符!=

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容