PromiseKit 6, Alamofire, Xcode 10.2/Swift 5 - swift

I'm having trouble writing the following code:
public
func
get(organization inSID: String)
-> Promise<Organization>
{
URLSession.showNetworkActivity()
return firstly
{
let req = buildRequest(path: "/api/\(inUUID)", date: inDate, headers: [.organizationSID : inSID])
self.mgr.request(req).responseJSON()
}
.map()
{ inData, inResp in
return Organization(sid: "")
}
.ensure
{
URLSession.hideNetworkActivity()
}
}
I get an error on firstly: Ambiguous reference to member 'firstly(execute:)'
After adding import PMKAlamofire to the top of my file, and being more explicit, I get this to compile:
public
func
get(organization inSID: String)
-> Promise<Organization>
{
URLSession.showNetworkActivity()
return firstly
{ () -> Promise<(json: Any, response: PMKAlamofireDataResponse)> in
let req = buildRequest(path: "/api/v2/organizations/\(inSID)", headers: [.organizationSID : inSID])
return self.mgr.request(req).responseJSON()
}
.map()
{ inResp in
return Organization(sid: "")
}
.ensure
{
URLSession.hideNetworkActivity()
}
}
Note the added explicit () -> Promise<(json: Any, response: PMKAlamofireDataResponse)> and the explicit return statement in the firstly closure. I don't know if this is now required by Swift 5 or it still can't properly infer types.

Related

PromiseKit wrapping external closure in Promises

