Static generic extension for Array for help function for iterated async callbacks - swift

I have two help methods that I want to move to an extension to increase it's reusability. The two methods manages multiple callbacks when calling parallell http request. However when making the methods static and moving them to an extension I get this error:
Cannot convert value of type '[Action]' to expected argument type '[_]'
The code is
extension Array
{
private static func iterateObjectList<Type>(objectList:[Type], multiplier:Int=1, foreach:(object:Type, (newObject:Type?, error:NSError?) -> Void) -> (), finally: (objectList:[Type], errorList:[NSError]) -> Void)
{
var iterationsLeft = objectList.count * multiplier
var errorList:[NSError] = []
var returnObjectList:[Type] = []
if (iterationsLeft == 0) {
finally (objectList:objectList, errorList:[])
}
for object:Type in objectList {
foreach (object:object, { (requestObject, requestError) -> Void in
iterationsLeft -= 1
if (requestError != nil) {
errorList.append(requestError!);
}
if (requestObject != nil) {
returnObjectList.append(requestObject!)
}
if (iterationsLeft <= 0) {
finally (objectList:returnObjectList, errorList:errorList)
}
})
}
}
private static func simpleIterate<Type>(objectList:[Type], multiplier:Int=1, foreach:(object:Type, Void -> Void) -> (), finally: Void -> Void)
{
var iterationsLeft = objectList.count * multiplier
if (iterationsLeft == 0) {
finally ()
}
for object:Type in objectList {
foreach (object:object, { Void -> Void in
iterationsLeft -= 1
if (iterationsLeft <= 0) {
finally ()
}
})
}
}
}
The error is when using the methods:
Array.iterateObjectList(actions, foreach: { (action, iterationComplete) -> () in
self.fetchStatusAndUpdateAction(action, callback: { (error) -> Void in
iterationComplete(newObject: action, error:error)
})
}, finally: { (objectList, errorList) -> Void in
callback(error: errorList.first)
})
where actions is of type [Action] where Action is a custom object.

The problem is that
public struct Array<Element>
is a generic type, and in
Array.iterateObjectList(actions, foreach: { (action, iterationComplete) -> () in
// ...
}, finally: { (objectList, errorList) -> Void in
// ...
})
the compiler cannot infer what Element should be. You could
make it compile as
Array<Action>.iterateObjectList(actions, foreach: { (action, iterationComplete) -> () in
// ...
}, finally: { (objectList, errorList) -> Void in
// ...
})
or even
Array<Int>.iterateObjectList(...)
The array Element is unrelated to your generic placeholder Type,
so any type will do.
But the better solution would be to make the static method an
instance method:
func iterateObjectList(multiplier:Int=1, foreach:(object:Element, (newObject:Element?, error:NSError?) -> Void) -> (), finally: (objectList:[Element], errorList:[NSError]) -> Void)
{
// Your code with `Type` replaced by `Element`,
// and `objectList` replaced by `self`.
// ...
}
and call it on the actions array:
actions.iterateObjectList(foreach: { (action, iterationComplete) -> () in
// ...
}, finally: { (objectList, errorList) -> Void in
// ...
})

Related

Observable extension with generic type

