Protocol Oriented Blockchain in Swift
This is the third and final instalment in a pretty technical implementation of Blockchain using protocol oriented programming with a mocked client-server and an observer pattern. In part one of this series we created a protocol and struct based blockchain solution and in part two we built a Client protocol and the business logic of our Server.
In this article we will take everything we have already built to create an observer pattern for our final UITableView to update as changes occur on our Chain.
(TL;DR: If you are keen to get your hands on the working playground for this, you can do that through the RNDM Github Page here.)
Beauty is in the Eye of the… Observer
So what exactly is an observer pattern? Well in programming paradigms, it is deemed to be a clean approach that allows for a reactive approach to state changes. When learning to code, the majority of us learn how to tell an object such as a view to do something and thereby change the state of that view. This style of code is called Imperative Programming.
Over time the counter point to this style, Declarative Programming, has been growing ever more in popularity. This paradigm allows us to define our objects and allow the flow of the state to determine the final result of that object. Effectively, we allow our code to respond or react to changes in the state that comes into it.
There are many frameworks across different languages that have been created to allow this paradigm to be implemented, with some of the most popular ones being heavily influenced by the ReactiveX concept. Within the swift world, there are two extremely popular frameworks that are found fairly frequently: RXSwift and ReactSwift. Whereas, if you have been within the JavaScript world you will recognise React and it’s native counterpart React Native. You can even checkout the RNDM website to see how this can be implemented across 8 different platforms simultaneously, have your entire application delivered as a server-side API response!
And when it comes to designing the structure of your project, you’ll see heavy use of this pattern in architectures such as MVVM, MVP and VIPER among others.
(Wow! That section was simply drowning in hyperlinks! sorry 😬)
Code
That being said, we are not going to use any of those frameworks. We are going to implement our own, purely because we want to get a general understanding of what they do before jumping in and actually learning any of those frameworks.
Observer
We are going to start with the end item. The Object that is subscribing to be updated with any changes.
// Final Observer.swift
protocol Observer {
func didUpdate(withData data: Any?)
}extension Observer {
func didUpdate(withData data: Any?) {}
}
Observable
Though the Observer was simple, the Observable will have a little more complexity. First, lets define our protocol:
protocol Observable {
static var observers: [String: [String: Observer]] { get set }
var identifier: String { get }
}
In this signature we have defined one static variable and one instance variable. However, we want our protocol to have a bit more umph behind it. So let’s extend it and give it some power!
extension Observable {
var observers: [String: Observer]? {
if let owned = Self.observers[identifier] {
return owned
}
Self.observers.updateValue([:], forKey: identifier)
return Self.observers[identifier]
}
}
First we are going to have an instance computed variable that will get the statically registered observers for this observable, or else it will register an empty dictionary.
extension Observable {
func register(observer: Observer, forKey key: String) {
if var current = observers {
current.updateValue(observer, forKey: key)
Self.observers.updateValue(current, forKey: identifier)
} else {
Self.observers.updateValue([key: observer],
forKey: identifier)
}
} func deregister(withKey key: String) {
if var current = observers {
current.removeValue(forKey: key)
Self.observers.updateValue(current, forKey: identifier)
}
}
}
Now we are creating two methods for adding and removing our observers.
extension Observable {
func notify(withData data: Any?) {
observers?.values.forEach { $0.didUpdate(withData: data) }
}
}
Finally, we add method to notify any subscribed observers of the changes. Now, or final Observable should look like the below:
// Final Observable.swift
protocol Observable {
static var observers: [String: [String: Observer]] { get set }
var identifier: String { get }
}extension Observable {
var observers: [String: Observer]? {
if let owned = Self.observers[identifier] {
return owned
}
Self.observers.updateValue([:], forKey: identifier)
return Self.observers[identifier]
}
}extension Observable {
func register(observer: Observer, forKey key: String) {
if var current = observers {
current.updateValue(observer, forKey: key)
Self.observers.updateValue(current, forKey: identifier)
} else {
Self.observers.updateValue([key: observer],
forKey: identifier)
}
}func deregister(withKey key: String) {
if var current = observers {
current.removeValue(forKey: key)
Self.observers.updateValue(current, forKey: identifier)
}
}
}extension Observable {
func notify(withData data: Any?) {
observers?.values.forEach { $0.didUpdate(withData: data) }
}
}
Believe it our not, that is actually it for the observer pattern! From now on, we are just linking up all the parts of our project to create a solution that will create a UITableViewController that becomes a client and an observer, register for changes with an observable, and starts implementing fetches on our server.
Fetcher
Our fetch solution is going to do the following:
- Initialise with a Client
- Generate a random array of transactions for our Server
- Begin fetching
- Notify any observers with changes
struct Fetcher: Observable {
static var observers: [String: [String: Observer]] = [:]
let identifier = UUID().string
let client: Client
}
First things first, we create our struct and conform to the Observable protocol.
extension Fetcher {
private enum Accounts {
case base
case existing
case new
static var accounts: [String] = []
} private static func getFrom() -> Accounts {
let random = arc4random() % 100
switch random {
case 0...50: return .base
default: return .existing
}
} private static func getTo() -> Accounts {
let random = arc4random() % 100
switch random {
case 0...50: return .new
case 51...80: return .existing
default: return .base
}
}
private static func getAccount(input: Accounts) -> String {
switch input {
case .new:
let output = UUID().uuidString
Accounts.accounts.append(output)
return output case .base:
return Accounts.accounts.first ?? getAccount(input: .new) case .existing:
return Accounts.accounts.dropFirst().shuffled()[0]
}
}
}
In the section above, we have created all we need to auto-generate a suite of transactions, all of which are private since this mechanism will not need to be exposed to the outside world.
Having said that, let’s create a new typealias and a method for returning a set of generated items for us to use:
extension Fetcher {
typealias Fetch = (url: String,
method: String,
body: Any?,
latency: UInt32) static func generated(initial: Int = 10_000_000,
maximum: Int = 1_000,
count: Int = 1_000) -> [Fetch] {
let fetches: [Fetch] = (0...count).map {
let latency: UInt32 = UInt32($0)
switch $0 {
case 0:
return (url: "http://test.com/creation",
method: "POST",
body: [
"account": getAccount(input: .base),
"value": initial
],
latency: latency) case 1:
return (url: "http://test.com/exchange",
method: "POST",
body: [
"from": getAccount(input: .base),
"to": getAccount(input: .new),
"value": arc4random() % UInt32(maximum) + 1
],
latency: latency) default:
return (url: "http://test.com/exchange",
method: "POST",
body: [
"from": getAccount(input: getFrom()),
"to": getAccount(input: getTo()),
"value": arc4random() % UInt32(maximum) + 1
],
latency: latency)
}
}
return fetches
}
}
This random generator will simple ensure that the first transaction is a creation, the second is an exchange from the created account and from that point onwards try to generate exchanges from one account to another. Sometimes these will fail as the value is greater than the from account’s value, but that will be great for any testing we want to implement. Another feature is that the latency will be exactly one second apart.
Ans so, ready and raring to go and armed to the teeth with some randomly generated Fetch tuples, we can build our fetch mechanism:
extension Fetcher {
static func fetch(fetches: [Fetch]? = nil,
initial: Int = 10_000_000,
maximum: Int = 1_000,
count: Int = 1_000,
observer: Observable,
client: Client) {
let resolution: Resolution = {
let _ = $1
observer.notify(withData: $0)
} (fetches ?? generated(count: count)).forEach {
client.fetch(url: $0.url,
method: $0.method,
body: $0.body,
latency: $0.latency,
resolve: resolution)
}
} func fetch(fetches: [Fetch]? = nil,
initial: Int = 10_000_000,
maximum: Int = 1_000,
count: Int = 1000) {
Fetcher.fetch(fetches: fetches,
initial: initial,
maximum: maximum,
count: count,
observer: self,
client: client)
}
}
We’ve created a system here for both static and instance fetching, which we can use later if we need to. So our final fetching mechanism will look something like this:
// Final Fetcher.swift
struct Fetcher: Observable {
static var observers: [String: [String: Observer]] = [:]
let identifier = UUID().string
let client: Client
}extension Fetcher {
private enum Accounts {
case base
case existing
case new
static var accounts: [String] = []
} private static func getFrom() -> Accounts {
let random = arc4random() % 100
switch random {
case 0...50: return .base
default: return .existing
}
} private static func getTo() -> Accounts {
let random = arc4random() % 100
switch random {
case 0...50: return .new
case 51...80: return .existing
default: return .base
}
}
private static func getAccount(input: Accounts) -> String {
switch input {
case .new:
let output = UUID().uuidString
Accounts.accounts.append(output)
return output case .base:
return Accounts.accounts.first ?? getAccount(input: .new) case .existing:
return Accounts.accounts.dropFirst().shuffled()[0]
}
}
}extension Fetcher {
typealias Fetch = (url: String,
method: String,
body: Any?,
latency: UInt32) static func generated(initial: Int = 10_000_000,
maximum: Int = 1_000,
count: Int = 1_000) -> [Fetch] {
let fetches: [Fetch] = (0...count).map {
let latency: UInt32 = UInt32($0)
switch $0 {
case 0:
return (url: "http://test.com/creation",
method: "POST",
body: [
"account": getAccount(input: .base),
"value": initial
],
latency: latency) case 1:
return (url: "http://test.com/exchange",
method: "POST",
body: [
"from": getAccount(input: .base),
"to": getAccount(input: .new),
"value": arc4random() % UInt32(maximum) + 1
],
latency: latency) default:
return (url: "http://test.com/exchange",
method: "POST",
body: [
"from": getAccount(input: getFrom()),
"to": getAccount(input: getTo()),
"value": arc4random() % UInt32(maximum) + 1
],
latency: latency)
}
}
return fetches
}
}extension Fetcher {
static func fetch(fetches: [Fetch]? = nil,
initial: Int = 10_000_000,
maximum: Int = 1_000,
count: Int = 1_000,
observer: Observable,
client: Client) {
let resolution: Resolution = {
let _ = $1
observer.notify(withData: $0)
} (fetches ?? generated(count: count)).forEach {
client.fetch(url: $0.url,
method: $0.method,
body: $0.body,
latency: $0.latency,
resolve: resolution)
}
} func fetch(fetches: [Fetch]? = nil,
initial: Int = 10_000_000,
maximum: Int = 1_000,
count: Int = 1000) {
Fetcher.fetch(fetches: fetches,
initial: initial,
maximum: maximum,
count: count,
observer: self,
client: client)
}
}
ViewController
Since we are not learning about UITableViewControllers, I will assume you understand this type of object and whip through the first initial setup part of this project:
class ViewController: UITableViewController {
private enum ViewStyle {
case accounts
case chain
} let queue = DispatchQueue(label: "background_queue",
qos: .background,
attributes: .concurrent,
autoreleaseFrequency: .inherit,
target: nil)
let server = Server()
private var chain: [Block] = []
private var accounts: [Payload] = []
private var viewStyle: ViewStyle = .accounts init () {
super.init(style: .grouped)
initialize()
} required init?(coder aDecoder: NSCoder) {
super.init(style: .grouped)
initialize()
} func initialize() {
tableView.register(UITableViewCell.classForKeyedArchiver(),
forCellReuseIdentifier: "CELL")
} override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
fetcher.fetch()
} override func numberOfSections(in tableView: UITableView) -> Int {
return 2
} override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return section == 0 ? 1 :
viewStyle == .accounts ? accounts.count : chain.count
} override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CELL")
?? UITableViewCell()
switch indexPath.section {
case 0: cell.textLabel?.text = "Switch view"
default:
switch viewStyle {
case .accounts:
let payload = accounts[indexPath.row]
cell.textLabel?.text = "\(payload.account): \(payload.value)" case .chain:
let block = chain[indexPath.row]
cell.textLabel?.text = "\(block.transaction) -
\(block.payload.account): \(block.payload.value)"
}
}
return cell
} override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
if (indexPath.section == 0 && indexPath.row == 0) {
switch (viewStyle) {
case .chain: viewStyle = .accounts
case .accounts: viewStyle = .chain
}
tableView.reloadData()
}
}
}
The above is simple creating UITableViewController that will switch between two views. One that will show the accounts, and the second that will show the full chain.
Let’s have our ViewController class become an observer:
extension ViewController: Observer {
func didUpdate(withData data: Any?) {
queue.async {
if
let response = data as? Response,
let json = response.json as? [String: Any],
let value = json["response"] as? String,
value == "SUCCESS",
let responseChain = json["chain"] as? [[String: Any]]
{
let chain: [Block] = responseChain.compactMap {
guard let data = try? JSONSerialization
.data(withJSONObject: $0,
options: .prettyPrinted)
else { return nil }
return try? JSONDecoder().decode(Block.self, from: data)
}
let reduced = (chain.reduce([String: Payload](), {
var res = $0
if (Transaction
.isValueTransaction(input: $1.transaction)) {
res[$1.payload.account] = $1.payload
}
return res
}))
DispatchQueue.main.async {
self.chain = chain
self.accounts = reduced.values.sorted {
$0.value > $1.value
}
self.tableView.reloadData()
}
}
}
}
}
As you can see in the above code, we are doing a little heavy processing on our background queue to create both the accounts view and the chain view, then throwing the result onto our main queue for updating our UI.
And now finally, let’s add our fetcher.
extension ViewController: Client {
var fetcher: Fetcher {
return {
$0.register(observer: self, forKey: "key")
return $0
}(Fetcher(client: self))
}
}
And we’re done! Our final ViewController should look a little like this:
// Final ViewController.Swift
class ViewController: UITableViewController {
private enum ViewStyle {
case accounts
case chain
} let queue = DispatchQueue(label: "background_queue",
qos: .background,
attributes: .concurrent,
autoreleaseFrequency: .inherit,
target: nil)
let server = Server()
private var chain: [Block] = []
private var accounts: [Payload] = []
private var viewStyle: ViewStyle = .accounts init () {
super.init(style: .grouped)
initialize()
} required init?(coder aDecoder: NSCoder) {
super.init(style: .grouped)
initialize()
} func initialize() {
tableView.register(UITableViewCell.classForKeyedArchiver(),
forCellReuseIdentifier: "CELL")
} override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
fetcher.fetch()
} override func numberOfSections(in tableView: UITableView) -> Int {
return 2
} override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return section == 0 ? 1 :
viewStyle == .accounts ? accounts.count : chain.count
} override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CELL")
?? UITableViewCell()
switch indexPath.section {
case 0: cell.textLabel?.text = "Switch view"
default:
switch viewStyle {
case .accounts:
let payload = accounts[indexPath.row]
cell.textLabel?.text = "\(payload.account): \(payload.value)" case .chain:
let block = chain[indexPath.row]
cell.textLabel?.text = "\(block.transaction) -
\(block.payload.account): \(block.payload.value)"
}
}
return cell
} override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
if (indexPath.section == 0 && indexPath.row == 0) {
switch (viewStyle) {
case .chain: viewStyle = .accounts
case .accounts: viewStyle = .chain
}
tableView.reloadData()
}
}
}extension ViewController: Observer {
func didUpdate(withData data: Any?) {
queue.async {
if
let response = data as? Response,
let json = response.json as? [String: Any],
let value = json["response"] as? String,
value == "SUCCESS",
let responseChain = json["chain"] as? [[String: Any]]
{
let chain: [Block] = responseChain.compactMap {
guard let data = try? JSONSerialization
.data(withJSONObject: $0,
options: .prettyPrinted)
else { return nil }
return try? JSONDecoder().decode(Block.self, from: data)
}
let reduced = (chain.reduce([String: Payload](), {
var res = $0
if (Transaction
.isValueTransaction(input: $1.transaction)) {
res[$1.payload.account] = $1.payload
}
return res
}))
DispatchQueue.main.async {
self.chain = chain
self.accounts = reduced.values.sorted {
$0.value > $1.value
}
self.tableView.reloadData()
}
}
}
}
}extension ViewController: Client {
var fetcher: Fetcher {
return {
$0.register(observer: self, forKey: "key")
return $0
}(Fetcher(client: self))
}
}
Putting it all together
In the last 3 articles we have done a lot and come a long way. There is only one last thing to do. If I were to have done this inside a playground, then all I want to do is instantiate a ViewController instance and allow it to begin hitting the Server. You know what: let’s do just that:
import PlaygroundSupportlet controller = ViewController()PlaygroundPage.current.needsIndefiniteExecution = truePlaygroundPage.current.liveView = controller.view
Done!
Conclusion
I know there is a fair amount of code and a lot of it is pretty complex and functional. However, I hope you enjoyed the journey through these tutorials and they challenged your thoughts and approach to code.
If you are keen to get your hands on the working playground for this, you can do that through the RNDM Github Page here.
About Paul:
Paul is a Cross-Platform Tech Lead with experience across iOS, Android and Web. In his spare time, apart from teaching people to code, he can be found writing out low-code, open-source solutions and advocating the automation of development work as well as its integration with machine learning and AI. You can check out his latest projects here: