本文大部分内容翻译至《Pro Design Pattern In Swift》By Adam Freeman,一些地方做了些许修改,并将代码升级到了Swift2.0,翻译不当之处望多包涵。
享元模式(The Flyweight Pattern)
使用共享物件,用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件;它适合用于只是因重复而导致使用无法令人接受的大量内存的大量物件。通常物件中的部分状态是可以分享。常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元。
示例工程
Xcode OS X Command Line Tool工程:
Spreadsheet.swift
func == (lhs: Coordinate, rhs: Coordinate) -> Bool {
return lhs.col == rhs.col && lhs.row == rhs.row
}
class Coordinate : Hashable, CustomStringConvertible {
let col:Character
let row:Int
init(col:Character, row:Int) {
self.col = col
self.row = row
}
var hashValue: Int {
return description.hashValue
}
var description: String {
return "\(col)(\row)"
}
}
class Cell {
var coordinate:Coordinate
var value:Int
init(col:Character, row:Int, val:Int) {
self.coordinate = Coordinate(col: col, row: row)
self.value = val
}
}
class Spreadsheet {
var grid = Dictionary<Coordinate, Cell>()
init() {
let letters:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
var stringIndex = letters.startIndex
let rows = 50
repeat {
let colLetter = letters[stringIndex]
stringIndex = stringIndex.successor()
for rowIndex in 1 ... rows {
let cell = Cell(col: colLetter, row: rowIndex, val: rowIndex)
grid[cell.coordinate] = cell
}
} while (stringIndex != letters.endIndex)
}
func setValue(coord: Coordinate, value:Int) {
grid[coord]?.value = value
}
var total:Int {
return grid.values.reduce(0){
total,cell -> Int in
return total + cell.value
}
}
}
Spreadsheet 类有一个键是Coordinate 对象,值是Cell对象的字典属性。Coordinate类有column和row 属性,例如A45就是指列是A,行是45的单元格。Cell对象用来在指定的单元格存储一个Int值,同时它也有该单元格的Coordinate值。Spreadsheet 类的初始化方法创建了一个列数26行数50的网格,并且将每个单元格的值设置成了所在行的值。 setValue方法用来改变指定位置单元格的值。
Tip:全局方法==用来比较两个Coordinate对象是否相等。
理解享元模式解决的问题
享元模式定位的问题是创建大量完全相同的对象所带来的影响,也就是大量的内存消耗和时间消耗。示例中Spreadsheet类网格中的每一个单元格创建了Cell 和 Coordinate对象,这就意味着Spreadsheet类相对的创建了大量的对象。
main.swift
let ss1 = Spreadsheet()
ss1.setValue(Coordinate(col: "A", row: 1), value: 100)
ss1.setValue(Coordinate(col: "J", row: 20), value: 200)
print("SS1 Total: \(ss1.total)")
let ss2 = Spreadsheet()
ss2.setValue(Coordinate(col: "F", row: 10), value: 200)
ss2.setValue(Coordinate(col: "G", row: 23), value: 250)
print("SS2 Total: \(ss2.total)")
print("Cells created: \(ss1.grid.count + ss2.grid.count)")
运行程序,输出以下内容:
SS1 Total: 33429
SS2 Total: 33567
Cells created: 2600
理解享元模式
2600个Cell对象每一个的创建都会消耗内存和时间。享元模式通过识别和分离普通相似的数据并且分享它们,意味着实际上只有一个用于分享的对象被创建了。
享元模式用享元对象来管理组件请求的数据对象。享元对象将数据对象分为非固有的和固有的。非固有的数据对于请求组件来说是共同的,固有数据是独特的。
享元模式通过分享非固有的数据来最小化消耗。因为请求组件都分享同一个数据集合,所以非固有的数据显然是不可变的。
固有的数据不能分享,所以享元模式的效果取决于非固有数据对象和固有数据对象的比例。
实现享元模式
创建享元协议
享元模式并非一定需要创建协议,只是协议可以让我们注意到暴露给请求组件的的数据,因为我们必须在协议里面明确的定义属性和方法。
Flyweight.swift
import Foundation
protocol Flyweight {
subscript(index:Coordinate) -> Int? { get set }
var total:Int { get }
var count:Int { get }
}
Spreadsheet类中的数据对象存储在字典中,这一点也反映在了享元协议里。这里我们定义的下标(subscript)允许用Coordinate 作为键来设置和获取值,并且count属性用来返回固有数据对象的个数。
创建享元实现类
Flyweight.swift
import Foundation
protocol Flyweight {
subscript(index:Coordinate) -> Int? { get set }
var total:Int { get }
var count:Int { get }
}
class FlyweightImplementation : Flyweight {
private let extrinsicData:[Coordinate: Cell]
private var intrinsicData:[Coordinate: Cell]
private init(extrinsic:[Coordinate: Cell]) {
self.extrinsicData = extrinsic
self.intrinsicData = Dictionary<Coordinate, Cell>()
}
subscript(key:Coordinate) -> Int? {
get {
if let cell = intrinsicData[key] {
return cell.value;
} else {
return extrinsicData[key]?.value
}
}
set (value) {
if (value != nil) {
intrinsicData[key] = Cell(col: key.col,
row: key.row, val: value!)
}
}
}
var total:Int {
return extrinsicData.values.reduce(0){
total,cell -> Int in
if let intrinsicCell = self.intrinsicData[cell.coordinate] {
return total + intrinsicCell.value;
} else {
return total + cell.value
}
}
}
var count:Int {
return intrinsicData.count
}
}
Tip:注意到享元类没有修改非固有的数据(extrinsicData)或者允许请求组件修改它,因为非固有数据是共享的。
并发访问保护
Flyweight.swift
import Foundation
protocol Flyweight {
subscript(index:Coordinate) -> Int? { get set }
var total:Int { get }
var count:Int { get }
}
extension Dictionary {
init(setupFunc:(() -> [(Key, Value)])) {
self.init()
for item in setupFunc() {
self[item.0] = item.1
}
}
}
class FlyweightFactory {
class func createFlyweight() -> Flyweight {
return FlyweightImplementation(extrinsic: extrinsicData)
}
private class var extrinsicData:[Coordinate: Cell] {
get {
struct singletonWrapper {
static let singletonData = Dictionary<Coordinate, Cell> (
setupFunc: {() in
var results = [(Coordinate, Cell)]()
let letters:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
var stringIndex = letters.startIndex
let rows = 50
repeat {
let colLetter = letters[stringIndex]
stringIndex = stringIndex.successor()
for rowIndex in 1 ... rows {
let cell = Cell(col: colLetter, row: rowIndex,
val: rowIndex)
results.append((cell.coordinate, cell))
}
} while (stringIndex != letters.endIndex)
return results
})
}
return singletonWrapper.singletonData
}
}
}
class FlyweightImplementation : Flyweight {
private let extrinsicData:[Coordinate: Cell]
private var intrinsicData:[Coordinate: Cell]
private let queue:dispatch_queue_t
private init(extrinsic:[Coordinate: Cell]) {
self.extrinsicData = extrinsic
self.intrinsicData = Dictionary<Coordinate, Cell>()
self.queue = dispatch_queue_create("dataQ", DISPATCH_QUEUE_CONCURRENT)
}
subscript(key:Coordinate) -> Int? {
get {
var result:Int?
dispatch_sync(self.queue){[weak self] in
if let cell = self!.intrinsicData[key]{
result = cell.value
}else{
result = self!.extrinsicData[key]?.value
}
}
return result
}
set (value) {
if (value != nil) {
dispatch_barrier_sync(self.queue){[weak self] in
self!.intrinsicData[key] = Cell(col: key.col,
row: key.row, val: value!)
}
}
}
}
var total:Int {
var result = 0
dispatch_sync(self.queue){ [weak self] in
result = self!.extrinsicData.values.reduce(0){ total,cell -> Int in
if let intrinsicCell = self!.intrinsicData[cell.coordinate] {
return total + intrinsicCell.value
} else {
return total + cell.value
}
}
}
return result
}
var count:Int {
var result = 0
dispatch_sync(self.queue){ [weak self] in
result = self!.intrinsicData.count
}
return result
}
}
接着修改Spreadsheet类:
Spreadsheet.swift
func == (lhs: Coordinate, rhs: Coordinate) -> Bool {
return lhs.col == rhs.col && lhs.row == rhs.row
}
class Coordinate : Hashable, CustomStringConvertible {
let col:Character
let row:Int
init(col:Character, row:Int) {
self.col = col
self.row = row
}
var hashValue: Int {
return description.hashValue
}
var description: String {
return "\(col)(\row)"
}
}
class Cell {
var coordinate:Coordinate
var value:Int
init(col:Character, row:Int, val:Int) {
self.coordinate = Coordinate(col: col, row: row)
self.value = val
}
}
class Spreadsheet {
var grid:Flyweight
init() {
grid = FlyweightFactory.createFlyweight()
}
func setValue(coord: Coordinate, value:Int) {
grid[coord] = value
}
var total:Int {
return grid.total
}
}
最后我们再次运行,得到下面结果:
SS1 Total: 33429
SS2 Total: 33567
Cells created: 1304
Cocoa中的享元模式
看下面代码:
import Foundation
let num1 = NSNumber(int: 10)
let num2 = NSNumber(int: 10)
print("Comparison: \(num1 == num2)")
print("Identity: \(num1 === num2)")
运行结果:
Comparison: true
Identity: true