Class-only generic constraints in Swift
You want AnyObject
, which the Swift docs describe as:
The protocol to which all classes implicitly conform.
class X<T: AnyObject> {
weak var t: T?
}
class C { }
let x = X<C>() // works fine
x.t = C()
// error: type 'Int' does not conform to protocol ‘AnyObject’
// (because Int is a struct)
let y = X<Int>()
Swift extension type constraints for generic class where generic type is yet another generic type
What you want is possible, it's just that the syntax is slightly verbose. You can't put the where
constraint on the extension. You have to put it on each method.
extension Promise {
func failure<U>(_ error: Error) where T == Result<U, Error> {
fulfill(.failure(error))
}
func success<U>(_ result: U) where T == Result<U, Error> {
fulfill(.success(result))
}
}
You can also do it with a protocol, but for enums I find this very klunky, because enum cases can't be treated as conforming methods.
protocol ResultType {
associatedtype Success
associatedtype Failure: Error
static func makeFailure(_: Failure) -> Self
static func makeSuccess(_: Success) -> Self
}
extension Result: ResultType {
static func makeFailure(_ failure: Failure) -> Result<Success, Failure> { .failure(failure) }
static func makeSuccess(_ success: Success) -> Result<Success, Failure> { .success(success) }
}
extension Promise where T: ResultType {
func failure(_ error: T.Failure) {
fulfill(T.makeFailure(error))
}
func success(_ result: T.Success) {
fulfill(T.makeSuccess(result))
}
}
Swift Generic Type Constraint with or operator
This look more like a XOR, since a variable can't be an URL
and a String
at the same time but to answer you question you can't do it!
But I think you already know that since the compiler probably told you :
Consecutive declarations on a line must be separated by ';'
But if you want ValueToSet
to be either URL
, String
or Bool
It should be because these 3 types have something that you need in common, and it means a conformance to a protocol
or inheritance
from a base class.
The 3 types you gave conforms
- CKRecordValueProtocol
- CustomReflectable
- CustomStringConvertible
- CVarArg
- Decodable
- Encodable
- Equatable
- Hashable
- MLDataValueConvertible
So let's say you want to be able to use them as keys in a Dictionary
you could write :
associatedtype ValueToSet where ValueToSet: Hashable
Meaning that ValueToSet
can be ANY type conforming to Hashable
.
But since you seem to be working with UserDefaults
and it's public methods to set default values are not using protocols
nor classes.
eg:
func set(_ url: URL?, forKey defaultName: String)
This approach won't get you far, the one proposed by Sweeper is probably best for you.
Use protocol in swift's generic with type constraint
You are running into the issue of protocols not conforming to themselves. You can resolve this by creating a concrete type conforming to MyProtocol
and converting all your conforming types to that before storing them in your Store
.
class AnyMyProtocol: MyProtocol {
private let _baseMethod: () -> ()
private let _method: () -> ()
init(base: MyProtocol) {
_baseMethod = base.baseMethod
_method = base.method
}
func baseMethod() {
_baseMethod()
}
func method() {
_method()
}
}
extension MyProtocol {
var erased: AnyMyProtocol {
AnyMyProtocol(base: self)
}
}
var otherStorage = Store<AnyMyProtocol>()
class C1 : MyProtocol {
func method() {}
func baseMethod() {}
}
struct S1 : MyProtocol {
func method() {}
func baseMethod() {}
}
enum MyEnum: MyProtocol {
case some
func method() {}
func baseMethod() {}
}
otherStorage.addElement(C1().erased)
otherStorage.addElement(S1().erased)
otherStorage.addElement(MyEnum.some.erased)
Type constraints on contained generic type parameter
The problem is that methods defined in extensions are statically dispatched. So when you have:
class Generic2<T2> {
private let generic1 = Generic1<T2>()
func doSomething() {
generic1.doSomething()
}
}
The compiler cannot know here whether T2
is going to be a String
or not, so it generates a call to the method in the base class. When you explicitly specify that T2
is String
, then with that information, the compiler can generate a call to the extension's method here. Otherwise, though, the type of T2
isn't known until runtime, so you can't reach the extension method via static dispatch.
Swift - Protocol can only be used as a generic constraint because it has Self or associated type requirements
You have to turn these requirements around;
Instead of injecting a MicroServiceProvider into each request, you should write a generic MicroService 'Connector' Protocol that should define what it expects from each request, and what each request expects it to return.
You can then write a TestConnector which conforms to this protocol, so that you have complete control over how your requests are handled. The best part is, your requests won't even need to be modified.
Consider the following example:
protocol Request {
// What type data you expect to decode and return
associatedtype Response
// Turn all the data defined by your concrete type
// into a URLRequest that we can natively send out.
func makeURLRequest() -> URLRequest
// Once the URLRequest returns, decode its content
// if it succeeds, you have your actual response object
func decode(incomingData: Data?) -> Response?
}
protocol Connector {
// Take in any type conforming to Request,
// do whatever is needed to get back some potential data,
// and eventually call the handler with the expected response
func perform<T: Request>(request: T, handler: @escaping (T.Response?) -> Void)
}
These are essentially the bare minimum requirements to setup such a framework. In real life, you'll want more requirements from your Request protocol (such as ways to define the URL, request headers, request body, etc).
The best part is, you can write default implementations for your protocols. That removes a lot of boilerplate code! So for an actual Connector, you could do this:
extension Connector {
func perform<T: Request>(request: T, handler: @escaping (T.Response?) -> Void) {
// Use a native URLSession
let session = URLSession()
// Get our URLRequest
let urlRequest = request.makeURLRequest()
// define how our URLRequest is handled
let task = session.dataTask(with: urlRequest) { data, response, error in
// Try to decode our expected response object from the request's data
let responseObject = request.decode(incomingData: data)
// send back our potential object to the caller's completion block
handler(responseObject)
}
task.resume()
}
}
Now, with that, all you need to do is implement your ProfilePictureRequest like this (with extra example class variables):
struct ProfilePictureRequest: Request {
private let userID: String
private let useAuthentication: Bool
/// MARK: Conform to Request
typealias Response = UIImage
func makeURLRequest() -> URLRequest {
// get the url from somewhere
let url = YourEndpointProvider.profilePictureURL(byUserID: userID)
// use that URL to instantiate a native URLRequest
var urlRequest = URLRequest(url: url)
// example use: Set the http method
urlRequest.httpMethod = "GET"
// example use: Modify headers
if useAuthentication {
urlRequest.setValue(someAuthenticationToken.rawValue, forHTTPHeaderField: "Authorization")
}
// Once the configuration is done, return the urlRequest
return urlRequest
}
func decode(incomingData: Data?) -> Response? {
// make sure we actually have some data
guard let data = incomingData else { return nil }
// use UIImage's native data initializer.
return UIImage(data: data)
}
}
If you then want to send a profile picture request out, all you then need to do is (you'll need a concrete type that conforms to Connector, but since the Connector protocol has default implementations, that concrete type is mostly empty in this example: struct GenericConnector: Connector {}
):
// Create an instance of your request with the arguments you desire
let request = ProfilePictureRequest(userID: "JohnDoe", useAuthentication: false)
// perform your request with the desired Connector
GenericConnector().perform(request) { image in
guard let image = image else { return }
// You have your image, you can now use that instance whichever way you'd like
ProfilePictureViewController.current.update(with: image)
}
And finally, to set up your TestConnector, all you need to do is:
struct TestConnector: Connector {
// define a convenience action for your tests
enum Behavior {
// The network call always fails
case alwaysFail
// The network call always succeeds with the given response
case alwaysSucceed(Any)
}
// configure this before each request you want to test
static var behavior: Behavior
func perform<T: Request>(request: T, handler: @escaping (T.Response?) -> Void) {
// since this is a test, you don't need to actually perform any network calls.
// just check what should be done
switch Self.behavior {
case alwaysFail:
handler(nil)
case alwaysSucceed(let response):
handler(response as! T)
}
}
}
With this, you can easily define Requests, how they should configure their URL actions and how they decode their own Response type, and you can easily write mocks for you connectors.
Of course, keep in mind that the examples given in this answer are quite limited in how they can be used. I would highly suggest you to take a look at this library I wrote. It extends this example in a much more structured way.
Related Topics
Check for Value or Reference Type in Swift
How Does Typecasting/Polymorphism Work with This Nested, Closure Type in Swift
Sending a Parameter Argument to Function Through Uitapgesturerecognizer Selector
Understanding Shorthand Closure Syntax for Map Function in Swift
Swift 4 Timer Crashes with Nsexception
How to Determine If a Variable Passed in Is Reference Type or Value Type
Handle Swiftui and Corelocation with Mvvm-Pattern
Updating Uitableview with Multiple Sections from Rlmresults.Observe()
How to Make Firebase Database Data the Data Source for Uicollection View
Drawing a 3D Arc and Helix in Scenekit
Attaching Audiounit Effects to Scnaudiosource Nodes in Scenekit
Scenekit - Animation with Dae File Format
Deploy App with Pre-Populated Core Data
Xcode Incorrectly Reporting Swift Access Race Condition
Using Nstimer in Swift Playground
Creating a Custom Scngeometry Polygon Plane with Scngeometryprimitivetype Polygon Crash/Error