Context
I want to wrap the Alamofire.upload into an observable and having info regarding the upload progress.
For that I have created a custom UploadElement that is an enum representing either the progress and the value or the result. So far I have:
enum UploadElement<Result> where Result: Codable {
case progress(Double)
case response(Result)
}
private func buildUploadRequest(url: URL, parts: [Data]) -> Observable<UploadRequest> {
let uploadRequest = manager.upload(
multipartFormData: { multipartFormData in /* build multipart */ },
to: url
)
return Observable.just(uploadRequest)
}
func upload<Result: Codable>(url: URL, parts: [Data]) -> Observable<UploadElement<Result>> {
buildUploadRequest(url: url, parts: parts)
.flatMap { request in
Observable<UploadElement<Result>>.create { observer in
request.response { response in
do {
observer.on(.next(.response(/* decode here */)))
observer.on(.completed)
} catch let error {
observer.on(.error(error))
}
}.uploadProgress { progress in
observer.on(.next(.progress(progress.fractionCompleted)))
}
.resume()
return Disposable.create { request.cancel() }
}
}
}
Now I would like to have an extension on an Observable<UploadEment<Result>> to have a nicer way to be notified.
Basically it would be:
service.upload(url: ..., parts: ...)
.progress { progress in /* */ }
.result { result in /* */ }
.subscribe()
.dispose(by: disposeBag)
To do that I tried:
extension ObservableType where Element == UploadElement<Resource> {
func progress(progressCompletion: #escaping (Double) -> Void) -> Self {
return self.do(onNext: { element in
switch element {
case .progress(let progress): progressCompletion(progress)
case .response: return
}
})
}
func result(resultCompletion: #escaping (Result) -> Void) -> Self {
return self.do(onNext: { element in
switch element {
case .response(let result): resultCompletion(result)
case .progress: return
}
})
}
}
I tried multiple variation of that but the errors that I get are:
Cannot find 'Result in scope'
Reference to generic type ... required argument
Is it possible to achieve something like that?
You just need to move the where clause from class scope down to function scope (shown below).
That said, I don't think breaking out of the monad like this in the middle of a stream is "a nicer way to be notified".
Better would be to break your Observable into two streams and subscribe to each of them:
extension ObservableType {
func progress<Resource>() -> Observable<Double> where Element == UploadElement<Resource> {
self.compactMap { element in
switch element {
case let .progress(progress):
return progress
case .response:
return nil
}
}
}
func result<Resource>() -> Observable<Resource> where Element == UploadElement<Resource> {
self.compactMap { element in
switch element {
case .progress:
return nil
case let .response(resource):
return resource
}
}
}
}
With the above you can now do something like this:
let response = service.upload(url: ..., parts: ...)
.share()
response
.progress()
.subscribe(onNext: { progress in /*...*/ })
.disposed(by: disposeBag)
response
.result()
.subscribe(onNext: { result in /*...*/ })
.dispose(by: disposeBag)
Now you don't have any empty subscribes.
I found something that is working:
extension ObservableType {
func progress<O: Codable>(progressCompletion: #escaping (Double) -> Void) -> Observable<UploadElement<O>> where Element == UploadElement<O> {
return self.do(onNext: { element in
if case .progress(let progress) = element {
progressCompletion(progress)
}
})
}
func response<O: Codable>(responseCompletion: #escaping (O) -> Void) -> Observable<UploadElement<O>> where Element == UploadElement<O> {
return self.do(onNext: { element in
if case .response(let response) = element {
responseCompletion(response)
}
})
}
}
Now I can use the "planned" api:
service.update(data: /* ... */)
.progress { progress in /* */ }
.response { result in /* */ }
.subscribe(
onError: { error in /* */ }
)
.dispose(by: disposeBag)
However as Daniel mentioned this might not be the "nicer way of being notified".

How to create closure calling closure with less parameters in swift

