iOS 实用技术Swift编程iOS Developer

结构体和类(一)

2017-03-25  本文已影响172人  SmartisanBool

结构体和类模块分两篇笔记来学习:

本篇开始学习第一部分,go!

1.结构体(和枚举)和类的区别

2.实体和值

3.不可控制的可变性 (为何不用类)

4.值类型

引用一个例子做对比引用类型和值类型:

 let inputParameters = [
    kCIInputRadiusKey: 10,
    kCIInputImageKey: image
 ]

let blurFilter = CIFilter(name: "CIGaussianBlur",
withInputParameters: inputParameters)!
let secondBlurFilter = blurFilter
secondBlurFilter.setValue(20, forKey: kCIInputRadiusKey)

CIFilter是CoreData里的一个滤镜类,第二个滤镜的配置变动会影响第一个滤镜的配置。可以通过复制时手动复制来避免影响:

let otherBlurFilter = blurFilter.copy() as! CIFilter
otherBlurFilter.setValue(20, forKey: kCIInputRadiusKey)”

接下来使用结构体来实现:

struct GaussianBlur {
  var inputImage: CIImage
  var radius: Double
}
var blur1 = GaussianBlur(inputImage: image, radius: 10)
blur1.radius = 20
var blur2 = blur1
blur2.radius = 30”

这种复制听上去有点浪费,但是编译器会为我们优化,此外如果如果使用写时复制的话,实际对数据的复制只会在其中某个值实际改变的时候才会发生。其实这也是 Swift 数组的工作方式。接下来使用扩展对GaussianBlur 结构体上添加一个 outputImage 属性:

  extension GaussianBlur {
    var outputImage: CIImage {
        let filter = CIFilter(name: "CIGaussianBlur", withInputParameters: [kCIInputImageKey: inputImage,kCIInputRadiusKey: radius])!
        return filter.outputImage!
    }

上面的代码在每次 outputImage 被访问时都会创建一个新的滤镜。这并不是很高效的做法,如果我们多次访问这个属性,会有好多新的滤镜被创建出来。

为了避免这种性能的浪费,采用另一种高效的做法,不像上面那样将值存在结构体中,这次采用直接存储CIFilter实例,然后通过自定义属性进行修改:

struct GaussianBlur {
  private var filter: CIFilter
  init(inputImage: CIImage, radius: Double) {
    filter = CIFilter(name: "CIGaussianBlur", withInputParameters: [
      kCIInputImageKey: inputImage,
      kCIInputRadiusKey: radius
    ])!
  }
 }

接下来,我们实现 inputImage 和 radius 属性,来直接访问和修改滤镜的属性:

extension GaussianBlur {
  var inputImage: CIImage {
    get { return filter.valueForKey(kCIInputImageKey) as! CIImage }
    set { filter.setValue(newValue, forKey: kCIInputImageKey) }
  }

  var radius: Double {
    get { return filter.valueForKey(kCIInputRadiusKey) as! Double }
    set { filter.setValue(newValue, forKey: kCIInputRadiusKey) }
  }
}

最后,要取出输出图像,我们可以直接使用滤镜上的 outputImage 属性:

extension GaussianBlur {
    var outputImage: CIImage {
      return filter.outputImage!
    }
}

此时可以正常的满足需求了:

var blur = GaussianBlur(inputImage: image, radius: 25)
blur.outputImage

但是如果复制结构体的话就会有个问题:其中的所有值类型将被复制,而引用类型却只有对于对象的引用会被复制,对象本身不被复制。如果对blur进行复制产生otherBlur时,blur和otherBlur中的滤镜将指向同一个CIFilter实例!

5.写时复制

Swift 在实现值语义的结构体时,其底层使用了可变的对象,这也正是 Swift 中最强大的特性之一。我们从值语义里获益良多,同时又可以保持代码高效。然而,正如我们所看到的,这种做法可能导致意外的数据共享。

通过写时复制这种技术可以实现:在每次结构体被改变时,去复制一个封装后的对象,以此来避免共享。其实Swift 的很多数据结构都是以这种方式工作的。

配合上一节的滤镜例子,我们可以通过写时复制来避免滤镜的共享:

extension GaussianBlur {
  private var filterForWriting: CIFilter {
    mutating get {
      filter = filter.copy() as! CIFilter
      return filter
    }
   }
  }

mutating修饰符作用是使方法能够修改结构体的变量。这让我们得以更改滤镜的设置方法,在改变值之前先进行复制:

extension GaussianBlur {
  var inputImage: CIImage {
    get { return filter.valueForKey(kCIInputImageKey) as! CIImage }
    set {
        filterForWriting.setValue(newValue, forKey: kCIInputImageKey)
    }
  }

  var radius: Double {
    get { return filter.valueForKey(kCIInputRadiusKey) as! Double }
    set {
      filterForWriting.setValue(newValue, forKey: kCIInputRadiusKey)
    }
  }
}

上面这种方式可以按照预想工作,但是却带来了性能上的代价:每次我们改变滤镜值的时候,滤镜都会被复制,即便是我们只是做一些本地修改,而没有共享结构体时,这个复制也会发生”

6.高效的写时复制

在 Swift 中有一个方法,isUniquelyReferencedNonObjC,它会检查一个类的实例是不是唯一的引用。我们可以使用它来在保证结构体的值语义的同时,避免不必要的复制。

不幸的是,isUniquelyReferencedNonObjC 函数只对 Swift 对象有用,而 CIFilter 是一个 Objective-C 类。想要绕过这个限制,我们可以创建一个简单的 Box 封装类型,来把任意的 Swift 类封装进去:

final class Box<A> {
    var unbox: A
    init(_ value: A) { unbox = value }
}


struct GaussianBlur {

  private var boxedFilter: Box<CIFilter> = {
    var filter = CIFilter(name: "CIGaussianBlur",withInputParameters: [:])!
    filter.setDefaults()
    return Box(filter)
   }()

  var filter: CIFilter {
    get { return boxedFilter.unbox }
    set { boxedFilter = Box(newValue) }
  }
  
  private var filterForWriting: CIFilter {
    mutating get {
      if !isUniquelyReferencedNonObjC(&boxedFilter) {
        filter = filter.copy() as! CIFilter
        }
      return filter
    }
   }  

 }

这正是 Swift 数组内部的工作方式。当你创建一个新的数组的复制时,它背后的数据是一样的。只有当你要修改这个数组时,才会进行复制,这样一来,对该数组的更改不会影响到其他的数组。当在一个结构体中使用类时,我们需要保证它确实是不可变的。如果办不到这一点的话,我们就需要 (像上面那样的) 额外的步骤。或者就干脆使用一个类,这样我们的数据的使用者就不会期望它表现得像一个值。

7.闭包的可变性

闭包和函数也是引用类型的,如果进行复制,两个函数或闭包对象将共享同样的状态:

var i = 0
func uniqueInteger() -> Int {
    i += 1
    return i
}

let otherFunction: () -> Int = uniqueInteger

此时uniqueInteger和otherFunction将共同享用i变量。使用对闭包的再次封装,使得返回享用共同变量的闭包的方式来解决问题:

func uniqueIntegerProvider() -> () -> Int {
    var i = 0
    return {
        i += 1
        return i
    }
}
上一篇 下一篇

猜你喜欢

热点阅读