GeekBand Swift高级编程(第三周)

泛型编程

认知泛型 Generics
泛型是一种参数化类型的机制(类型站位符),为算法和类型的实现提供了:
更高的复用性
更强的安全型
更好的性能

泛型支持包括:函数、类、结构、枚举

//泛型类型
class Stack<T> {
    
    var items = [T]()
    
    func push(item: T) {
        items.append(item)
    }
    
    func pop() -> T {
        return items.removeLast()
    }
}

var stack1=Stack<Int>()  //将占位符T换成Int
stack1.push(10)
stack1.push(20)

var s2 = Stack<String>()  //使用String版本的函数
s2.push("Hello")
s2.push("World")
print(s2.dynamicType)  //将打印出泛型函数实例s2的动态类型,即Stack<String>

//泛型函数
func swap<T>(inout first:T, inout second:T){
    let temp=first
    first=second
    second=temp
}

var data1=100.87
var data2=200.22
swap(&data1, &data2) //泛型函数会根据参数的类型进行自动类型推断

//继承一个泛型类时,替换类型必须要和基类一样(这里都是T)
class ExStack<T> : Stack<T> {
        //...
}

//这里基类的占位符是U,子类的是T,这样不可以
class ExStack2<T> : Stack<U> {
        //...
}

//基类明确使用Int,子类用占位符这种情况是可以的
class ExStack3<T> : Stack<Int> {
        //...
}

//基类明确使用Int,子类不使用泛型也是可以的
class ExStack4 : Stack<Int> {
        //...
}

//总结:基类的占位符要么来自子类相同的站位符,要么在继承时先确定好占位符的具体类型。

类型参数实例化
“泛型类型的实例化”即用实际类型替换占位符。泛型类型参数实例化的过程可以采用自动类型推断
Swift采用编译时泛型模型,即在编译时进行泛型类型的实例化,产生新的类型吗或函数码。
如果类型参数是值类型,泛型实例化会为不同的值类型生成不同的类型码(尺寸不同)。
如果类型参数是引用类型,泛型实例化会为所有不同的引用类型生成同一份类型吗(因为引用类型都是相同等尺寸的指针)

协议的关联类型
通过typealias可以为protocol定义一到多个关联类型。
在实现协议时,可将关联类型设置为泛型参数(或自动类型推断)
协议的关联类型可以看作是支持“泛型版的协议”。

protocol Container {
    typealias ItemType //关联类型,也是一个占位符,自己命名
    
    func append(item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}

class Stack<T>: Container {
    
    typealias ItemType=T //ItemType是Container 里面的占位符
    
    var items = [T]()
    
    func push(item: T) {
        items.append(item)
    }
    
    func pop() -> T {
        return items.removeLast()
    }
    
