RxMVVM using Inputs / Outputs and issues with complex mapping - swift

Given the design pattern as described by this post, here is an example view model:
final class SayHelloViewModel: ViewModelType {
let input: Input
let output: Output
struct Input {
let name: AnyObserver<String>
let validate: AnyObserver<Void>
}
struct Output {
let greeting: Driver<String>
}
private let nameSubject = ReplaySubject<String>.create(bufferSize: 1)
private let validateSubject = PublishSubject<Void>()
init() {
let greeting = validateSubject
.withLatestFrom(nameSubject)
.map { name in
return "Hello \(name)!"
}
.asDriver(onErrorJustReturn: ":-(")
self.output = Output(greeting: greeting)
self.input = Input(name: nameSubject.asObserver(), validate: validateSubject.asObserver())
}
}
The above seems like a perfectly good design pattern. My only issue is, what happens when your mapping function from nameSubject -> greeting is more complex than what is shown here and instead needs to be abstracted into it's own function?
In the below scenario, i've abstracted the mapping functionality into its own function called sayHello. Of course, the issue now is that we're referencing self before self is initialised. How is it possible to maintain this design pattern across non-trivial examples?
final class SayHelloViewModel {
let input: Input
let output: Output
struct Input {
let name: AnyObserver<String>
let validate: AnyObserver<Void>
}
struct Output {
let greeting: Driver<String>
}
private let nameSubject = ReplaySubject<String>.create(bufferSize: 1)
private let validateSubject = PublishSubject<Void>()
init() {
let greeting = validateSubject
.withLatestFrom(nameSubject)
.map(sayHello)
.asDriver(onErrorJustReturn: ":-(")
self.output = Output(greeting: greeting)
self.input = Input(name: nameSubject.asObserver(), validate: validateSubject.asObserver())
}
private func sayHello(name: String) -> String {
return "Hello \(name)!"
}
}

Just make your mapping function a private free function instead of a class member. It only needs to be a member itself if it needs access to members, which in this pattern is highly unlikely.
Edit: Also you could clean this up a lot by avoiding subjects and operate on the inputs/outputs directly like so:
final struct SayHelloViewModel {
struct Input {
let name: Observable<String>
let validate: Observable<Void>
}
// An output
let greeting: Driver<String>
init(inputs: Input) {
let greeting = input.validate
.withLatestFrom(input.name)
.map(sayHello)
.asDriver(onErrorJustReturn: ":-(")
}
}
private func sayHello(name: String) -> String {
return "Hello \(name)!"
}
You could take it even further and not use a struct/class at all and make it purely a function that returns a tuple/struct of outputs.

Related

How do I get the value of a Published<String> in swift without using debugDescription?

I've got the following code, that runs in a playground.
I'm attempting to allow subscript access to #Published variables in a class.
The only way I've found so far to retrieve the String value in the below implementation of
getStringValue
is to use the debugDescription, and pull it out -- I've looked at the interface for Published, but can't find any way to retrieve the value in a func like getStringValue
Any pointers would be greatly appreciated :)
Edited to include an example of how it works with a non-published variable.
Cheers
import Foundation
import Combine
protocol PropertyReflectable {}
extension PropertyReflectable {
subscript(key: String) -> Any? {
return Mirror(reflecting: self).children.first { $0.label == key }?.value
}
}
class Foo : PropertyReflectable {
#Published var str: String = "bar"
var str2: String = "bar2"
}
// it seems like there should be a way to get the Published value without using debugDescription
func getStringValue(_ obj: Combine.Published<String>?) -> String? {
if obj == nil { return nil }
let components = obj.debugDescription.components(separatedBy: "\"")
return components[1]
}
let f = Foo()
let str = getStringValue(f["_str"] as? Published<String>)
print("got str: \(str!)")
// str == "bar" as expected
let str2 = f["str2"]!
print("got non-published string easily: \(str2)")
Published seems to be steeped in some compiler magic, for lack of a better wording, since it can only be used as a property wrapper inside classes.
That being said, would something like this work?
final class PublishedExtractor<T> {
#Published var value: T
init(_ wrapper: Published<T>) {
_value = wrapper
}
}
func extractValue<T>(_ published: Published<T>) -> T {
return PublishedExtractor(published).value
}

Swift - switch between Core ML Model

