Asynchronous iteration using Swift Combine - swift

I am trying to do multiple async operations, in sequence, on an array of data. However I am having problems with the return values of map.
Here is the test code:
import Combine
func getLength(_ string: String) -> Future<Int,Error> {
return Future<Int,Error>{ promise in
print("Length \(string.count)")
promise(.success(string.count))
}
}
func isEven(_ int: Int) -> Future<Bool,Error> {
return Future<Bool,Error>{ promise in
print("Even \(int % 2 == 0)")
promise(.success(int % 2 == 0))
}
}
let stringList = ["a","bbb","c","dddd"]
func testStrings(_ strings:ArraySlice<String>) -> Future<Void,Error> {
var remaining = strings
if let first = remaining.popFirst() {
return getLength(first).map{ length in
return isEven(length)
}.map{ even in
return testStrings(remaining)
}
} else {
return Future { promise in
promise(.success(()))
}
}
}
var storage = Set<AnyCancellable>()
testStrings(ArraySlice<String>(stringList)).sink { _ in } receiveValue: { _ in print("Done") }.store(in: &storage)
This generates the following error:
error: MyPlayground.playground:26:11: error: cannot convert return expression of type 'Publishers.Map<Future<Int, Error>, Future<Void, Error>>' to return type 'Future<Void, Error>'
}.map{ even in
I thought we could use map to convert from one publisher type to the other, but it seems it's wrapped inside a Publishers.Map. How do I get rid of this?
Thanks!

Well it seems that this works:
import Combine
func getLength(_ string: String) -> Future<Int,Error> {
return Future<Int,Error>{ promise in
print("Length \(string.count)")
promise(.success(string.count))
}
}
func isEven(_ int: Int) -> Future<Bool,Error> {
return Future<Bool,Error>{ promise in
print("Even \(int % 2 == 0)")
promise(.success(int % 2 == 0))
}
}
let stringList = ["a","bbb","c","dddd"]
func testStrings(_ strings:ArraySlice<String>) -> AnyPublisher<Void,Error> {
var remaining = strings
if let first = remaining.popFirst() {
return getLength(first).flatMap{ length in
return isEven(length)
}.flatMap{ even in
return testStrings(remaining)
}.eraseToAnyPublisher()
} else {
return Future<Void,Error> { promise in
promise(.success(()))
}.eraseToAnyPublisher()
}
}
var storage = Set<AnyCancellable>()
testStrings(ArraySlice<String>(stringList)).sink { _ in } receiveValue: { _ in print("Done") }.store(in: &storage)

Related

How to handle Nested and Paginated Network Call in Swift Combine

I'm using the Notion api which returns all the data I need in Block objects. The struggle is that each Block can be paginated and/or have children that is an array of Block.
Basic Block Structure:
struct Block {
var id: String
var data: [Data]
var hasMore: Bool
var hasChildren: Bool
var childrenId: String
var nextPageId: String
var children: [Block]? = nil
// used for pagination to append data
mutating func accumulator(newRes: BlockResponse) -> BlockResponse {
let combinedArr = self.results + newRes.results
var combinedRes = newRes
combinedRes.results = combinedArr
self = combinedRes
return self
}
}
What I am trying to do:
Pass in list of Block Ids
Get all of the Block data for each ID including all paginated and nested data
Note:
The paginated data should be stored in the original Block data field
The children data should be stored in their parent Block children field
Each child Block can also have paginated data and/or children data
I've gotten semi-close, but I just can't figure out how to get the nested and paginated children data (the last function below). I'm sure I am way overcomplicating this, but right now it basically returns everything properly except the nested children data (it only returns 1 level deep).
Current Code
func getAllBlockData() -> AnyPublisher<[Block], Never> {
return loadAllBlockIds() // just returns a list of ids
.map { $0.results.map { $0 } } // getting the ids from a list of ids
.flatMap(\.publisher)
.flatMap(loadBlockData)
.collect()
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
func loadBlockData(forId id: String) -> AnyPublisher<Block, Never> {
return loadBlock(id: id)
.flatMap(getMoreBlockData)
.flatMap(getChildData)
.eraseToAnyPublisher()
}
func loadBlock(forId id: String) -> AnyPublisher<Block, Never> {
// basic code to get the block data
}
func getMoreBlockData(forBlock block: Block) -> AnyPublisher<Block, Never> {
if block.hasMore {
return loadMoreBlockData(forBlock: block)
}
return Just(block).eraseToAnyPublisher()
}
func getChildData(forBlock block: Block) -> AnyPublisher<Block, Never> {
if block.hasChildren {
return loadChildData(forBlock: block)
}
return Just(block).eraseToAnyPublisher()
}
// Recursively calls its inner function to accumulate the paginated data
func loadMoreBlockData(forBlock block: Block) -> AnyPublisher<Block, Never> {
func doRequest(forId id: String, accumulator: Block) -> AnyPublisher<Block, Never> {
return loadBlock(id: id)
.flatMap { blockRes -> AnyPublisher<Block, Never> in
var mutableAccumulator = accumulator
if blockRes.hasMore {
return doRequest(forId: blockRes.moreId!, accumulator: mutableAccumulator.accumulator(newRes: blockRes))
}
return Just(mutableAccumulator.accumulator(newRes: blockRes)).eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
return doRequest(forId: block.moreId!, accumulator: block)
}
// This is causing me the problems (well, at least the major ones)
func loadChildData(forBlock block: Block) -> AnyPublisher<Block, Never> {
var mutableBlock = block
func doRequest(forId id: String) -> AnyPublisher<Block, Never> {
return loadBlock(id: id)
.flatMap(getMoreBlockData)
.flatMap({ childRes -> AnyPublisher<Block, Never> in
if mutableBlock.children != nil {
mutableBlock.children?.append(childRes)
} else {
mutableBlock.children = [childRes]
}
return Just(childRes).eraseToAnyPublisher()
})
.flatMap(getChildData)
.flatMap { _ -> AnyPublisher<BlockResponse, Never> in
return Just(mutableBlock).eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
return doRequest(forId: block.childId!)
}

"withLatestFrom" won't accept BehaviorRelay

I've got this simple view model to check a phone number's status before registering a user. But I've got this error:
Instance method 'withLatestFrom' requires that 'BehaviorRelay' conform to 'SharedSequenceConvertibleType'
Here's the code:
import Foundation
import RxSwift
import RxCocoa
protocol RegisterViewModelling {
var openRegistrationData: Signal<String> { get }
var showErrorMessage: Signal<String> { get }
var sendButtonActive: Driver<Bool> { get }
var phoneNumberText: BehaviorRelay<String> { get }
var tapSendButton: PublishRelay<Void> { get }
}
final class RegisterViewModel: RegisterViewModelling {
var openRegistrationData: Signal<String>
let showErrorMessage: Signal<String>
let sendButtonActive: Driver<Bool>
let phoneNumberText: BehaviorRelay<String> = BehaviorRelay<String>(value: "")
let tapSendButton: PublishRelay<Void> = PublishRelay<Void>()
init(getPhoneNumberStatus: GetPhoneNumberStatusUseCase) {
sendButtonActive = phoneNumberText
.asDriver(onErrorDriveWith: .empty())
.map(shouldButtonActive(number:))
let isRegistered = tapSendButton
.withLatestFrom(phoneNumberText)
.flatMap(getPhoneNumberStatus.get(number:))
.share()
showErrorMessage = isRegistered
.asSignal(onErrorSignalWith: .just(true))
.filter { $0 == true }
.map { _ in () }
.map(getErrorMessage)
openRegistrationData = isRegistered
.asSignal(onErrorSignalWith: .just(true))
.filter { $0 == false }
.withLatestFrom(phoneNumberText) // ERROR: Instance method 'withLatestFrom' requires that 'BehaviorRelay<String>' conform to 'SharedSequenceConvertibleType'
}
}
private func shouldButtonActive(number: String) -> Bool {
return !number.isEmpty && number.count <= 15
}
private func getErrorMessage() -> String {
return "Phone number has been registered."
}
protocol GetPhoneNumberStatusUseCase {
func get(number: String) -> Observable<Bool>
}
What went wrong here? Why won't withLatestFrom work at that line but it worked fine on the others? Thanks.
I think it because you have convert isRegistered to Signal before use withLatestFrom. You can try to move asSignal() to below withLatestFrom

Storing Generic Objects in a Heterogeneous Array and retrieving object parameter as the correct type

Ahoy everyone,
I have recently been trying to implement a Node based graph system that passes data between nodes using plugs. Similar to many of the 3D applications like houdini and maya.
I have written a similar system before using Python, and wanted to try this with Swift as my first learning excersise. Boy did I jump into the deep end on this one.
I am stuck now with Swifts Arrays, as I would like to store a list of Generic plugs.
Each plug can have its own value type float, int, color, string, Vector Matrix.
I have read up about Type Erasers and opaque types, but still cant seem to get my values our of a list in a way that I can perform some arithmetic on them.
All and any help that might put me in the direction would be greatly appreciated :D
import Foundation
import MetalKit
protocol genericPlug {
associatedtype T
func GetValue() -> T
}
class Plug<T>:genericPlug{
var _value:T?
var value:T {
get{GetValue()}
set(val){
value = val
}
}
func GetValue() -> T{
return _value!
}
init(_ newValue:T){
_value=newValue
}
}
class Node{
var plugs:[genericPlug] = []
init(){
var p1 = Plug<Int>(0)
var p2 = Plug(vector2(1.2, 3.1))
var p3 = Plug([0.0, 3.1, 0.6, 1])
plugs.append(p1)
plugs.append(p2)
plugs.append(p3)
}
func execute(){
// will access the plugs in the array and perform some sort of calculations on them.
plugs[0].value + 1 // should equal 1
plugs[1].value.x + 0.8 // should have x=2.0 y=3.1
plugs[2].value[1] - 0.1 // should equal 3.0
}
}
Thanks everyone
Use a generic something to extract what you need. Your options are methods and subscripts.
protocol PlugValue {
init()
}
extension Int: PlugValue { }
extension Float: PlugValue { }
extension Double: PlugValue { }
extension SIMD3: PlugValue where Scalar == Int32 { }
struct Plug<Value: PlugValue> {
var value: Value
init(_ value: Value) {
self.value = value
}
}
protocol AnyPlug {
var anyValue: PlugValue { get }
}
extension AnyPlug {
subscript<Value: PlugValue>(type: Value.Type = Value.self) -> Value {
anyValue as? Value ?? .init()
}
func callAsFunction<Value: PlugValue>(_ type: Value.Type = Value.self) -> Value {
anyValue as? Value ?? .init()
}
}
extension Plug: AnyPlug {
var anyValue: PlugValue { value }
}
let plugs: [AnyPlug] = [
Plug(1),
Plug(2.3 as Float),
Plug(4.5),
Plug([6, 7, 8] as SIMD3)
]
plugs[0][Int.self] // 1
plugs[1][Double.self] // 0
plugs[1][] as Float // 2.3
let double: Double = plugs[2]() // 4.5
plugs[3](SIMD3.self).y // 7
With the array of protocols, do you have to down cast them into their Plug when retrieving them every time?
Essentially. This is true of all heterogenous sequences. Here are your options:
extension Array: PlugValue where Element: PlugValue { }
let plug: AnyPlug = Plug([0.1, 1.1, 2.1])
(plug as? Plug<[Double]>)?.value[1]
(plug.anyValue as? [Double])?[1]
extension Plug {
enum Error: Swift.Error {
case typeMismatch
}
}
extension AnyPlug {
func callAsFunction<Value: PlugValue, Return>(_ closure: (Value) -> Return) throws {
guard let value = anyValue as? Value
else { throw Plug<Value>.Error.typeMismatch }
closure(value)
}
}
try plug { (doubles: [Double]) in doubles[1] } // 1.1
try plug { ($0 as [Double])[1] } // 1.1
try plug { $0 as Int } // <Swift.Int>.Error.typeMismatch
I managed to find a solution that worked for my needs.
I am still looking at finding a better way to handle getting the data and their correct type.
import Foundation
import MetalKit
// Creating the PlugType Enum
enum PlugType{
case Integer(Int?)
case Float_(Float?)
case Double_(Double?)
case Vector3(simd_int3)
// default types
static func IntegerType() -> PlugType{ return PlugType.Integer(nil)}
static func FloatType() -> PlugType{ return PlugType.Float_(nil)}
static func DoubleType() -> PlugType{ return PlugType.Double_(nil)}
}
// Implements a way to retrieve the correct value type
extension PlugType{
var IntegerValue: Int{
switch self{
case .Integer(let value):
return value ?? 0
default:
return 0
}
}
var FloatValue: Float{
switch self
{
case .Float_(let value):
return value ?? 0
default:
return 0
}
}
var DoubleValue: Double{
switch self
{
case .Double_(let value):
return value ?? 0
default:
return 0
}
}
}
// Get the string representation of the PlugType
extension PlugType {
var typeName: String{
switch self {
case .Integer: return "Integer"
case .Float_: return "Float"
case .Double_: return "Double"
case .Vector3: return "Vector3"
}
}
var swiftType: Any.Type {
switch self {
case .Integer: return Int.self
case .Float_: return Float.self
case .Double_: return Double.self
case .Vector3: return simd_int3.self
}
}
}
class Plug{
var _value:PlugType?
var type:String? { get{ return _value?.typeName } }
init(_ newValue:PlugType){
_value = newValue
}
func geee<T>(_ input:T) -> T{
switch type {
case "Integer":
return getVal(_value!.IntegerValue) as! T
case "Double":
return getVal(_value!.DoubleValue) as! T
default:
return 0 as! T
}
}
func getVal(_ val:Int) -> Int {
return val
}
func getVal(_ val:Float) -> Float {
return val
}
func getVal(_ val:Double) -> Double {
return val
}
}
var plugs:[Plug] = []
var p1 = Plug(PlugType.Integer(2))

What is the main difference between the "return function" and the "callback function"?

I think I am going to change all of my code functions from "callback functions" to "return functions". I don't like the "stairs" look of my code.
Do you think it is a good idea?
I don't understand the difference between the two (except for the asynchronous web service calls that force the use of the callback function in my code).
Callback function:
Declaration:
func methodToSelectData(strQuery : String, dataBase: String, completion: #escaping (_ result: [AnyObject]) -> Void) {
let arryToReturn : [AnyObject] = []
let contactDB = FMDatabase(path: String(methodToCreateDatabase(dataBase: dataBase)!.absoluteString) )
if (contactDB?.open())! {
let results:FMResultSet? = contactDB?.executeQuery(strQuery, withArgumentsIn: nil)
while results?.next() == true {
arryToReturn.add(results!.resultDictionary())
}
if arryToReturn.count == 0 {
completion(arryToReturn)
}
contactDB?.close()
} else {
print("Error: \(String(describing: contactDB?.lastErrorMessage()))")
}
completion(arryToReturn)
}
Usage:
DBHandler.sharedInstance.methodToSelectData(strQuery:"SELECT * FROM table", dataBase: "DB.db", completion: { resultQuery in
if (resultQuery.count > 0) {
...
}
})
Return function
Declaration:
func method2ToSelectData(strQuery : String, dataBase: String) -> [AnyObject] {
let arryToReturn : [AnyObject] = []
let contactDB = FMDatabase(path: String(methodToCreateDatabase(dataBase: dataBase)!.absoluteString) )
if (contactDB?.open())! {
let results:FMResultSet? = contactDB?.executeQuery(strQuery, withArgumentsIn: nil)
while results?.next() == true {
arryToReturn.add(results!.resultDictionary())
}
if arryToReturn.count == 0 {
return arryToReturn
}
contactDB?.close()
} else {
print("Error: \(String(describing: contactDB?.lastErrorMessage()))")
}
return arryToReturn
}
Usage:
let resultQuery = DBHandler.sharedInstance.method2ToSelectData(strQuery:"SELECT * FROM table", dataBase: "DB.db")
if (resultQuery.count > 0) {
...
}
What is the best way to use one or the other? I don't understand the subtlety very well.
It's really a matter of what you need in any given situation.
For something as simple as returning a piece of data, you can do just that:
// Definition //
func newString(firstHalf: String, secondHalf: String) -> String {
return firstHalf + secondHalf
}
// Usage //
print(newString(firstHalf: "Hello", secondHalf: "world"))
Something more complicated, like a data call, might need a completion handler or closure:
// Definition //
func getData(fromEndpoint endpoint: String, completion: (String) -> Void) {
let data = serverData(from: endpoint) //Makes the server request.
completion(data)
}
// Usage //
getData(fromEndpoint: "https://www.reddit.com/.json") { data in
doThings(with: data)
}
You don't necessarily need an asynchronous call to use a closure/callback, but it tends to be one of the most common use-cases for one. As you do more coding in Swift, you'll find more use-cases for each.

Extending SignalProducerType in case Value is an Array<SomeProtocol>

I have a protocol for fetching database objects by PrimaryKey
typealias PrimaryKey = String
protocol PrimaryKeyConvertible {
var pkValue : PrimaryKey { get }
static func pkObject(key: PrimaryKey) -> Self?
}
and I want to extend the SignalProducerType to be able to operate on a SignalProducer.Value of that type.
So the Single object extension (single as in not Array) works fine and implemented as following:
extension SignalProducerType
where Value: PrimaryKeyConvertible
{
func fetchOnMainThread() -> SignalProducer<Value?, Error> {
return
self.map{ (obj: Value) -> PrimaryKey in
return obj.pkValue
}
.observeOn(UIScheduler())
.map{ (key: PrimaryKey) -> Value? in
return Value.pkObject(key)
}
}
}
But when I try to implement it on an Array of these elements i hit some compilation challenges:
extension SignalProducerType
{
func fetchOnMainThread<P: PrimaryKeyConvertible where Self.Value == Array<P>>() -> SignalProducer<[P], Error> { //(1)
return self.map({ (value: Self.Value) -> [PrimaryKey] in
return value.map{ $0.pkValue } //(2)
})
}
}
(1) i suspect that the signature is not communicating the idea to the compiler correctly
(2) produces the following error:
Type of expression is ambiguous without more context
the issue i'm trying to solve is how to let the compiler recognize the the SignalProducer is operating on an Array<P> where P is PrimaryKeyConvertible and have the .map operate on it accordingly ...
my current solution for the array issue is to implement using a generic function as listed below:
func fetchOnMainThread<Value: PrimaryKeyConvertible, Error: ErrorType>
(signal: SignalProducer<[Value], Error>) -> SignalProducer<[Value], Error> {
return signal
.map{ (convertibles: [Value]) -> [PrimaryKey] in
return convertibles.map { $0.pkValue }
}
.observeOn(UIScheduler())
.map{ (keys: [PrimaryKey]) -> [Value] in
return keys.flatMap{ Value.pkObject($0) }
}
}
and then used for example:
extension GoogleContact: PrimaryKeyConvertible {...}
extension GoogleContact {
static func fetchGoogleContactsSignal() -> SignalProducer<[GoogleContact], GoogleContactError> { ...}
}
and the call site would be like:
let signal = fetchOnMainThread(GoogleContacts.fetchGoogleContactsSignal()).onNext...
where I would prefer to have it as an extension where it would flow as usual
GoogleContacts
.fetchGoogleContactsSignal()
.fetchOnMainThread()
Update
another version of the function I've tried : (#J.Wang)
extension SignalProducerType
where Value == [PrimaryKeyConvertible]
{
func fetchArrayOnMainThread2<T: PrimaryKeyConvertible>() -> SignalProducer<[T], Error> {
return self
.map{ (values: Self.Value) -> [PrimaryKey] in
return values.map{ $0.pkValue }
}
.deliverOnMainThread()
.map{ (keys: [PrimaryKey]) -> [T] in
return keys.flatMap{ T.pkObject($0) }
}
}
}
let signal =
GoogleContacts
.fetchGoogleContactsSignal()
.fetchArrayOnMainThread2() //(3)
(3) Generates error:
'[PrimaryKeyConvertible]' is not convertible to '[GoogleContact]'
Hmm, although I'm not quite sure what the problem is, but I think the following implementation might be what you want.
extension SignalProducerType where Value == [PrimaryKeyConvertible]
{
func fetchOnMainThread() -> SignalProducer<[PrimaryKey], Error> {
return self.map { value in
value.map { $0.pkValue }
}
}
}
Try this:
extension SignalProducerType where Value == [PrimaryKeyConvertible]
{
func fetchOnMainThread<T: PrimaryKeyConvertible>() -> SignalProducer<[T], Error> {
return self.map { value in
value.map { $0.pkValue }
}.map { keys in
keys.flatMap { T.pkObject($0) }
}
}
}