I am trying to use the SwiftQueue library. The problem is Xcode says:
Type 'BackgroundJobCreator' does not conform to protocol 'JobCreator'
I have stripped back my class and made it as simplified as possible to find the problem.
class BackgroundJobCreator: JobCreator {
func create(type: String, params: [String : Any]?)->Job {
return BackgroundUploadService(params: params)
}
}
Xcode asks "Do you want to add protocol subs", when I say yes it generates this..
func create(type: String, params: [String : Any]?) -> Job {
[code]
}
The second I delete the old function, the error comes up again stating I am not conforming to protocol 'JobCreator'. The generated one is completely untouched. (I also can not have two as it raises "Invalid redeclaration of 'create(type:params:)'"
I even checked the library's source code to check the protocols were public
/// Protocol to create instance of your job
public protocol JobCreator {
/// method called when a job has be to instantiate
/// Type as specified in JobBuilder.init(type) and params as JobBuilder.with(params)
func create(type: String, params: [String: Any]?) -> Job
}
SwiftQueue Protocols - Github
If the autogenerated protocols are wrong? Then it must be a mistake by the Swift compiler (or whatever checks this stuff)? I raised the issue on the GitHub a couple months back, but I feel this might be an isolated problem on my end.
EDIT: Here is the minimal code request to run..
import Foundation
import SwiftQueue
class BackgroundJobCreator: JobCreator {
func create(type: String, params: [String : Any]?)->Job {
return BackgroundUploadService(params: params)
}
}
-
import Foundation
import SwiftQueue
class BackgroundUploadService: Job{
static let type = ""
private var params: [String: Any]?
required init(params: [String: Any]?) {
// Receive params from JobBuilder.with()
self.params = params
}
required convenience init(from decoder: Decoder) throws {
fatalError("init(from:) has not been implemented")
}
}
Related
I'm migrating this code with Alamofire 3 to Alamofire 4.
The reference is: Creating a generic method with AlamoFire in Swift
I have this class:
import Foundation
import Alamofire
import AlamofireObjectMapper
import ObjectMapper
class HttpHandler {
static func sendRequest<T:BaseUser>(url: String, parameters: [String: AnyObject]?) -> T {
let res : T
Alamofire.request(url)
.validate()
.responseObject { (response: DataResponse<[T]>) in
switch response.result {
case .Success(let value):
res.valueHandle?(value)
case .Failure(let error):
res.errorHandle?(error)
}
}
return res
}
}
class BaseUser: ResponseObjectSerializable {
var valueHandle : ((BaseUser)->())?
var errorHandle : ((NSError)->())?
required init?(response: HTTPURLResponse, representation: AnyObject) {
}
}
public protocol ResponseObjectSerializable {
init?(response: HTTPURLResponse, representation: AnyObject)
}
But, I'm getting this error:
Cannot convert value of type '(DataResponse<[T]>) -> ()' to expected
argument type '(DataResponse<_>) -> Void'
How I can fix it?
You are returning from asynchronous method, and expecting that the result is valid, I think you should rethink your implementation of sendRequest method a bit. You can make it such that it takes in completion closure.
Also, your BaseUser does not seem to be a model class, it rather seems like some handler class, so to say. It only has two properties which are closure, what are you trying to map from http request. Should it have some real attributes.
With that, if you have a pure model class and a proper send method, you can achieve what you are trying pretty easily.
The error in your case is because you are using, AlamofireObjectMapper and trying to map values without actually implementing protocol.
Look at the signature of the method,
public func responseObject<T>(queue: DispatchQueue? = default,
keyPath: String? = default,
mapToObject object: T? = default,
context: MapContext? = default,
completionHandler: #escaping(Alamofire.DataResponse<T>) -> Swift.Void) -> Self where T : BaseMappable
Here the generic parameter T is expected to be of type BaseMapper. If you conform to the protocol Mappable from ObjectMapper, it should be just fine.
class BaseUser: BaseMappable {
func mapping(map: Map) {
// map your actual properties here
// like username, name
}
}
I have also changed your sendRequest method a bit, such that instead of returning values, it does the request and does a callback after finishing.
static func sendRequest<T:BaseUser>(url: String,
parameters: [String: AnyObject]?,
completion: #escaping ([T], Error?) -> Void) {
Alamofire.request(url)
.validate()
.responseObject { (response: DataResponse<[T]>) in
switch response.result {
case .success(let value):
completion(value, nil)
case .failure(let error):
completion([], error)
}
}
}
The code looks fine, but it does not yet work. You are expecting that your response to be mapped to array of BaseUser, but AlamofireObjectMapper, expect it to be of type, Mappable.
Now you can use extension to array and conform to the protocol. We can make use of Swift 4.1 Conditional Conformance here.
extension Array: BaseMappable where Element: BaseMappable {
public mutating func mapping(map: Map) {
var userHolder: [BaseUser] = []
// map array of values to your BaseUser
}
}
And with this you are good to go. Note, that I showed you how the problem that you had could be solved. I also did some small refactoring just to make code more clearer.
Please make changes according to your need.
I'm looking to create a very generic service layer that looks to call Alamofire. See code:
func getRequest(from endpoint:String!, withParameters parameters:[String:Any]?,withModel model:RuutsBaseResponse, andCompleteWith handler:#escaping (RuutsBaseResponse?, NSError?) -> ()){
func generateModel(withResponse result:NSDictionary?, withError error:NSError?) -> (){
handler(model.init(fromDictionary: result),error);
}
alamoFireService.AlamoFireServiceRequest(endpoint:endpoint, httpVerb:.get, parameters:parameters!, completionHandler:generateModel);
}
This is what the RuutsBaseResponse looks like:
protocol RuutsBaseResponse {
init(fromDictionary dictionary: NSDictionary);
}
The getRequest looks to do the following:
Taken in any class so long as it conforms to RuutsBaseResponse protocol.
Make a service call using alamoFire using the parameters passed into it.
alamoFire will call the generateModel method once the service call is completed.
When it calls the generateModel the method is supposed to instantiate the model and pass into it the Dictionary received from alamoFire.
The issue is the model, i'm struggling to achieve the requirements above. I keep getting:
Error:(22, 21) 'init' is a member of the type; use 'type(of: ...)' to
initialize a new object of the same dynamic type
All I'm looking to do is make a layer generic enough to make a service call and create an object/model that is created from the Dictionary passed back from alamoFire.
What you are looking for is how to use Generics:
protocol RuutsBaseResponse {
init(fromDictionary dictionary: NSDictionary);
}
struct BaseModel: RuutsBaseResponse {
internal init(fromDictionary dictionary: NSDictionary) {
print("instantiated BaseModel")
}
}
struct SecondaryModel: RuutsBaseResponse {
internal init(fromDictionary dictionary: NSDictionary) {
print("instantiated SecondaryModel")
}
}
// state that this function handles generics that conform to the RuutsBaseResponse
// protocol
func getRequest<T: RuutsBaseResponse>(handler: (_ response: T) -> ()) {
handler(T(fromDictionary: NSDictionary()))
}
getRequest(handler: { model in
// will print 'instantiated BaseModel'
(model as! BaseModel)
})
getRequest(handler: { model in
// will print 'instantiated SecondaryModel'
(model as! SecondaryModel)
})
Here is my sample code which brings my questions:
protocol Decodable {
init?(json: [String: AnyObject]) // remove this line will solve the problem
}
extension UIFont: Decodable { // remove `Decodable` will also solve the problem
convenience init?(json: [String: AnyObject]) { // Error: Initialiser requirement 'init(json:)' can only be satisfied by a `required` initializer in the definition of non-final class 'UIFont'
self.init(name: "whatever", size: 10)
}
}
The code above tested with Xcode 7.3 & Swift 2.2
Anyone have an idea about this error, why there is somehow a relation with protocol defined method ?
If I observe a property using KVO, if the observer is a generic class then I receive the following error:
An -observeValueForKeyPath:ofObject:change:context: message was
received but not handled.
The following setup demonstrates the problem succinctly. Define some simple classes:
var context = "SomeContextString"
class Publisher : NSObject {
dynamic var observeMeString:String = "Initially this value"
}
class Subscriber<T> : NSObject {
override func observeValueForKeyPath(keyPath: String,
ofObject object: AnyObject,
change: [NSObject : AnyObject],
context: UnsafeMutablePointer<Void>) {
println("Hey I saw something change")
}
}
Instantiate them and try to observe the publisher with the subscriber, like so (done here inside a UIViewController subclass of a blank project):
var pub = Publisher()
var sub = Subscriber<String>()
override func viewDidLoad() {
super.viewDidLoad()
pub.addObserver(sub, forKeyPath: "observeMeString", options: .New, context: &context)
pub.observeMeString = "Now this value"
}
If I remove the generic type T from the class definition then everything works fine, but otherwise I get the "received but not handled error". Am I missing something obvious here? Is there something else I need to do, or are generics not supposed to work with KVO?
Explanation
There are two reasons, in general, that can prevent a particular Swift class or method from being used in Objective-C.
The first is that a pure Swift class uses C++-style vtable dispatch, which is not understood by Objective-C. This can be overcome in most cases by using the dynamic keyword, as you obviously understand.
The second is that as soon as generics are introduced, Objective-C looses the ability to see any methods of the generic class until it reaches a point in the inheritance hierarchy where an ancestor is not generic. This includes new methods introduced as well as overrides.
class Watusi : NSObject {
dynamic func watusi() {
println("watusi")
}
}
class Nguni<T> : Watusi {
override func watusi() {
println("nguni")
}
}
var nguni = Nguni<Int>();
When passed to Objective-C, it regards our nguni variable effectively as an instance of Watusi, not an instance of Nguni<Int>, which it does not understand at all. Passed an nguni, Objective-C will print "watusi" (instead of "nguni") when the watusi method is called. (I say "effectively" because if you try this and print the name of the class in Obj-C, it shows _TtC7Divided5Nguni00007FB5E2419A20, where Divided is the name of my Swift module. So ObjC is certainly "aware" that this is not a Watusi.)
Workaround
A workaround is to use a thunk that hides the generic type parameter. My implementation differs from yours in that the generic parameter represents the class being observed, not the type of the key. This should be regarded as one step above pseudocode and is not well fleshed out (or well thought out) beyond what's needed to get you the gist. (However, I did test it.)
class Subscriber : NSObject {
private let _observe : (String, AnyObject, [NSObject: AnyObject], UnsafeMutablePointer<Void>) -> Void
required init<T: NSObject>(obj: T, observe: ((T, String) -> Void)) {
_observe = { keyPath, obj, changes, context in
observe(obj as T, keyPath)
}
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
_observe(keyPath, object, change, context)
}
}
class Publisher: NSObject {
dynamic var string: String = nil
}
let publisher = Publisher()
let subscriber = Subscriber(publisher) { _, _ in println("Something changed!") }
publisher.addObserver(subscriber, forKeyPath: "string", options: .New, context: nil)
publisher.string = "Something else!"
This works because Subscriber itself is not generic, only its init method. Closures are used to "hide" the generic type parameter from Objective-C.
I have a protocol StateMachineDelegate, a class DataSource that conforms to it, and a class StateMachine that has a delegate with such protocol
Both classes implement a function found on the protocol so that if the class has a delegate, let them handle the functions; otherwise the class handles it itself.
StateMachine contains a function like this:
func target() -> AnyObject {
return delegate ?? self
}
My full code goes like this:
import Foundation
#objc protocol StateMachineDelegate {
optional func stateWillChange()
optional func stateDidChange()
optional func missingTransitionFromState(fromState: String?, toState: String?) -> String
}
class StateMachine {
var delegate: StateMachineDelegate?
func target() -> AnyObject {
return delegate ?? self
}
func missingTransitionFromState(fromState: String, toState: String) -> String {
return "Hello"
}
}
class DataSource: StateMachineDelegate {
func missingTransitionFromState(fromState: String?, toState: String?) -> String {
return "Hi"
}
}
When I was running some tests in playground and the StateMachine instance did not possess a delegate the target function returned the same instance as AnyObject. But once I called missingTransitionFromState from target it crashed so I change it to missingTransitionFromState?() with returned nil
Last function line should have returned "Hello"
Once delegate was given the target returned the delegateObject and proceeded to run the function as normal
The playground test are these:
All of your calls to missingTransitionFromState have a ? at the end except for the last one that won't execute. Replacing the ! with a ? fixes the problem. I didn't really understand what the code is doing but the question mark fixes it.
Making the following changes fixes the problem:
Annotating StateMachine's missingTransitionFromState method with dynamic
Changing the parameter types in StateMachine's missingTransitionFromState method from String to String?, to match the signature of the other missingTransitionFromState method.
I believe that the method needs dynamic or #objc in order to be able to be called dynamically using AnyObject. However, after adding that, the compiler will complain that calls to missingTransitionFromState on an AnyObject is ambiguous, because there are two signatures, so you have to fix the signature.