Pues el otro día miré Twitter, es algo raro, desde que tengo Instagram, casi no veo Twitter. Lo uso mucho mas en plan “profesional”, por llamarlo de alguna manera. Sigo a algunos desarrolladores interesantes y de vez en cuando veo alguna referencia a un artículo o algo bastante bueno.

Bien, pues el otro día me encontré con esto:

Interactor Inspiration

Resulta que el artículo explicaba que los Playground te valen muy bien para documentar tu código con markdown, viendo cómo se ejecuta y dejándote juguetear con las variables que les pasas y las cosillas que te devuelve. No creo que la documentación quede mucho mas clara que con un test bien hecho con Quick, pues usando Quick describes perfectamente que haces y además “testeas” el código 😂, aunque no puedas jueguetear con él.

Bueno, el caso es que me pareció muy interesante la “interfaz” de uso de ese Interactor. Como veis en la imagen, usan PromiseKit un framework de “futures”. Yo usé ese framework en Objective-c en una de las empresas que he trabajado y la verdad que mola un montón. Pero no es de lo que quiero hablar ahora.

Back to the point, lo que mas me gustó es la simpleza de la interfaz, en este caso, llamamos al get y luego podemos hacer then y catch e ir concatenando tantos de estos como queramos.

En la imagen el tema es que si un then devuelve algo, el siguiente then recibe lo que ha devuelto el primero y así sucesivamente. No se si lo he explicado muy bien pero mas o menos es como ir haciendo .map tras .map tras .map y en el caso de que algo pete ir al mismo catch. Pero personalmente yo no busco eso en este momento, no busco usar Promises ni nada, sino adaptar esa interfaz tan simple a mis interactors diarios.

Entonces lo vi, una clase base llamada UseCase. Todos llevan un execute() que es el método al que llamamos y le pasamos los datos necesarios.

Esa fue mi primera iteración. Algo así:

final class SomeInteractor {

    func execute() {
        //do something
    }

}

Cuando quería pasarle variables porque iba hacer una petición de red o simplemente necesitaba algún tipo de datos, estos venían a través del método execute, de modo que queda así:

final class SomeInteractor {

    func execute(var: String, other: Int) {
        //do something with var and other
    }

}

Pues partiendo de eso, empezaron mis problemas. Mientras hacia la aplicación, por parte del servidor estaban haciendo la API, y aunque hubiese una documentación inicial que respetar, al final nos dábamos cuenta de que hacían falta unas cosas o sobraban otras. Por cada capa desde el interactor hasta la red teníamos que ir toqueteando las variables que pasábamos ante cualquier cambio. Y eso es un autentico 💩 coñazo 💩.

Bienvenida a los DTOs

Los DTOs estos, son objetos que utilizaba bastante en el mundillo java en server antes de pasarme a iOS. DTO significa Data Transfer Object, o lo que es lo mismo, un Struct con las cuatro variables que queremos pasar, así cuando necesitamos tocar algo, tocamos solo el DTO.

Entonces el interactor empezó a recibir un objeto llamado UseCaseRequest que hacía las veces de DTO.

struct SomeUseCaseRequest {
    var some: String
    var other: Int
}


final class SomeInteractor {

    func execute(_ request: SomeUseCaseRequest) {

    }

}

Para las respuestas del servidor usé, como los androides, Callbacks. Definia una interfaz de callback para ese interactor y se la pasaba, cuando el interactor acaba de hacer sus tareas, me llamaba a métodos tipo onDoSomethingSuccess(_ response: UseCaseResponse), onNetworkError()

Veía la ventaja de que dependiendo del tipo de error se me llamaba a un método a otro de forma muy limpia. Pero con el tiempo vi otro problema.

Cuando llamas a 3 o 4 interactors en un mismo presenter, tenia 3 o 4 extensiones de ese presenter para las interfaces del callback y seguir el código, sea depurando o leyéndolo se volvia un dolor intestinal que provocaba diarreas infinitas 💩💩💩💩.

Pero como había hecho todos así y solo eran un par de pantallas, no me molesté en darle muchas vueltas. A mi, personalmente, el típico completionBlock: (_ response: UseCaseResponse?, _ error: Error?) -> Void no me gusta nada.

Odio usar opcionales en Swift, si algo te va a devolver un dato o falla, mejor que lance un error o el código se bifurque de algún modo mas como que if let error = error {} else {} y cosas de esas.

También conozco el devolver un Result<T>, y me parece chulo, en algunos lados lo uso, pero me aptecía probar algo distinto.