I'm trying to create class in swift, that can take closure with 0..N arguments and then when callback function with N arguments will be invoked, will pass only required amount to closure.
I'm trying to do it like that:
class CallbackImpl: AuthServiceLogoutCallback {
private let callback: ((UUID, AuthServiceLogoutType) -> Void )?
public init( cb: #escaping ((UUID, AuthServiceLogoutType) -> Void ))
{
callback = { cb( $0, $1 ) }
}
public init( cb: #escaping ((UUID) -> Void ))
{
callback = { cb( $0 ) }
}
public init( cb: #escaping (() -> Void ))
{
callback = { cb() }
}
public func onEvent(_ userId: UUID, type: AuthServiceLogoutType)
{
callback!( userId, type )
}
}
First init with closure with two arguments is ok, inits with closures with 1 and 0 arguments give me error expression type '()' is ambiguous without more context
What is the right way to do such a thing?
You can achieve what you are trying, if you give more context as to which closure parameter you are using and which not:
class CallbackImpl: AuthServiceLogoutCallback {
private let callback: ((UUID, AuthServiceLogoutType) -> Void)?
public init(cb: #escaping ((UUID, AuthServiceLogoutType) -> Void)) {
callback = { cb($0, $1) }
}
public init(cb: #escaping ((UUID) -> Void)) {
callback = { uuid, _ in cb(uuid) }
}
public init(cb: #escaping (() -> Void)) {
callback = { _, _ in cb() }
}
public func onEvent(_ userId: UUID, type: AuthServiceLogoutType) {
callback!( userId, type )
}
}

Swift: async execution in series + array of closures + escaping

I have a few async function which I'd like to run in series to avoid "callback hell" and simplify things I've written the following helper structure:
typealias AsyncCallback = (Bool) -> Void
typealias AsyncFunction = (AsyncCallback) -> Void
public struct AsyncHelpers {
public static func series(_ functions: [AsyncFunction], _ callback: #escaping AsyncCallback) {
var index = 0
var completed: AsyncCallback? = nil
completed = { success in
if success == false { callback(false); return }
index += 1
if index < functions.count {
functions[index](completed!)
return
}
callback(true)
}
functions[index](completed!)
}
}
AsyncHelpers.series([
{ callback in
// do async stuff
callback(true)
}, { callback in
// then do async stuff
callback(true)
}
]) { callback in
// when all completed
}
I can set the #escaping attribute for completion handler, but when I try to apply this attribute to [AsyncFunction] compilator fails with this error: "#escaping attribute only applies to function types". Should I mark these closured as escaping in other way, please?
What is lifecycle of index variable, can I use it inside completed closure without any problem?

Obscure low-level fatal error in Swift

I have a Swift implementation of an Ordered Set that I believe is correct (if not very efficient), since NSOrderedSet won't work with non-ObjC structures. However, when using the class in a multi-threaded app, I occasionally get a very low level crash which reports nothing on the console but "fatal error", and shows this in the debugger:
function signature specialization <preserving fragile attribute, Arg[1] =
[Closure Propagated : reabstraction thunk helper from #callee_owned
(#unowned Swift.UnsafeBufferPointer<Swift.UInt8>) -> () to
#callee_owned (#unowned Swift.UnsafeBufferPointer<Swift.UInt8>) ->
(#out ()), Argument Types : [#callee_owned (#unowned
Swift.UnsafeBufferPointer<Swift.UInt8>) -> ()]> of generic
specialization <preserving fragile attribute, ()> of
Swift.StaticString.withUTF8Buffer <A>
((Swift.UnsafeBufferPointer<Swift.UInt8>) -> A) -> A
The code being executed is this:
import Foundation
fileprivate struct OrderedSetElementWrapper<T>: Hashable, Equatable where T: Hashable, T: Equatable {
let value: T
let index: Int
var hashValue: Int { return self.value.hashValue }
}
fileprivate func ==<T>(lhs: OrderedSetElementWrapper<T>, rhs: OrderedSetElementWrapper<T>) -> Bool {
return lhs.value == rhs.value
}
public struct OrderedSet<T> where T: Hashable, T: Equatable {
private var _set = Set<OrderedSetElementWrapper<T>>()
init() {}
init(array: [T]) { array.forEach { self.insert($0) } }
var array: [T] {
// ***** Error intermittently on next line ******
let sortedArray = self._set.sorted { $0.index < $1.index }
// **********************************************
let mapped = sortedArray.map { $0.value }
return mapped
//return self._set.sorted(by: { $0.index < $1.index }).map({ $0.value })
}
var set: Set<T> { return Set(self._set.map { $0.value } ) }
var count: Int { return self._set.count }
func contains(item: T) -> Bool { return self.set.contains(item) }
mutating func insert(_ item: T) {
self._set.insert(OrderedSetElementWrapper(value: item, index: self._set.count))
}
mutating func remove(_ item: T) {
var newSet = Set<OrderedSetElementWrapper<T>>()
for element in self._set {
if element.value != item {
newSet.insert(OrderedSetElementWrapper(value: element.value, index: newSet.count))
}
}
self._set = newSet
}
}
The class works perfectly the vast majority of the time, I'm reasonably sure of the logic, and I simply don't understand the error message! Can anyone explain the error message?
The error is triggered by a call to the .array property. The calling class is
public class XXXItemGroupBase: XXXItemBase {
// ***************************************
// MARK: New properties
// ***************************************
private var _theItems = OrderedSet<XXXItemBase>()
// _theItems is used as a private backing store.
// Do NOT access it directly outside of the class -
// use self.components instead...
// ***************************************
// MARK: Overridden properties
// ***************************************
override public var components: [XXXItemBase] { // The public face of self._theItems
get { return self._theItems.array } // **** CRASH HERE ****
set { self._theItems = OrderedSet(array: newValue) }
}

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) }
}
}
}