    func append(item: T) {
        self.push(item)
    }
    
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> T {
        return items[i]
    }
}

泛型约束
类型约束为泛型类型参数提供了更多的功能约定
Swift要求显示的类型约束,编译器在泛型类型参数实例化时会检查约束的有效性。
泛型类型约束支持两类约束
基类约束
协议约束

//协议约束:Comparable用来约束占位符T必须支持可比较大小,即可以使用>或<符号
//如果将Comparable换成一个类,就叫基类约束, 内部只可以实现基类所支持的函数
func max<T:Comparable>(array:[T])-> T {
      var value = array[0]
      for index in 1..<array.count {
            if array[index] > value {
                value = array[index]
            }
       }
       return value
}

Where子句约束:指定关联类型必须符合某种协议、或者和其他关联类型相等。

//用where关键字,将T.ItemType约束成支持相等符号的实例
func find<T:Container where T.ItemType: Equatable>(sequence :T, item:T.ItemType)->Bool{ 
    
    for index in 0..<sequence.count{
        
        if(sequence[index]==item){
            return true
        }
    }
    return false
}

函数类型与闭包

**函数类型 **
函数类型定义:(参数类型1,参数类型2,...)->返回值类型

函数类型是一种引用类型,类似于函数指针。可以将函数类型应用于任何使用类型的地方:变量、参数、返回值。

函数类型实例化支持:
全局函数
嵌套函数
成员函数(实例方法 与 静态方法)

func add(x:Double, y:Double)->Double {
        return x+y
}

//变量compute是一个函数类型,该函数类型有两个Double参数,返回值是Double类型
var compute: (Double, Double)->Double

compute = add  //将函数add赋值给了相同函数类型变量compute,这里的add不用加(),add()表示调用函数。

let result = compute(100,200) //此时调用compute(100,200)等于调用add(100,200)
函数类型的内存图

上图compute在栈空间有一个地址,指向堆空间的内容,堆空间的内容有两个指针,一个是对象指针,如果是实例方法,这个指针指向的是实例对象的地址,如果是全局函数这个指针为0。第二个指针是函数指针,指向函数的地址。

认识闭包Closure
闭包是函数类型的实例,一段自包含的代码块,可被用于函数类型的变量、参数或返回值

三种闭包形式:
全局函数:具名函数,但不捕获任何值
嵌套函数:在函数内部嵌套定义具名函数,可捕获包含函数中的值
闭包表达式:匿名函数类型实例。不具名的代码块,可捕获上下文的值

闭包是引用类型,闭包变量拷贝具有引用语义。

闭包和函数类型实例拥有同样的内存模型。

//闭包表达式,{(first:Rectangle, second:Rectangle)->Bool in return first.width*first.length<=second.width*second.length} 就是一个函数闭包
rects.sort({(first:Rectangle, second:Rectangle)->Bool in
      return first.width*first.length<=second.width*second.length
})

闭包表达式

{ (参数1,参数2... )->返回值类型 in
     语句块
}

闭包表达式的几种简化缩写形式:
自动类型推断:省略参数类型和返回值类型。

rects.sort({ first, second in
      return first.width*first.length<=second.width*second.length
})

单表达式闭包可以省去return关键字。

rects.sort({ first, second in
   first.width*first.length<=second.width*second.length
})

使用参数缩略形式$0,$1...省略参数声明和in

rects.sort({ $0.width*$0.length<=$1.width*$1.length
})

将操作符函数自动推导为函数类型
尾随闭包:当闭包表达式为函数最后一个参数,可将其写在括号后。

//尾随闭包,如果只有一个参数,可以把()去掉
//rects.sort(),如果有闭包参数将闭包参数放在最后
rects.sort {
      first, second in
      fitst.width*first.length<=second.width*second.length
}

自动闭包:不接受任何参数,直接返回表达式的值。允许延迟计算。

var cities = ["Beijing","Shanghai","New York", "Paris","London"]
print(cities.count)  //此时的城市数是5

let filter = { cities.removeLast() } //()->String 将闭包赋值给filter,此时还没有执行removeLast()

print(cities.count) //由于上一句并没有执行removeLast()此时的城市数还是5

print("Deleting \(filter())!") //执行了filter的闭包。 显示Deleting London!

print(cities.count) //此时的城市数是4, London被删除掉了

函数类型与闭包的变量捕获
函数类型和闭包可以捕获其所在上下文的任何值:
函数参数
局部变量
对象实例属性
全局变量
类的类型属性

//捕获实例属性
class Rectangle{
      var width = 0
      var length = 0
     
      func getComputHandler()-> ()->Int {
          return {
               return self.width*self.length //这个闭包就捕获了实例属性self.width和self.length
          }
      }
}

//捕获参数或局部变量
func addHandler(step: Int)-> ()->Int{
        var sum = 0
      return{
          sum +=step  //这里捕获了参数step和局部变量sum 
          return sum
      }
}

let addByTen = addHandler(10)

print(addByTen())  //显示结果 10
print(addByTen())  //显示结果 20
print(addByTen())  //显示结果 30

如果捕获值生存周期小于闭包对象(参数和局部变量),系统会将被捕获的值封装在一个临时对象里,然后再闭包对象上创建一个对象指针,指向该临时对象。

临时对象和闭包对象之间是强引用关系,生存周期跟随闭包对象。

扩展 Extension

扩展支持为现有类型增加功能(可以没有源代码),支持一下类型:类、结构、枚举、协议
除了不能增加实例存储属性和析构器外(因为扩展不能改变原来内的内存模型),使用扩展可以增加:
计算属性(实例属性和类型属性)
类型存储属性
方法(实例化方法和类型方法)
初始化器
下标
为类型实现协议
内嵌类型

class Rectangle:Shape {
      var x:Int
      var y:Int
      var length:Int
      var width:Int