I'm trying to compare predictions from different MLModels in SwiftUI. To do that I have to switch between them, but can't because every ML variable has its own class, so I get the error:
Cannot assign value of type 'ModelOne' to type 'ModelTwo'
Here's an example code:
import Foundation
import CoreML
import SwiftUI
let modelone = { //declaration model 1
do {
let config = MLModelConfiguration()
return try ModelOne(configuration: config)
} catch {
/*...*/
}
}()
let modeltwo = { //declaration model 2
do {
let config = MLModelConfiguration()
return try ModelTwo(configuration: config)
} catch {
/*...*/
}
}()
var imageused : UIImage! //image to classify
var modelstring = "" //string of model user chosen
var modelchosen = modelone
Button(action: { //button user decide to use model two
modelstring = "Model Two"
}) {/*...*/}
/*...*/
func classifyphoto() {
guard let image = imageused as UIImage?,
let imagebuffer = image.convertToBuffer() else {
return
}
if modelstring == "Model Two" { //if the user chosen model two, use ModelTwo
modelchosen = modeltwo // Error: Cannot assign value of type 'ModelOne' to type 'ModelTwo'
} else {
modelchosen = modelone}
let output = try? modelchosen.prediction(image: imagebuffer) //prediction with model chosen
if let output = output {
let results = output.classLabelProbs.sorted { $0.1 > $1.1 }
_ = results.map { /*...*/
}
}
}
Thank you!
The issue is that the two model classes do not have a common class or common inherited class. There are several ways to implement what you want. I think this is the best way based on your example.
class MyModel {
var model: MLModel? = nil
init(modelName: String) {
let bundle = Bundle.main
if let modelURL = bundle.url(forResource: modelName, withExtension:"mlmodelc") {
do {
self.model = try MLModel(contentsOf: modelURL)
}
catch {
print("Unable to open MLModel: \(error)")
}
}
}
}
class TestModel {
class func testModels() {
let modelOne = MyModel(modelName: "ModelOne")
let modelTwo = MyModel(modelName: "ModelTwo")
var selectedModel = modelOne
selectedModel = modelTwo
}
}
Swift is a statically typed language which means that in the general case you cannot assign a variable of one type to a variable of another type:
var int: Int = 42
int = "Hello, world!" // Not allowed: cannot assign String to Int
The problem is that modelchosen is of type ModelOne since it is initialized with modelone, thus, you cannot later assign modeltwo to it as you are trying to do.
To make that working, you have first to identify the common capabilities of ModelOne and ModelTwo. Take a look at their definition. For instance, do their .predict(image:) method return the same type? It looks like you are trying to do image classification, so a common capability could be the capability to return a String describing the image (or a list of potential objects, etc.).
When you'll have identified the common capability, you'll be able to define the common interface of your different types. This common interface can be expressed in many ways:
Using a base class
Using a protocol
Using an enum with payloads (union)
The following examples suppose that the common capabilities are:
The two networks can both be initialized with a MLModelConfiuration
They are used for image classification, i.e. they predict label (a String) describing a given image
Using a base class
The base class definition expresses those requirements like this:
class MLClassifier {
init(from config: MLModelConfig) {
fatalError("not implemented")
}
func classify(image: ImageBuffer) -> String {
fatalError("not implemented")
}
}
You then derive this base class for the two models (example with the first one:
final class ModelOne: MLClassifier {
init(from config: MLModelConfig) {
// the specific implementation for `ModelOne`...
}
func classify(image: ImageBuffer) -> String {
// the specific implementation for `ModelOne`..
}
}
Finally, you can make the variable modelchosen to be of type MLClassifier to erase the underlying concrete type of the model:
var modelchosen: MLClassifier = ModelOne(from: config1)
As MLClassifier is a common base class for both ModelOne and ModelTwo you can dynamically change the type of modelchosen whenever you need:
// Later...
modelchosen = ModelTwo(from: config2)
The variable modelchosen being of type MLClassifier ensures that you can call the .classify(image:) method whatever the concrete model type is:
func classifyphoto() {
guard let image = imageused as UIImage?,
let imagebuffer = image.convertToBuffer() else {
return
}
let output = modelchosen.classify(image: imageBuffer)
// Update the UI...
}
Using protocols
Protocols are the modern and preferred way of expressing common interfaces in Swift, they should be used over classes when possible:
protocol MLClassifier {
init(from config: MLModelConfig)
func classify(image: ImageBuffer) -> String
}
// Implement the protocol for your models
struct ModelOne: MLClassifier {
init(from config: MLModelConfig) { ... }
func classify(image: ImageBuffer) -> String { ... }
}
// Store an instance of any `MLClassfier` using an existential
var classifier: any MLClassifier = ModelOne(from: config1)
// Later...
classifier = ModelTwo(from: config2)
To sum up, the key is to identify the common capabilities of the different types you are trying to unify. For instance, if the two models output at some point a classLabelProbs of the same type, then you could use this as the common abstraction.
As a last resort, you could wrap everything in a big if-else statement, event though it is not recommended since it is not very readable, is not a good way to encapsulate common behavior and leads to a lot of code repetition:
func classifyphoto() {
guard let image = imageused as UIImage?,
let imagebuffer = image.convertToBuffer() else {
return
}
if modelstring == "Model Two" {
// Use modeltwo
let output = try? modeltwo.prediction(image: imagebuffer)
if let output = output {
let results = output.classLabelProbs.sorted { $0.1 > $1.1 }
_ = results.map { /*...*/ }
} else {
// Use modelone
let output = try? modelone.prediction(image: imagebuffer)
if let output = output {
let results = output.classLabelProbs.sorted { $0.1 > $1.1 }
_ = results.map { /*...*/ }
}
}

How to recursively iterate over Swift Syntax with SwiftSyntax library?

I would like to iterate in my code over the Swift AST like this, finding the struct keyword.
private func recursion(node: Syntax) -> String {
for child in node.children {
if let tokenKind = (child as? TokenSyntax)?.tokenKind, tokenKind == .structKeyword {
// if type(of: child) == StructDeclSyntax.self {
print ("yeah")
}
recursion(node: child)
}
}
let input = """
public struct cmd_deleteEdge<E: VEdge> : EdgeCommand {
public var keyEquivalent = KeyEquivalent.none
public let title = "Delete Edge"
public let id = "deleteEdge"
public let toolTip = "Delete selected Edge"
public let icon = Icon.delete
//receivers
public let edge: E
public init(edge: E) {
self.edge = edge
}
public func execute() throws -> ActionResult {
edge.deleteYourself()
return .success("deleted edge")
}
}
"""
public func convert(structText: String) throws -> String {
let sourceFile = try SyntaxParser.parse(source: structText)
let result = recursion(node: Syntax(sourceFile))
return result
}
try convert(structText: input)
It just simply doesn't work, I never reach the "Yeah" (which means I cannot do anything useful during the recursion).
I find this library very confusing. Would anyone have a good UML diagram explaining how it really works?
Before you tell me, yes I know I could use a Visitor, but I want to understand how it works by myself.
You can use SyntaxProtocol for iterating all items in AST and then use its _syntaxNode public property to make a target syntax, e.g.:
import SwiftSyntax
import SwiftSyntaxParser
func recursion(node: SyntaxProtocol) {
if let decl = StructDeclSyntax(node._syntaxNode) {
print(decl.identifier)
}
node.children.forEach { recursion(node: $0) }
}
let code = """
struct A {}
class Some {
struct B {}
}
func foo() {
struct C {}
}
"""
let sourceFile = try SyntaxParser.parse(source: code)
recursion(node: sourceFile)
Outputs:
A
B
C
NOTE: It is not recommended to retrieve _syntaxNode property directly and you can use Syntax(fromProtocol: node) instead.
SyntaxVisitor
But the best approach is using Visitor pattern with SyntaxVisitor class to avoid recursion issues for large and complex files:
class Visitor: SyntaxVisitor {
var structs = [StructDeclSyntax]()
init(source: String) throws {
super.init()
let sourceFile = try SyntaxParser.parse(source: source)
walk(sourceFile)
}
// MARK: - SyntaxVisitor
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
structs.append(node)
return .skipChildren
}
}
let visitor = try Visitor(source: code)
visitor.structs.forEach {
print($0.identifier)
}
I found it after trial & error and reviewing of the API.
private func recursion(node: Syntax) -> String {
for child in node.children {
if let token = TokenSyntax(child), token.tokenKind == .structKeyword {
print ("yeah")
}
recursion(node: child)
}
return node.description
}
This approach to identify the kind of the token works, and the print statement will be reached. Again, I do wonder how the class diagram for SwiftSyntax would look like.

How to use type-erase in Swift

Story:
I have some layouts.
A layout have a pattern and keys. The layout can make message from these.
Each patterns have maximum number of keys.
That is my code to expression templates.
protocol LayoutPattern {
static var numberOfKeys: Int { get }
static func make(with keys: [String]) -> String
}
struct Pattern1: LayoutPattern {
static let numberOfKeys: Int = 1
static func make(with keys: [String]) -> String {
return "Pattern 1:" + keys.joined(separator: ",")
}
let value1: String
}
struct Pattern2: LayoutPattern {
static let numberOfKeys: Int = 2
static func make(with keys: [String]) -> String {
return "Pattern 2:" + keys.joined(separator: ",")
}
let value1: String
let value2: String
}
protocol LayoutProtocol {
associatedtype Pattern: LayoutPattern
var keys: [String] { get }
func make() -> String
}
struct Layout<T: LayoutPattern>: LayoutProtocol {
typealias Pattern = T
let keys: [String]
init(keys: [String]) {
assert(keys.count == Pattern.numberOfKeys)
self.keys = keys
}
func make() -> String {
return Pattern.make(with: keys)
}
}
let t1 = Layout<Pattern1>(keys: ["key1"])
t1.make() // Pattern 1: key1
let t2 = Layout<Pattern2>(keys: ["key1", "key2"])
t2.make() // Pattern 2: Key1,Key2
This is valid code.
But I can't write that:
class MyNote {
let layout: LayoutProtocol
}
I know that I should use a technique called type-erase like AnyPokemon!
I wrote that:
struct AnyLayout<T: LayoutPattern>: LayoutProtocol {
typealias Pattern = T
let keys: [String]
private let _make: () -> String
init<U: LayoutProtocol>(_ layout: U) where T == U.Pattern {
self.keys = layout.keys
self._make = { layout.make() }
}
func make() -> String {
_make()
}
}
let anyLayout = AnyLayout(Layout<Pattern2>(keys: ["key1", "key2"]))
anyLayout.make() // Pattern 2: Key1,Key2
This can be executed. But MyNote class can't still have a property as AnyLayout.
What should I do?
The issue is the addition of the associatedtype. It isn't doing any work here. Nothing relies on it. Remove it, and the issue goes away. Don't add associatedtypes until you have a specific requirement for them.
As a rule, if you think you need type-erasure, first ask if your protocol is designed correctly. There are definitely times that type erasers are needed, but they're far rarer than people expect.
If you have an algorithm that relies on Pattern, then show that, and we can discuss the way to build that. (There are many techniques, including using multiple protocols.)
It's also worth asking whether Layout needs to be generic here. Do you want Layout<Pattern1> to be a different type than Layout<Pattern2>? The fact that you're then trying to type-erase it suggests you don't. In that case, there's no reason for the extra generic layers. In your example, Layout isn't really doing any work. Again, you can probably just get rid of it. Let each pattern be its own thing and let Layout be a protocol that binds them with make():
protocol Layout {
func make() -> String
}
struct Pattern1: Layout {
let key: String
func make() -> String {
return "Pattern 1:" + key
}
}
struct Pattern2: Layout {
let keys: [String]
init(key1: String, key2: String) {
keys = [key1, key2]
}
func make() -> String {
return "Pattern 2:" + keys.joined(separator: ",")
}
}
let t1 = Pattern1(key: "key1")
t1.make() // Pattern 1: key1
let t2 = Pattern2(key1: "key1", key2: "key2")
t2.make() // Pattern 2: Key1,Key2
class MyNote {
let layout: Layout
init(layout: Layout) {
self.layout = layout
}
}
let note = MyNote(layout: t1)
This lets you make your Pattern initializers much stronger types. The need for an assert means you're not letting the types do the work. With the above design, you can't pass the wrong number of keys.

How do i get labels from mirror with empty values

What i want to do is when i start the app, save all the names (in userDefault) of the constants from all models. I plan on doing it with a function looking something like:
public static func setup(models: [Codable]) {
models.forEach { (model) in
let mirr = Mirror.init(reflecting: model)
mirr.children.map({
UserDefaults.save("\(type(of: model))+\($0.label!)")})
}
}
Please note that i'm not finished yet.
What i know of there are 3 solutions to using this method. As the mirror wont show me labels unless the variable / constant has an actual value. It becomes really ugly and i wonder if i can do some work around because current you call it like this.
Scenario A:
public struct Testing: Codable {
let name: String = ""
let sex: String = ""
}
setup(models: [Testing()])
Scenario B:
public struct testing: Codable {
let name: String
let sex: String
}
setup(models: [Testing(name: "", sex: "")])
Scenario C:
public struct testing: Codable {
let name: String
let sex: String
init(name: String = "", sex: String = "") {
self.name = name
self.sex = sex
}
}
setup(models: [Testing()])
So basically what i want to do is:
public struct testing: Codable {
let name: String
let sex: String
}
setup(models: [Testing()])
// or
setup(models: [Testing.self])
Or kinda anything that wont force me to init the values.
I guess it can't be done, but maybe someone have some hack out there that work...
Thanks in advance.
You can simply update your setup(models:) method to,
public func setup(models: [Codable]) {
let arr: [String] = models.compactMap {
let mirror = Mirror(reflecting: $0)
if let name = mirror.children.first(where: { $0.label == "name" }) {
let value = "\(type(of: $0))+\(name.value)"
return value
}
return nil
}
UserDefaults.standard.set(arr, forKey: "Names")
}