GeekBand Swift高级编程(第二周)

结构与枚举

认识结构(struct)
struct属于值类型,具有拷贝语义(赋值和传参)
struct不支持面向对象,主要用于定义轻量级数值类型;class支持面向对象,主要用于设计有丰富关系的组件系统(有继承、多态等复杂设计模式)。
struct有传参拷贝成本,不要定义尺寸过大的结构;class有ARC内存管理成本。
不要再struct内定义引用类型属性(会改变struct的值拷贝语义)。

class RPoint{
    var x:Int
    var y:Int
    
    init(x:Int, y:Int){   //class的初始化器,必须写。
        self.x=x
        self.y=y
    }
    
    deinit{
        print("clean up")
    }  
}

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

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

//struct有默认按成员初始化器
var sp1=SPoint(x:10, y:20, z: RPoint(x: 100,y: 200))

var sp2=sp1
print(" \(sp1.x), \(sp1.y), \(sp1.z.x), \(sp1.z.y)")     //" 10, 20, 100, 200\n"
print(" \(sp2.x), \(sp2.y), \(sp2.z.x), \(sp2.z.y)")     //" 10, 20, 100, 200\n"
sp1.x+=10
sp1.y+=10
sp1.z.x+=10
sp1.z.y+=10

print(" \(sp1.x), \(sp1.y), \(sp1.z.x), \(sp1.z.y)")     //" 20, 30, 110, 210\n"
print(" \(sp2.x), \(sp2.y), \(sp2.z.x), \(sp2.z.y)")     //" 10, 20, 110, 210\n"

在struct中定义一个class

struct VS class
相同点:
都可以定义一下成员:属性、方法、下标、初始化器。
都支持类型扩展、协议。
不同点:
类支持继承和多态,结构不支持。
类必须自己定义初始化器,结构会有默认的按成员初始化器
类支持析构器,结构不支持
类的实例在对上,由ARC负责释放;结构的实例在栈上,栈结束会自动释放,不参与ARC管理。
类支持引用相等比较(===与!==),结构不支持

认识枚举(enum)
enum用于定义一组相关的值成员,enum同属于值类型,具有拷贝语义(赋值与传参)。

//定义枚举类型,换行每一行都要用case
enum Color {
      case Red
      case Green
      case Blue
}

//或者可以不换行,用一个case就可以
enum ComplexColor {
      case Red,Green,Blue,Alpha
}

//枚举实例的使用
var c1 = Color.Red
var c2:Color
c2 = .Green  //也可以写成  c2 = Color.Green

可以使用switch-case语句处理enum,但case必须包含所有的枚举值,否则需要用default。

func print (color: Color) {
        switch color  {
        case .Red:
                print("Red Color!")
        case .Green:
                print("Green Color")
       }
}

可以为enum指定原始值,即raw value, 类型可以是字符、字符串、整数、或者浮点数。数值默认从0开始,字符串与枚举值名称相同。

//指定原始类型。默认从0开始,这里设置Monday = 1那么就从1开始了
enum WeekDay: Int {
      case Monday = 1, Tuesday, Wednesday, Thursday, Friday, Satutday, Sunday
}

//用原生值来初始化,rawValue是默认值
var day: Weekday?
day = WeekDay(rawValue: 7)
//获取一个rawValue
var data = WeekDay.Saturday.rawValue

enum支持关键值,可以设置不同类型的值成员,类似联合数据结构

//枚举关联值
class Point {
      var x=0
      var y=0
      init(x:Int, y:Int) {
          self.x = x
          self.y = y
      }
}

enum Position {
      case Number(Point)
      case Name(String)
}

var p1 = Position.Number(Point(x: 123, y: 588))
var p2 = Position.Name("Shanghai People's Square")

//不同类型的值成员枚举类型的使用,根据不同的类型做出不同的操作
func print(position: Position) {
      switch position {
      case .Number (let number):
          print("[\(number.x), \(number.y)]")
      case .Name (let name):
          print(name)
      }
}

enum还可以定义以下成员:计算属性、方法、初始化器。

//定义计算属性、方法、初始化器
enum Sex {
      case Male
      case Female
//初始化器
      init? (symbol: Character) {
            switch symbol {
            case "M"
                self = .Male
            case "F"
                self = .Female
            default:
                return nil
            }
      }

    stactic var Default: Sex {
          return Sex.Male
    }

    func show () {
          switch self {
          case Male:
              print("I am Male")
          case Female:
              print("I am Female")
          }
    }
}

