本文大部分內容翻譯至《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