A generic object pool in Swift
The object pool sample is a creational design sample. The principle thought behind it’s that first you create a set of objects (a pool), then you definately purchase & launch objects from the pool, as an alternative of continually creating and releasing them. 👍
Why? Efficiency enhancements. For instance the Dispatch framework makes use of an object pool sample to present pre-created queues for the builders, as a result of making a queue (with an related thread) is an comparatively costly operation.
One other use case of the object pool sample is employees. For instance it’s important to obtain a whole lot of photos from the net, however you’d prefer to obtain solely 5 concurrently you are able to do it with a pool of 5 employee objects. Most likely it’ll be quite a bit cheaper to allocate a small variety of employees (that’ll really do the obtain process), than create a brand new one for each single picture obtain request. 🖼
What in regards to the downsides of this sample? There are some. For instance if in case you have employees in your pool, they may include states or delicate consumer knowledge. It’s important to be very cautious with them aka. reset all the pieces. Additionally in case you are working in a multi-threaded setting it’s important to make your pool thread-safe.
Right here is an easy generic thread-safe object pool class:
import Basis
class Pool<T> {
personal let lockQueue = DispatchQueue(label: "pool.lock.queue")
personal let semaphore: DispatchSemaphore
personal var objects = [T]()
init(_ objects: [T]) {
self.semaphore = DispatchSemaphore(worth: objects.depend)
self.objects.reserveCapacity(objects.depend)
self.objects.append(contentsOf: objects)
}
func purchase() -> T? {
if self.semaphore.wait(timeout: .distantFuture) == .success, !self.objects.isEmpty {
return self.lockQueue.sync {
return self.objects.take away(at: 0)
}
}
return nil
}
func launch(_ merchandise: T) {
self.lockQueue.sync {
self.objects.append(merchandise)
self.semaphore.sign()
}
}
}
let pool = Pool<String>(["a", "b", "c"])
let a = pool.purchase()
print("(a ?? "n/a") acquired")
let b = pool.purchase()
print("(b ?? "n/a") acquired")
let c = pool.purchase()
print("(c ?? "n/a") acquired")
DispatchQueue.international(qos: .default).asyncAfter(deadline: .now() + .seconds(2)) {
if let merchandise = b {
pool.launch(merchandise)
}
}
print("No extra useful resource within the pool, blocking thread till...")
let x = pool.purchase()
print("(x ?? "n/a") acquired once more")
As you’ll be able to see the implementation is only a few strains. You might have the thread protected array of the generic pool objects, a dispatch semaphore that’ll block if there aren’t any objects out there within the pool, and two strategies so as to really use the item pool.
Within the pattern you’ll be able to see that if there aren’t any extra objects left within the pool, the present queue shall be blocked till a useful resource is being freed & prepared to make use of. So be careful & do not block the primary thread by chance! 😉