var sex = Sex.Female
sex.show()

var defaultSex = Sex.Default

var newSex = Sex(symbol: "F")

继承与多态

Inheritance
继承:子类自动继承基类的属性、方法、下标
只有类支持继承,结构和枚举不支持继承
继承同时支持实例成员和类型成员

class Shape{
    var no=0
    
    func move() {
        print("NO: \(no) Shape.move")
    }
}

class Rectangle: Shape{
    var leftUp=Point()
    var width=0
    var height=0   
}

继承的两层含义:
成员复用:子类复用基类成员

var r1=Rectangle()
var c1=Circle()

//成员复用
r1.no++
r1.move()

c1.no++
c1.move()

类型抽象:将子类当作父类来使用(IS-A关系准则)

func process(shape: Shape){
    shape.no++
    shape.move()
}

var r1=Rectangle()
var c1=Circle()

//类型抽象
process(c1)
process(r1)

var s:Shape
s=c1
s=r1

继承的内存模型

class Base{
    var x=10
    var y=20
    
    static var min=1000
    
    func invoke(){
        print("Base.invoke")
    }
}

/*
func invoke(self:Base){
    print("Base.invoke")
}*/

class Sub: Base{
    var z=30
    static var max=2000
}

var b=Base()
var s=Sub()

print(Base.min)   //1000
print(Sub.min)    //1000 这句等于print(Base.min)

Base.min++

print(Base.min)   //1001
print(Sub.min)    //1001

b.invoke()// invoke(self: b)
s.invoke()// invoke(self: s)
继承的内存模型

认识多态 Polymorphism
多态:子类在同一行为接口下,表现不同的实现方式。
子类使用override关键字来表达多态定义。
可以重写的成员有:方法、属性、下标;包括实例成员和类型成员。
在子类代码中,可以使用super来调用基类的实现

class Shape{
    var no=0

    func move() {
        print("Shape.move")
    }
}

class Rectangle: Shape{
    
    override var no: Int {
        get{
            print("Rectangle.no.get()")
            return super.no
        }
        set{
            print("Rectangle.no.set()")
            super.no=newValue
        }
    }

    override func move() {
        print("Rectangle.move")
    }    
}

在成员上使用final阻止子类override该成员;在类上使用final阻止该类被继承

class Shape{
    var no=0
    
    //func show()不能被子类重写
    final func show(){
        print("Shape.show")
    }
    
    //0x000640
    func move() {
        print("Shape.move")
    }
}
vTable的内存模型
func process(shape: Shape){
    shape.no++
    shape.move() //根据实际类型来调用
}

//变量有双重身份:
//1. 声明类型
//2. 实际类型
var sp: Shape
sp=Shape()
sp.move() // JMP  GetVirtualMethod(sp) 二次指针间接运算
sp.show() // JMP  0x000340

sp=Rectangle()
sp.move() // JMP  GetVirtualMethod(sp) 二次指针间接运算
process(sp)

sp=Circle()
sp.move()
process(sp)

继承中的init和deinit
初始化器 init
如果子类没有定义初始化器,则将自动继承基类的初始化器
如果子类定义了初始化器,则不再继承,此时子类初始化器必须调用基类的一个初始化器。如果手工不调用,编译器将自动生成调用
如果子类初始化器与基类初始化器原型一致,必须使用override
在子类中使用基类属性,必须确保首先调用基类初始化器

析构器 deinit
如果子类没有定义析构器,会自动继承基类析构器
子类析构器执行完毕后,会自动调用基类析构器(无法手工调用)
子类析构器自动具有多态性


class Base{
    var data1:Int
    init(){
        data1=100
        print("\(data1)")
        print("Base.init")
    }
    
    deinit{
        print("Base.deinit")
    }
}

class Sub: Base{
    var data2=200
     override init(){
        super.init()
        print("\(data1), \(data2)")
        print("Sub.init")
    }
    deinit{
        print("Sub.deinit")
    }
}

func process(){
    var b:Base
    b=Base()
    print("----------")
    
    var s:Base
    s=Sub()
    print("----------")
}

process()

协议

认识协议 Protocol
协议:类型的合同约定,只描述外部接口,不提供具体实现
协议可以包含以下成员:属性、方法、初始化器、下标、操作符
一个类型可以实现多个协议,协议可以应用在如下类型上。但可以添加class关键字标明协议只能应用在类上:类、结构、枚举

//定义协议
protocol Drawable{
    