El interactor actual

Después de haber dado mas vueltas que un caballito en un tiovivo, vamos al final. Mis cosas a cumplir son:

  • Llamar a todos los interactors con execute(request) en caso de que sea necesario.
  • El código de mi interactor que se ejecute en background automáticamente.
  • Una interfaz con then y catch en el hilo principal.
  • Un .common {} también que se ejecute siempre que acaba, vaya bien o mal, así quitamos loadings y mierdas de esas.

Entonces, usando un playground, empezé a hacer pruebas y acabe con esto:

public protocol UseCaseRequest {

}

public protocol UseCaseResponse {

}

open class UseCase<Request, Response> where Response: UseCaseResponse, Request: UseCaseRequest {

    public typealias CommonBlock = () -> Void
    public typealias ThenBlock = (Response) -> Void
    public typealias CatchBlock = (Error) -> Void

    private var commons: [CommonBlock] = []
    private var thens: [ThenBlock] = []
    private var catchs: [CatchBlock] = []

    public var response: Response? {
        didSet {
            if let response = self.response {
                self.executeCommons()
                self.end(&self.thens, response)
            }
        }
    }

    public var error: Error? {
        didSet {
            if let error = self.error {
                self.executeCommons()
                self.end(&self.catchs, error)
            }
        }
    }

    public init() {}

    public func execute(_ request: Request?) -> Self {
        self.clean()
        self.request = request
        DispatchQueue.global().async {
            self.main()
        }
        return self
    }

    open func main() {
        //Override this
    }

    @discardableResult public func common(_ common: @escaping CommonBlock) -> Self {
        print("Assing common")
        self.commons.append(common)
        self.checkIfEnded()
        return self
    }

    @discardableResult public func then(_ then: @escaping ThenBlock) -> Self {
        print("Assign then")
        self.thens.append(then)
        self.checkIfEnded()
        return self
    }

    @discardableResult public func `catch`(_ `catch`: @escaping CatchBlock) -> Self {
        print("Assign catch")
        self.catchs.append(`catch`)
        self.checkIfEnded()
        return self
    }


    private func end<T>(_ blocks: inout [(T) -> Void], _ with: T) {
        print("End with \(with)")
        while !blocks.isEmpty {
            if let first = blocks.first {
                DispatchQueue.main.async {
                    first(with)
                }
                var newBlocks: [(T) -> Void] = []
                for i in 0..<blocks.count {
                    if i > 0 {
                        newBlocks.append(blocks[i])
                    }
                }
                blocks = newBlocks
            }
        }
    }

    private func executeCommons() {
        while !self.commons.isEmpty {
            if let first = commons.first {
                DispatchQueue.main.async {
                    first()
                }
                var newBlocks: [CommonBlock] = []
                for i in 0..<self.commons.count {
                    if i > 0 {
                        newBlocks.append(self.commons[i])
                    }
                }
                self.commons = newBlocks
            }
        }
    }

    private func checkIfEnded() {
        if let response = self.response {
            self.executeCommons()
            self.end(&self.thens, response)
        }
        if let error = self.error {
            self.executeCommons()
            self.end(&self.catchs, error)
        }
    }

    private func clean() {
        self.response = nil
        self.error = nil
        self.commons = []
        self.thens = []
        self.catchs = []
    }

}

Implementamos el interactor así:

struct SomeUseCaseRequest: UseCaseRequest {

}

struct SomeUseCaseResponse: UseCaseResponse {

}

final class SomeInteractor: UseCase<SomeUseCaseRequest, SomeUseCaseResponse> {

    override func main() {
        //This will be executed on background, self.request is available
        //Se hace lo que haga el interactor, asignamos respuesta o error y el ya empieza a ejecutar los bloques.
        self.response = SomeUseCaseResponse()
    }

}

Y finalmente, lo usamos:

let some = SomeInteractor()
some.execute(SomeUseCaseRequest()).common {
    print("common finished1")
}
.then { response in
    print("finished with response1: \(response)")
}
.catch { error in
    print("Finished with error1: \(error)")
}

Mas o menos, he llegado a esto. Creo que lo que queda es que sea Clonable, de este modo, podremos llamar varias veces al execute con distintos bloques y seguirá funcionando si devolvemos la copia en el execute. Ahora si hacemos varios execute seguidos sobre el mismo interactor vamos a tener comportamientos extraños 😖

Pues esto es todo, para cualquier sugerencia, cambio, error o vete tu a saber qué, no dudes en escribirme un mail o ponerme a parir en Twitter 🤗