      init(x:Int, y:Int, length:Int, width:Int) {
        self.x = x
        self.y = y
        self.length = length
        self.width = width
       }
}

//对Rectangle类增加一个扩展
extension Rectangle {
      static var max = 100

     //增加了一个实例计算属性
      var area: Int {
            return self.length*self.width
      }
       
       //增加了一个方法
    func moveTo(x:Int, _ y:Int) {
         self.x = x
         self.y = y
    }
  
      //增加了一个新的便捷初始化器
    convenience init(x1:Int, y1:Int, x2:Int, y2:Int){
        let l = abs(x2 - x1)
        let w = abs(y2 - y1)
    }
}

使用扩展的注意点
扩展不可以增加实例存储属性,但可以增加类型的存储属性。换言之,扩展不能更改实例对象的内存模型。
扩展只能增加便捷初始化器,不能增加指定初始化器。
扩展中添加新的方法,但不可以override基类方法。
结构和枚举的扩展,如果实例方法更改self,需要声明mutating

协议与扩展
扩展实现协议:在为类型实现扩展时,增加实现某些协议。
实现过程中必须实现协议的所有成员。
也可以添加实现非协议的成员。

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

//扩展实现协议,必须实现Drawable里的所有成员
extension Rectangle:Drawable{
      var discription:String{
          return"x=\(x), y=\(y), length=\(length), width=\(width)"
      }
      func draw() {
            print(self.discription)
      }
      //自己增加的非协议的成员实现
      func erase() {
            print("erase")
      }
 }

扩展协议类型:可以扩展协议来提供方法和属性的默认实现。
协议扩展内的成员都必须提供默认实现,可以调用原协议内的成员
所有遵守协议的类型自动继承这些默认实现,但可以提供自己独特的实现
扩展协议时,也可以添加where子句约束

//扩展协议类型
extension Drawable{
      //默认实现
      func encode()( {
            print("Drawable.encode")
            print(self.discription)
      }
}

使用扩展的建议
使用场景
适合在没有源代码的情况下,向已经封装的类中添加方法或属性。
为一个类在某些特殊场景下增加功能。
对于复杂的大型文件分割实现。

添加扩展
自己创建的类
系统的类
第三方库

内存管理

ARC是Swift默认的内存管理机制,其针对堆上的对象,由编译器自动生成操作引用计数的指令(retain或release)来管理对象的引用计数增加或减少。程序员无法手工控制。
哪些类型的对象受ARC管理:类class、闭包closure(存放在堆上的对象)
哪些对象不受ARC管理:基础数值类型,结构struct、枚举enum、元组tuple

关于Auto Release
Swift原生对象不支持autorelease消息,也就没有必要使用Autorelease Pool来管理内存峰值。

如果Swift调用Objective-C函数返回autorelease对象,那么如果出现内存峰值过高的风险,仍然需要使用Autorelease Pool来管理

Autorelease Pool在Swift中使用尾随闭包形式实现。

func useObjecAutoreleaseObject(){
        if let filepath = NSBundle.mainBundle().pathForResource("2015",ofType:"jpg") {
      for _ in 0..<5 {
            autoreleasepool {//在闭包内调用autoreleasepool,每当
                  for _ in 0..<1000 {
                        let data = NSData.dataWithContentsOfMappedFile(filepath)
                  }
            }
      }
}          

循环引用于内存泄漏
对象间的循环引用会造成ARC引用计数无法释放被引用的任何一个对象,从而造成内存泄漏。

//假设有一个Computer类,其中有个属性是Monitor类的实例。有一个Monitor类,其中有一个属性是Computer类的实例
var imac:Computer?
var screen:Monitor?
imac = Computer(name:"Jason's iMac")
screen = Monitor(no:29)

//造成循环引用
imac!.display = screen
screen!.device = imac

imac = nil
screen = nil //由于循环引用,这样并不能释放内存
循环引用的内存图

解决循环引用造成的内存泄漏有三种方式:
1.在合适的地方,手动将循环引用解除。

var imac:Computer?
var screen:Monitor?
imac = Computer(name:"Jason's iMac")
screen = Monitor(no:29)

imac!.display = screen
screen!.device = imac

imac!.display = nil //手动的释放了实例imac!中的引用
imac = nil
screen = nil //由于之前手动的释放了一边的引用,所以这里可以释放内存

2.如果允许对象引用为nil,可将引用声明为弱引用(weak)

 class Monitor{
      var no:Int
      weak var device:Computer? //设置成弱引用,弱引用使用时将不再让ARC计数加一,并且这个值可以为nil,如果Computer的实例提前释放的话
      init(no:Int) {
            self.no = no
            print("Monitor init")
      }
      deinit{
            print("Monitor deinit")
      }
}
弱引用内存图

3.如果不允许对象引用为nil,可将对象声明为无主引用(unowned)

class Monitor{
      var no:Int
      unowned var device:Computer //设置无主引用,使用时将不再让ARC计数加一,但是无主引用不可为nil,如果Computer的实例提前释放,将还会指向原来的地址,要确保不要访问,不然会抛出异常
      init(no:Int) {
            self.no = no
            print("Monitor init")
      }
      deinit{
            print("Monitor deinit")
      }
}
无主引用的内存模型

弱引用在对象被释放后,ARC会将引用设置为nil。无主引用在对象被释放后,ARC不会设置nil,访问时会抛运行时错误(空悬指针)。

闭包的内存管理
如果闭包满足以下两个条件,将产生循环引用,造成内存泄露:
1.闭包内使用实例成员,会捕获self,产生“从闭包对象->self的引用”。
2.将闭包对象设置为self的属性,产生“从self->闭包对象的引用”。

class Employee {
        var name:String
        var printer:(()->())? //设置一个闭包属性

        init(name:String) {
            self.name = name
            self.printer = { //初始化闭包值的时候又将self.name捕获了。之后又将这个捕获值赋值给了self里的属性printer,从而形成了循环引用
                  print("name: \(self.name)")
            }
            print("Employee init")
        }
}
闭包属性循环引用内存图

解决闭包产生的循环引用有以下方式:
1.手工解决循环引用

var employee:Employee?

employee?.printer = nil  //手动断开循环指针
employee = nil

2.在捕获列表中指定[weak self]弱引用

class Employee {
        var name:String
        var printer:(()->())? 

        init(name:String) {
            self.name = name
            self.printer = { 
                 [weak self] in //指定了弱引用,只有在闭包里面的捕获列表才可以这么设置                
                 print("name: \(self!.name)")
            }
            print("Employee init")
        }
}
weak self循环引用内存图

3.在捕获列表中指定[unowned self]无主引用

class Employee {
        var name:String
        var printer:(()->())? 

        init(name:String) {
            self.name = name
            self.printer = { 
                 [unowned self] in //指定了无主引用,只有在闭包里面的捕获列表才可以这么设置                
                 print("name: \(self.name)")
            }
            print("Employee init")
        }
}
unowned self循环引用内存图

Weak-Strong Dance
对象在弱引用或无主引用期间,随时“有可能”被释放,从而导致引用为nil,或者抛运行时错误。

为了避免在弱引用使用期间,对象被释放可能导致nil的问题,可以使用Weak-Strong Dance来解决,其核心是延长对象生存周期,确保对象不被释放,有两种方式:
1.将弱引用临时转换为“强引用局部变量”。

{ [weak self] in
  if let strongSelf = self {
      strongSelf.process("first process")
      usleep(500)
      strongSelf.process("second process")
  }
}

2.使用withExtendedLifetime函数来延长弱引用对象生存周期。

{ [weak self] in
   withExtendedLifetime(self) {
      self?.process("first process")
      usleep(500)
      self?.process("second process")
  }
}

推荐阅读更多精彩内容