本文大部分內(nèi)容翻譯至《Pro Design Pattern In Swift》By Adam Freeman,一些地方做了些許修改殷绍,并將代碼升級到了Swift2.0,翻譯不當之處望多包涵呆馁。
享元模式(The Flyweight Pattern)
使用共享物件桐经,用來盡可能減少內(nèi)存使用量以及分享資訊給盡可能多的相似物件;它適合用于只是因重復(fù)而導(dǎo)致使用無法令人接受的大量內(nèi)存的大量物件浙滤。通常物件中的部分狀態(tài)是可以分享阴挣。常見做法是把它們放在外部數(shù)據(jù)結(jié)構(gòu),當需要使用時再將它們傳遞給享元纺腊。
示例工程
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 類的初始化方法創(chuàng)建了一個列數(shù)26行數(shù)50的網(wǎng)格,并且將每個單元格的值設(shè)置成了所在行的值。 setValue方法用來改變指定位置單元格的值洪添。
Tip:全局方法==用來比較兩個Coordinate對象是否相等垦页。
理解享元模式解決的問題
享元模式定位的問題是創(chuàng)建大量完全相同的對象所帶來的影響,也就是大量的內(nèi)存消耗和時間消耗薇组。示例中Spreadsheet類網(wǎng)格中的每一個單元格創(chuàng)建了Cell 和 Coordinate對象外臂,這就意味著Spreadsheet類相對的創(chuàng)建了大量的對象坐儿。
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)")
運行程序律胀,輸出以下內(nèi)容:
SS1 Total: 33429
SS2 Total: 33567
Cells created: 2600
理解享元模式
2600個Cell對象每一個的創(chuàng)建都會消耗內(nèi)存和時間。享元模式通過識別和分離普通相似的數(shù)據(jù)并且分享它們貌矿,意味著實際上只有一個用于分享的對象被創(chuàng)建了炭菌。
享元模式用享元對象來管理組件請求的數(shù)據(jù)對象。享元對象將數(shù)據(jù)對象分為非固有的和固有的逛漫。非固有的數(shù)據(jù)對于請求組件來說是共同的黑低,固有數(shù)據(jù)是獨特的。
享元模式通過分享非固有的數(shù)據(jù)來最小化消耗酌毡。因為請求組件都分享同一個數(shù)據(jù)集合克握,所以非固有的數(shù)據(jù)顯然是不可變的。
固有的數(shù)據(jù)不能分享枷踏,所以享元模式的效果取決于非固有數(shù)據(jù)對象和固有數(shù)據(jù)對象的比例菩暗。
實現(xiàn)享元模式
創(chuàng)建享元協(xié)議
享元模式并非一定需要創(chuàng)建協(xié)議,只是協(xié)議可以讓我們注意到暴露給請求組件的的數(shù)據(jù)旭蠕,因為我們必須在協(xié)議里面明確的定義屬性和方法停团。
Flyweight.swift
import Foundation
protocol Flyweight {
subscript(index:Coordinate) -> Int? { get set }
var total:Int { get }
var count:Int { get }
}
Spreadsheet類中的數(shù)據(jù)對象存儲在字典中,這一點也反映在了享元協(xié)議里掏熬。這里我們定義的下標(subscript)允許用Coordinate 作為鍵來設(shè)置和獲取值佑稠,并且count屬性用來返回固有數(shù)據(jù)對象的個數(shù)。
創(chuàng)建享元實現(xiàn)類
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:注意到享元類沒有修改非固有的數(shù)據(jù)(extrinsicData)或者允許請求組件修改它旗芬,因為非固有數(shù)據(jù)是共享的舌胶。
并發(fā)訪問保護
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
}
}
最后我們再次運行,得到下面結(jié)果:
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)")
運行結(jié)果:
Comparison: true
Identity: true