Swift Actor-Model

Cart00nHero8
3 min readFeb 16, 2021

Flynn Actor-Model architecture

Foreword:

I really like the actor-model architecture. And I plan to implement it in App. In near time, I saw this libraries make me exciting:

https://github.com/KittyMac/flynn

So let us use this to make a fast、safely、efficiently App

Installation

Refer to https://github.com/KittyMac/flynn

Build first Actor:

import Flynn

class HelloFlynnActor: Actor {
private func _beSayHello() {
print("Hello Flynn")
}
}

// MARK: - Autogenerated by FlynnLint
// Contents of file after this marker will be overwritten as needed

extension HelloFlynnActor {

@discardableResult
public func beSayHello() -> Self {
unsafeSend(_beSayHello)
return self
}

}

remember use keyword:_be and FlynnLint can auto generate extention after building project

Say hello to Flynn:

HelloFlynnActor().beSayHello()

Concurrent Data store:

import Flynn

protocol ConcurrentDatastoreRetrievable {
@discardableResult
func beRetrieveValue(_ key: String, _ value: Any?) -> Self
}

class ConcurrentDatastore: Actor {
private var storage: [String: Any?] = [:]

private func _beGet(_ key: String, _ sender: ConcurrentDatastoreRetrievable) {
if let value = storage[key] {
sender.beRetrieveValue(key, value)
}
}

private func _beGet(_ key: String, _ sender: Actor, _ block: @escaping (Any?) -> Void) {
if let value = storage[key] {
sender.unsafeSend {
block(value)
}
}
}

private func _beSet(_ key: String, _ value: Any?) {
storage[key] = value
}

}

// MARK: - Autogenerated by FlynnLint
// Contents of file after this marker will be overwritten as needed

extension ConcurrentDatastore {

@discardableResult
public func beGet(_ key: String, _ sender: ConcurrentDatastoreRetrievable) -> Self {
unsafeSend { self._beGet(key, sender) }
return self
}
@discardableResult
public func beGet(_ key: String, _ sender: Actor, _ block: @escaping (Any?) -> Void) -> Self {
unsafeSend { self._beGet(key, sender, block) }
return self
}
@discardableResult
public func beSet(_ key: String, _ value: Any?) -> Self {
unsafeSend { self._beSet(key, value) }
return self
}

}

Scenario actors:

class Scenario1: Actor,ConcurrentDatastoreRetrievable {
private let monsters: ConcurrentDatastore

init(_ monsters: ConcurrentDatastore) {
self.monsters = monsters
super.init()
}

private func _beRetrieveValue(_ key: String, _ value: Any?) {
if let value = value {
print("\(value)")
}
}

private func _bePrint(_ name: String) {
monsters.beGet(name, self)
}
}

class Scenario2: Actor {
private let monsters: ConcurrentDatastore

init(_ monsters: ConcurrentDatastore) {
self.monsters = monsters
super.init()
}

private func _bePrint(_ name: String) {
monsters.beGet(name, self) { value in
if let value = value {
print("\(value)")
}
}
}

}

How to use it:

let monsters = ConcurrentDatastore()

monsters.beSet("Goblin", Monster(name: "Goblin", hitpoints: 10, strength: 3, dexterity: 2, intelligence: 1))
monsters.beSet("Orc", Monster(name: "Orc", hitpoints: 15, strength: 6, dexterity: 4, intelligence: 2))
monsters.beSet("Troll", Monster(name: "Troll", hitpoints: 30, strength: 4, dexterity: 1, intelligence: 5))

let scenario1 = Scenario1(monsters)
let scenario2 = Scenario2(monsters)

print("-------Show-------")
scenario1.bePrint("Goblin")
scenario1.bePrint("Orc")
scenario1.bePrint("Troll")

sleep(1)
print("--------------")

scenario2.bePrint("Troll")
scenario2.bePrint("Orc")
scenario2.bePrint("Goblin")

View Model Actor:

class ViewModelActor: Actor,ObservableObject {
private var coutTextBlock: ((String) -> Void)?
private var count: Int = 0 {
willSet { objectWillChange() }
}

private var unsafeCount: String {
set {
if let value = Int(newValue) {
beSetCount(value)
}
}
get { return "\(count)" }
}
private func objectWillChange() {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
private func _beSetCount(_ value: Int) {
count = value
}
private func _beAddCountTextBlock(_ block: @escaping (String?) -> Void) {
coutTextBlock = block
}

override init() {
super.init()
Flynn.Timer(timeInterval: 0.5, repeats: true, self) { (_) in
self.count += 1
if self.coutTextBlock != nil {
DispatchQueue.main.async {
self.coutTextBlock!("\(self.count)")
}
}
}
}
}

Use it:

model.beAddCountTextBlock { (title) in
self.button.setTitle(title, for: .normal)

It’s a simple sample, next time I’ll try RemoteActor

My practice project clone url: https://github.com/Cart00nHero/FlynnActorModel_iOS.git

--

--