I am using an external library in Swift so I cannot control the return statements. My understanding is that I should wrap these returns in promises in order to use PromiseKit. Is this correct?
Assuming so, I have working code as follows:
private func getChannelImage(for channel: TCHChannel, completion: #escaping (UIImage?, CAProfileError?) -> Void) {
if let members = channel.members {
members.members(completion: { (result, paginator) in
if result.isSuccessful() {
// ... do something
}
else {
completion(nil, CAProfileError.UnknownError)
}
})
}
}
This can be difficult to read. I am trying to simplify this using PromiseKit. First, I want to simplify members.members(completion: { (result, paginator) in to a promise that I can call with the firstly { ... } syntax. I thus try and do as follows:
private func asPromise(members: TCHMembers) -> Promise<TCHMemberPaginator> {
return Promise<TCHMemberPaginator> { fulfill, reject in
members.members(completion: { (result, paginator) in
if result.isSuccesful() {
fulfill(paginator)
} else {
reject()
}
})
}
}
But this approach does not work and I get "Unable to infer closure type in the current context". I'm trying to find a good example of this use case done online but am having trouble. Any thoughts on how to properly return promises?
Assuming the TCHMemberPaginator and TCHMembers as below,
class TCHMemberPaginator {}
class TCHMembers {
func members(completion: (Bool, TCHMemberPaginator?) -> Void) {}
}
Here is the method to return a Promise,
private func asPromise(members: TCHMembers) -> Promise<TCHMemberPaginator> {
return Promise { seal in
members.members(completion: { (result, paginator) in
if result == true, let p = paginator {
seal.fulfill(p)
} else {
seal.reject(NSError())
}
})
}
}

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

What does this Swift type error mean? It reads like what's expected is already the case

I'm stuck on a compilation error that I'm having trouble interpreting. The expected argument type appears identical to what I've passed to it, so I'm not sure what to make of it. Probably best to just paste the error and relevant bits of code.
Here's where it fails:
static func DefaultProvider() -> ReactiveManabiAPIProvider<ManabiAPI> {
return ReactiveManabiAPIProvider(endpointClosure: endpointsClosure,
requestClosure: endpointResolver(),
stubClosure: StubBehaviour,
plugins: APIProvider.plugins)
}
And the error message:
error: cannot convert value of type '(ManabiAPI) -> Endpoint' to expected argument type '_ -> Endpoint<_>'
return ReactiveManabiAPIProvider(endpointClosure: endpointsClosure,
^~~~~~~~~~~~~~~~
And some of the other relevant code referenced above:
enum ManabiAPI {
[...]
}
extension ManabiAPI : TargetType {
var baseURL: NSURL { return NSURL(string: "[...]")! }
var path: String {
[...]
}
var parameters: [String: AnyObject]? {
[...]
}
var method: ReactiveMoya.Method {
[...]
}
[...]
}
public class ReactiveManabiAPIProvider<Target where Target: TargetType> : ReactiveCocoaMoyaProvider<Target> {
var appController: AppController
var requestErrors: RACSubject
override init(endpointClosure: MoyaProvider<Target>.EndpointClosure = MoyaProvider.DefaultEndpointMapping,
requestClosure: MoyaProvider<Target>.RequestClosure = MoyaProvider.DefaultRequestMapping,
stubClosure: MoyaProvider<Target>.StubClosure = MoyaProvider.NeverStub,
manager: Manager = Alamofire.Manager.sharedInstance,
plugins: [PluginType] = [], stubScheduler: DateSchedulerType? = nil) {
let appDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
appController = appDelegate.appController!
requestErrors = RACSubject()
super.init(endpointClosure: endpointClosure, requestClosure: requestClosure, stubClosure: stubClosure, manager: manager, plugins: plugins)
}
[...]
}
Please let me know if it'd be useful to see anything else.
I have a difficult time trying this solution in a playground as your example above is still a bit too non-minimal. But I suspect you need to explicitly state the generic Target type (under type constraint TargetType) in the ReactiveManabiAPIProvider initializer in the return clause of your function DefaultProvider(). E.g.:
static func DefaultProvider() -> ReactiveManabiAPIProvider<ManabiAPI> {
return ReactiveManabiAPIProvider<ManabiAPI>(endpointClosure: endpointsClosure,
requestClosure: endpointResolver(),
stubClosure: StubBehaviour,
plugins: APIProvider.plugins)
}
Otherwise your return initializes a ReactiveManabiAPIProvider without an associated generic, and naturally the following return cast fails as function return expects a ReactiveManabiAPIProvider instance with generic Target type ManabiAPI.
That's the code where I have exactly the same problem (i'm using Moya 6.5.0)
public protocol ApiService {
var apiProvider: ReactiveCocoaMoyaProvider<ApiRouter> { get }
func request(token: ApiRouter) -> SignalProducer<Response, NSError>
}
public class ApiServiceImpl: ApiService {
private let application: UIApplication
public var apiProvider: ReactiveCocoaMoyaProvider<ApiRouter>
public init(stubbing: Bool, application: UIApplication) {
self.application = application
if stubbing == false {
let endpointsClosure = { (target: ApiRouter) -> Endpoint<ApiRouter> in
return Endpoint<ApiRouter>(
URL: ApiServiceImpl.url(target),
sampleResponseClosure: { .NetworkResponse(200, target.sampleData) },
method: target.method,
parameters: target.parameters
)
}
let networkActivityPlugin = NetworkActivityPlugin { type in
switch type {
case .Began : application.networkActivityIndicatorVisible = true
case .Ended : application.networkActivityIndicatorVisible = false
}
}
let networkLoggerPlugin = NetworkLoggerPlugin(verbose: true, responseDataFormatter: JSONResponseDataFormatter)
let plugins = [networkActivityPlugin, networkLoggerPlugin]
apiProvider = ReactiveCocoaMoyaProvider<ApiRouter>(endpointClosure: endpointsClosure, plugins: plugins)
} else {
apiProvider = ReactiveCocoaMoyaProvider<ApiRouter>(stubClosure: { _ in .Immediate })
}
}
public func request(token: ApiRouter) -> SignalProducer<Response, NSError> {
return apiProvider
.request(token)
.filterSuccessfulStatusAndRedirectCodes()
}
private static func url(route: TargetType) -> String {
return route.baseURL.URLByAppendingPathComponent(route.path).absoluteString
}
}
And i fixed that error by just adding [PluginType] to plugins array definition line, so it becomes
let plugins: [PluginType] = [networkActivityPlugin, networkLoggerPlugin]

Migration to Swift 2 "Use of unresolved identifier '***'"

I'm upgrading my project from Swift 1.2 to Swift 2.
I use this occasion to upgrade lot of the lib that I use, in particular Alamofire.
But now I got this error on many of my request:
Use of unresolved identifier 'notifTypeJSON'
Here is the code of one of the func:
func getNotifications(failure failure: (NSError) -> (), success: ([Notification]) -> ()) {
Alamofire.request(Router.Notifications)
.validate()
.responseJSON { response in
if let error = response.result.error {
failure(error)
} else {
var json = JSON(response.data!)
let status = json["error"].intValue
if status != 0 {
failure(self.createError(status))
} else {
var notifications = [Notification]()
let notificationsList = json["notification"]
for (index: String, notifTypeJSON: JSON) in notificationsList {
if let notifJSON = notifTypeJSON[NotificationTypes.Generic.rawValue].dictionaryObject {
notifications.append(GenericNotification(json: notifJSON))
}
else if let notifJSON = notifTypeJSON[NotificationTypes.Follow.rawValue].dictionaryObject {
notifications.append(FollowNotification(json: notifJSON))
}
else if let notifJSON = notifTypeJSON[NotificationTypes.Comment.rawValue].dictionaryObject {
notifications.append(CommentNotification(json: notifJSON))
}
else if let notifJSON = notifTypeJSON[NotificationTypes.Like.rawValue].dictionaryObject {
notifications.append(LikeNotification(json: notifJSON))
}
}
DDLogInfo("SeetyAPI getNotifications() success")
success(notifications)
}
}
}
}
In Swift 2 the way we loop over a dictionary with a tuple has changed: the types have to be in a separate tuple.
Example Before:
for (key:String, value:Int) in xxx {
Example After:
for (key, value):(String, Int) in xxx {
So for you, you would need to replace this:
for (index: String, notifTypeJSON: JSON) in notificationsList {
With this:
for (index, notifTypeJSON):(String, JSON) in notificationsList {

Swift callback inline function usage

Using Swift I can do a callback like the following:
userService.validateToken("6beba35f", success: onSuccess, failure: onFailure)
func onSuccess(status_code: Int, data: String)
{
var dd : String = ""
}
func onFailure(status_code: Int, data: String)
{
var dd : String = ""
}
but i would like to have the functions on the same line as the call:
Example 1:
userService.validateToken("6beba35f",
success: (Int, String) -> ()
{
},
failure: (Int, String) -> ()
{
})
Example 2:
userService.validateToken("6beba35f",
success: (Int, String)
{
},
failure: (Int, String)
{
})
both give errors. I think im close with Example 1 but it keeps giving me an error "Expected , seperator" and when i have it add the ","
success: (Int, String), -> ()
but the error keeps saying "Expected , separator"
Any ideas on what the solutions is?
Including function
func validateToken(token: String, success: (Int, String) -> Void, failure: (Int, String) -> Void)
{
if(Network.isOnline())
{
var url: String = Commons.plistValue("Base URL") + "/security/" + token
Alamofire.request(.GET, url)
.responseJSON { (request, response, json, error) in
let jsonData: JSON = JSON(json!)
let statusCode: Int = response!.statusCode
if(statusCode == 202)
{
success(statusCode, jsonData["status"]["message"].stringValue)
}
else
{
failure(statusCode, jsonData["status"]["message"].stringValue)
}
}
}
else
{
failure(-1, "No Internet Connection")
}
}
Usage Fix
userService.validateToken("6beba35f",
success: { status_code, data in
(
println(status_code)
)
},
failure: { status_code, data in
(
println(status_code)
)
})
There are several ways to declare or use closures. The simplest one you're looking for is probably:
{ status_code, data in
println(status_code)
}
This needs to be used in such a way that the compiler can infer the type of status_code, data, and determine that there should be no return value. For instance, you can pass it as a function parameter (what you want), or assign it to a variable with appropriate type hints.