美文网首页
GeekBand Swift高级编程(第三周)

GeekBand Swift高级编程(第三周)

作者: Hysoka | 来源:发表于2016-05-27 23:23 被阅读253次

    泛型编程

    认知泛型 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")
      }
    }
    

    相关文章

      网友评论

          本文标题:GeekBand Swift高级编程(第三周)

          本文链接:https://www.haomeiwen.com/subject/zgiirttx.html