    var discription: String{
        get
    }
    
    func draw()
    
    init()

    
    subscript(index: Int) -> Int {
        get
    }
    
    func ==(lhs: Self, rhs: Self) -> Bool
}

//实现协议
class Point: Drawable{
    var x:Int
    var y:Int
    
    required init(){
        x=10
        y=10
    }
    
    var discription: String{
        return "[\(x), \(y)]";  
    }

    func draw(){
        print(self.discription)
    }
    
    subscript(index: Int) -> Int {
        return 0
    }
}

使用协议
协议本质上是一种类型,可以作为声明类型,但不能创建实例
协议变量的内存模型遵从实际类型的内存模型
引用类型传参、拷贝采用传引用方式
值类型转参、拷贝采用传值方式
检查协议类型
使用is检查类型是否实现了协议
使用as?和as!将类型下溯转型为协议

protocol AProtocol{
    func display()
}

class Base{
    var no=0
}

class Sub:Base,AProtocol{
    
    func display(){
        print(no)
    }
}

//Compile-time Type 编译时类型,声明类型
var item1, item2:Base

//Runtime Type 运行时类型,实际类型
item1=Base()
item2=Sub()

if(item1 is AProtocol){
    print("the runtime type of item1 conforms Shape protocol")
}

if(item2 is AProtocol){
    print("the runtime type of item2 conforms Shape protocol")
}

var item3:AProtocol?
var item4=Sub()
item3 = item1 as? AProtocol
item3 = item2 as? AProtocol

item3=item4

协议中的属性
协议中可以定义只读属性,也可以定义读写属性
协议中可以定义实例属性,也可以定义类型属性
协议中只能定义变量属性,不能定义常量属性
实现协议时,并不要求实现为存储属性,还是计算属性

协议中的方法
协议可以定义实例方法、也可以定义类型方法
协议中的方法不能定义参数的默认值
针对值类型的mutating协议方法
值类型(struct和enum)实现的实例方法如果要更改实例本身,需要在协议方法的定义中标明mutating关键字,同时在方法实现时也添加mutating关键字
添加了mutating的协议方法,对类的实现方法无影响。在类内实现mutating协议方法时,无需再添加mutating关键字

协议中的初始化器
协议中可以定义初始化器,但不可以定义析构器
当class中实现协议定义的初始化器时,需要添加required关键字
标明子类也需要提供该初始化器
如果定义为final类,则不需要required关键字
协议可以定义可失败的初始化器init?,具体实现时可以失败,也可以非失败

更多协议形式
协议继承
一个协议可以继承一个或多个协议
实现自协议的类型,可必须实现父协议中约定的成员
协议组合
可以使用protocol<A,B,...>来组合多个协议
实现组合协议的类型,必须实现组合协议中的每一个协议
组合协议是一个临时类型
可选协议
协议的某些成员可以定义为optional,不必实现
可选协议只能应用于class,不能应用于struct和enum
可选协议必须标明@objc特性(即使不需要和Objective-C互操作)

protocol AProtocol{
    func display()
}

//协议继承
protocol BProtocol: AProtocol{
    func invoke()
}

protocol CProtocol{
    func excute()
}

class ClassA:BProtocol{
    
    func display(){
        print("display")
    }
    func invoke(){
        print("invoke")
    } 
}

class ClassB:AProtocol,CProtocol{
    func display(){
        print("display")
    }
    
    func excute(){
        print("excute")
    }
}


//协议组合
func process( item: protocol<AProtocol,CProtocol>){
    
    item.display()
    item.excute()   
}

var b=ClassB()
process(b)

//可选协议
@objc protocol DProtocol {
    optional var discription: String { get }
    func move()
    
}

class ClassC: DProtocol{
    
    @objc func move(){    
    }
}

字符串

认识字符串 String
String是一个Unicode编码、16位字符的字符序列
String与NSString支持无缝互操作
String被定义为struct,值类型,拷贝时具有值语义
String是一个struct,但内部包含一个指向堆上的“内容指针”,其指向的对象存放真正的字符串内容。

String的内存模型

使用字符串
使用var和let来控制字符串的可变性

var str1 = "Hello"
let str2 = ",Swift"

获取子串中的字符

//枚举字符
for item in str1.characters{
    print(item)
}

for index in str1.characters.indices {
    print(str1[index])
}

print(str1[str1.startIndex])
//print(str1[str1.endIndex])
print(str1[str1.startIndex.successor()])
print(str1[str1.endIndex.predecessor()])
let index = str1.startIndex.advancedBy(4)
print(str1[index])

