物件池模式
基本概念
物件池模式一般用來管理一組可重用物件,以供呼叫元件使用,它可以為元件提供多個完全相同的物件。元件可以從物件池中獲取物件,呼叫物件後,其他元件在該物件想歸還前都無法使用該物件。(Cocoa中 UITableViewCell的重用機制可以通過此模式和工廠模式共同實現)
好處
- 物件構建過程隱藏
- 物件池通過重用機制有效控制物件反覆重建造成的消耗。更好的控制記憶體
實現過程
- 初始化,準備需要的物件集合
- 借出物件,需要物件的元件從池子中借出物件
- 元件使用借到的物件完成任務,物件池保證這個物件在其被歸還之前不會再借給其他元件
- 元件返回物件給物件池
注意:
1、在多執行緒訪問中保護物件池資料陣列
2、確保每次請求都能獲得可用物件
程式碼示例:
下面程式碼將模擬一個簡單的圖書管理過程,包含圖書出借和歸還等。建立一個macos 命令列專案命名為ObjectPool
- 首先構造 Book類
class Book {
let author:String
let title:String
let stockNumber:Int
var reader:String?
var checkoutCount = 0
init(author:String,title:String,stock:Int) {
self.author = author
self.title = title
self.stockNumber = stock
}
}
複製程式碼
- 建立Pool類,這裡pool類僅代表物件池。為了方便複用,使用泛型建立。以便可以管理任何型別的物件
//Pool.swift
class Pool<T> {
private var data:[T] = []
init(items:[T]) {
data.reserveCapacity(data.count)
data.append(contentsOf: items)
}
func getFromPool() -> T? {
if data.count > 0 {
return data.remove(at: 0)
}
return nil
}
func returnPool(item:T) {
self.data.append(item)
}
}
複製程式碼
修改pool類加入執行緒保護
class Pool<T> {
private var data:[T] = []
private let dataProtectQueue = DispatchQueue(label: "com.ra.ObjectPool.Pool.DataProtectQueue")
private let semaphore:DispatchSemaphore
init(items:[T]) {
data.reserveCapacity(data.count)
data.append(contentsOf: items)
self.semaphore = DispatchSemaphore(value: items.count)
}
func getFromPool() -> T? {
var result:T?
if semaphore.wait(timeout: DispatchTime.distantFuture) == .success{
dataProtectQueue.sync {
result = self.data.remove(at: 0)
}
}
return result
}
func returnPool(item:T) {
dataProtectQueue.async {
self.data.append(item)
self.semaphore.signal()
}
}
}
複製程式碼
- 構造library單例類,用於管理圖書
final class Library{
static let shared:Library = Library(stockLevel: 2)
private var books:[Book] = []
private var pool:Pool<Book>
private init(stockLevel:Int){
for count in 1...stockLevel {
let book = Book(author: "xxxx", title: "Design Pattern in Swift", stock: count)
books.append(book)
}
self.pool = Pool(items: books)
}
func checkoutBook(reader:String) -> Book? {
let book = pool.getFromPool()
book?.reader = reader
book?.checkoutCount += 1
return book
}
func returnBook(_ book:Book) {
book.reader = nil
pool.returnPool(item: book)
}
func printReport() {
books.forEach { (book) in
print("....Book#\(book.stockNumber)....")
print("Checked out to \(book.checkoutCount) times ")
if let reader = book.reader{
print("Checked out to \(reader)")
}else{
print("in stock")
}
}
}
}
複製程式碼
然後在main.swift中做一個簡單呼叫
let queue = DispatchQueue(label: "work.queue", qos: DispatchQoS.default, attributes: .concurrent)
let group = DispatchGroup()
print("start")
for i in 0..<20{
let workItem = DispatchWorkItem(block: {
if let book = Library.shared.checkoutBook(reader: "reader#\(i)"){
Thread.sleep(forTimeInterval: TimeInterval(arc4random() % 2))
Library.shared.returnBook(book)
}
})
queue.async(group: group, execute: workItem)
}
_ = group.wait(timeout: DispatchTime.distantFuture)
print("all blocks complete")
Library.shared.printReport()
複製程式碼
下面是執行結果
Hello, World!
start
all blocks complete
....Book#1....
Checked out to 10 times
in stock
....Book#2....
Checked out to 10 times
in stock
Program ended with exit code: 0
複製程式碼
物件池模式的變體
基本概念
更改物件池的運作方式來適應不同的場景
物件池實現設計四種策略:
物件建立策略(物件的建立方式)
積極性策略,即物件在使用前就已經被建立(上面示例程式碼中pool類的初始化方法屬於該型別)
缺點:
- 在需求出現之前就已經花了建立和配置物件所需要的資源
- 建立和配置的物件有可能與需求不相符即物件不可用
惰性策略,即被動型,需要物件的時候才會被建立
程式碼示例:
在ObjectPool工程中建立BookSeller類。這裡的實現方式只是給需求方提供Book的獲取方法。其實現不重要
class BookSeller {
class func buyBook(author:String,title:String,stockNumber:Int) -> Book{
return Book(author: author, title: title, stock: stockNumber)
}
}
複製程式碼
修改pool類
class Pool<T> {
private var data:[T] = []
private let dataProtectQueue = DispatchQueue(label: "com.ra.ObjectPool.Pool.DataProtectQueue")
private let semaphore:DispatchSemaphore
private var itemCount:Int = 0
private let maxItemCount:Int
private let itemFactory:()->T
init(maxItemCount:Int,factory:@escaping ()->T) {
self.itemFactory = factory
self.maxItemCount = maxItemCount
self.semaphore = DispatchSemaphore(value: maxItemCount)
}
func getFromPool() -> T? {
var result:T?
if semaphore.wait(timeout: DispatchTime.distantFuture) == .success{
dataProtectQueue.sync {
if self.data.count == 0 && self.itemCount < self.maxItemCount{
result = self.itemFactory()
self.itemCount += 1
}else{
result = self.data.removeFirst()
}
}
}
return result
}
func returnPool(item:T) {
dataProtectQueue.async {
self.data.append(item)
self.semaphore.signal()
}
}
func processPoolItems(callBack:(([T]) -> Void)) {
dataProtectQueue.sync {
callBack(self.data)
}
}
}
複製程式碼
修改Library類中相關初始化方法
///修改初始化方法
private init(stockLevel:Int){
var stockId = 1
stockId += 1
self.pool = Pool(maxItemCount: stockLevel, factory: { () in
return BookSeller.buyBook(author: "Dickens,Charles", title: "Hard times", stockNumber: stockId)
})
}
///修改列印方法
func printReport() {
pool.processPoolItems { (books) in
books.forEach { (book) in
print("....Book#\(book.stockNumber)....")
print("Checked out to \(book.checkoutCount) times ")
if let reader = book.reader{
print("Checked out to \(reader)")
}else{
print("in stock")
}
}
}
}
複製程式碼
物件複用策略
物件池模式的本質決定了它所管理的物件會被重複分配給呼叫元件,這意味著返還的物件會處於非常正常狀態的風險
- 相信策略(預設所有返回物件都是可服用的)
- 不信任策略(物件返回給物件池之前進行檢查。不可用就拋棄)
程式碼修改: 建立PoolItem.Swift檔案。並建立協議
protocol PoolItem {
var canReuse:Bool{ get }
}
複製程式碼
修改Pool類以及returnToPool方法
class Pool<T> where T:AnyObject {
.....
func returnPool(item:T) {
dataProtectQueue.async {
///對歸還物件進行檢查
if let pitem = item as? PoolItem {
if pitem.canReuse {
self.data.append(item)
self.semaphore.signal()
}
}
}
}
.....
}
複製程式碼
Book類遵守並實現協議
class Book : PoolItem{
let author:String
let title:String
let stockNumber:Int
var reader:String?
var checkoutCount = 0
var canReuse: Bool{
get{
let reusable = checkoutCount < 5
if !reusable {
print("Eject : Book#\(self.stockNumber)")
}
return reusable
}
}
init(author:String,title:String,stock:Int) {
self.author = author
self.title = title
self.stockNumber = stock
}
}
複製程式碼
空池策略
物件池中沒有物件可滿足新的請求時,阻塞請求執行緒,強制讓發起物件請求的執行緒等待,值到有可用物件後再繼續執行
修改main.swift
// 修改為 35次。物件池最多隻能返回5個物件。此處修改只為了配合測空池策略
for i in 0..<35{
let workItem = DispatchWorkItem(block: {
if let book = Library.shared.checkoutBook(reader: "reader#\(i)"){
Thread.sleep(forTimeInterval: TimeInterval(arc4random() % 2))
Library.shared.returnBook(book)
}
})
queue.async(group: group, execute: workItem)
}
....
queue.sync {
print("all blocks complete")
Library.shared.printReport()
}
複製程式碼
修改pool類
...
func getFromPool(maxWaitSecond:Int = 5) -> T? {
var result:T?
let waitTime = (maxWaitSecond == -1) ? DispatchTime.distantFuture : DispatchTime(uptimeNanoseconds: UInt64(maxWaitSecond*Int(NSEC_PER_SEC)))
if semaphore.wait(timeout: waitTime) == .success{
dataProtectQueue.sync {
if self.data.count == 0 && self.itemCount < self.maxItemCount{
result = self.itemFactory()
self.itemCount += 1
}else{
result = self.data.removeFirst()
}
}
}
return result
}
...
複製程式碼
- 彈性物件
此處修改見 demo ObjectPool(彈性物件)
物件分配策略
- 先進先出
- 優先分配使用最少的
等