使用append和+链接字符串

//连接字符串
str1.appendContentsOf(",World")

var str3=str1+str2

str1+=str2

var char:Character="!"
str1.append(char)

使用字符转义

//字符转义
let heart = "\u{2665}"
print(heart)
let words = "\tHello"
print(words)

字符串插值(String Interpolation)

//字符串插值
var x=100
var y=200.0
var text="[\(x),\(y)]"

copy-on-write 共享技术
同一个字符串内容拷贝到不同的变量中时,“内容指针”不变,即不同变量“共享”同一份堆内存。从而实现“节省内存”。
如果某一个变量的字符串内容改变时,先将原来堆内存拷贝一份,“内容指针”指向新的拷贝,然后再更改新的拷贝。从而确保“正确性”。
copy-on-write的目的是实现“内容相同的字符串共享内存,同时又支持字符串随时改变”。
最佳实践:尽量不改变字符串,尽量使用常量字符串let

copy-on-write更改前

copy-on-write更改后

缓存容量与增长
字符串初始化后,会分配一个缓存容量capacity,其长度一般大于实际的字符数量
当字符串增长时,如果实际需求大于capacity,其capacity会以二倍的方式指数增长。伴随的代价:
分配新的堆内存2*capacity
将原来堆内存上的内容拷贝到新内存
释放原来堆内存
最佳实践:估计好capacity,预先分配好一定容量,避免以后capacity的增长
str.reserveCapacity(10000) //分配了10000个字节给capacity

集合类型

集合类型有数组(Array),集合(Set),字典(Dictionary)三种。
认识数组
Array是一个有序的元素序列,支持随机存取,支持动态更新长度。
索引从0开始,索引访问越界会抛出运行时异常。
Array被定义为Struct,值类型,拷贝时具有值语义。
Array是一个Struct,但内部包含一个指向堆上的“元素指针”,其指向的对象存放真正的数组元素。
如果数组元素为值类型,拷贝数组时,元素包含值本身。
如果数组元素为引用类型,拷贝数组是,元素引用指向相同的对象。

数组的内存模型

使用数组
使var和let来控制数组的常量性:数组长度和元素内容都不能更改。

//变量数组和常量数组
var array5=[1,2,3]
let array6=[1,2,3]

数组遍历
使用for循环访问array[index]需要检查索引越界,具有性能代价
尽可能使用for-in遍历数组元素;或使用Array.enumerate()遍历索引;二者编译器会优化掉索引检查
尽量避免使用insert和remove操作,因为会改变数组元素序列,涉及到大量的内存拷贝操作,代价较高。

//数组操作
for item in array5{
    print(item)
}

for(index, value) in array5.enumerate(){
    print("\(index): \(value)")
}

for index in 0..<array5.count {
    print(array5[index])
}

for var index=0;index<array5.count;index++ {
    print(array5[index])
}

缓存容量与增长
数组初始化后,会分配一个缓存容量capacity,其长度一般大于实际的元素数量
当数组长度增加时,如果实际需求大于capacity,其capacity会以近似二倍的方式指数增长。伴随的代价:
分配新的堆内存2*capacity
将原来堆内存上的元素拷贝到新内存
释放原来的堆内存
最佳实践:估计好capacity,预先分配好一定容量,避免以后capacity的增长
copy-on-write 共享技术:与String的实现原理一样,详情请看String的copy-on-write 共享技术。

认识集合
Set是一个无序的集合,其存储的值不能重复
Set中的元素必须有哈希值,即支持Hashable协议
Set被定义为Struct,值类型,拷贝时具有值语义
Set也是一个Struct,但内部包含一个指向堆上的“元素指针”,其指向的对象存放真正的元素。

var words = Set<String>()

words.insert("Hello")
words.insert("Swift")
words.insert("Language")
words.insert("Swift")

认知字典
Dictionary是一个存储key-value的无序的集合,key唯一,value可重复
Dictionary中的key必须有哈希值,即支持Hashable协议
Dictionary被定义为Struct,值类型,拷贝时具有值语义
Dictionary也是一个Struct,但内部包含一个指向堆上的“元素指针”,其指向的对象存放真正的元素。

var dictionary1 = [String:Int]()
var dictionary2 : Dictionary<String,Int>
dictionary2=["Jason":36, "Tom":31, "Marty":44]

推荐阅读更多精彩内容