Difference between T and Self in generic methods - swift

I’m writing a protocol called JSONDataInitializable, which will enable values to be initialized from Data that contains JSON using JSONDecoder.
Since it’s not possible to explicitly use generics within initializers, I declared a universal, type-agnostic helper method in a protocol extension, which the initializer later calls.
However, I came up with not one, but two ways to write such a method.
(1):
private static func initialize<T: JSONDataInitializable>(from jsonData: Data) throws -> T {
return try JSONDecoder().decode(T.self, from: jsonData)
}
(2):
private static func initialize(from jsonData: Data) throws -> Self {
return try JSONDecoder().decode(Self.self, from: jsonData)
}
Could you explain the difference between these methods? They both seem to produce the same result.
The only visible difference is the protocol conformance part in the first variant. However, the methods are declared in a protocol extension and therefore only available to types which conform to the protocol.
UPD
Here’s the complete protocol declaration:
protocol JSONDataInitializable: Decodable {
init?(from jsonData: Data)
}
extension JSONDataInitializable {
init?(from jsonData: Data) {
do {
self = try Self.initialize(from: jsonData)
} catch {
print(error)
return nil
}
}
// (1)
private static func initialize<T: JSONDataInitializable>(from jsonData: Data) throws -> T {
return try JSONDecoder().decode(T.self, from: jsonData)
}
// ⬆⬆⬆
// OR
// ⬇⬇⬇
// (2)
private static func initialize(from jsonData: Data) throws -> Self {
return try JSONDecoder().decode(Self.self, from: jsonData)
}
}
Let’s say we have a struct called User that’s Decodable. We need to initialize values of User from JSON (stored as Data). With the protocol, initialization works like that:
// Protocol conformance declaration
extension User: JSONDataInitializable { }
// JSON stored as Data
let networkData = ...
// Initialization
let john = User(from: networkData)

Your second implementation, using Self is the one that matches your requirements. You want to create an initializer in a protocol, which you can call on the type you want to initialize. Self in protocol functions refers to the type that you call the specific method on.
On the other hand, the generic implementation would allow you to initialize any types conforming to the protocol, but in your init(from:) method you assign the return value to self, so the generic type parameter T will be inferred as Self. This makes it unnecessary to make the method generic, since on a specific type, T will always be Self.

Related

Use `self =` in convenience initializers to delegate to JSONDecoder or factory methods in Swift to avoid `Cannot assign to value: 'self' is immutable`

Sometimes in Swift, it may be convenient to write an initializer for a class which delegates to JSONDecoder or a factory method. For example, one might want to write
final class Test: Codable {
let foo: Int
init(foo: Int) {
self.foo = foo
}
func jsonData() throws -> Data {
try JSONEncoder().encode(self)
}
convenience init(fromJSON data: Data) throws {
self = try JSONDecoder().decode(Self.self, from: data)
}
}
let test = Test(foo: 42)
let data = try test.jsonData()
let decodedTest = try Test(fromJSON: data)
print(decodedTest.foo)
but this fails to compile with
Cannot assign to value: 'self' is immutable.
What is the solution to work around this problem?
First, note that this limitation exists only for classes, so the example initializer will work for as-is for structs and enums, but not all situations allow changing a class to one of these types.
This limitation on class initializers is a frequent pain-point that shows up often on this site (for example). There is a thread on the Swift forums discussing this issue, and work has started to add the necessary language features to make the example code above compile, but this is not complete as of Swift 5.4.
From the thread:
Swift's own standard library and Foundation overlay hack around this missing functionality by making classes conform to dummy protocols and using protocol extension initializers where necessary to implement this functionality.
Using this idea to fix the example code yields
final class Test: Codable {
let foo: Int
init(foo: Int) {
self.foo = foo
}
func jsonData() throws -> Data {
try JSONEncoder().encode(self)
}
}
protocol TestProtocol: Decodable {}
extension Test: TestProtocol {}
extension TestProtocol {
init(fromJSON data: Data) throws {
self = try JSONDecoder().decode(Self.self, from: data)
}
}
let test = Test(foo: 42)
let data = try test.jsonData()
let decodedTest = try Test(fromJSON: data)
print(decodedTest.foo)
which works fine. If Test is the only type conforming to TestProtocol, then only Test will get this initializer.
An alternative is to simply extend Decodable or another protocol to which your class conforms, but this may be undesirable if you do not want other types conforming to that protocol to also get your initializer.

JSONDecodable and required init

I've created a network manager that gets a JSON response and converts it in to a Swift object,
protocol HTTPResponse: Codable {
}
class UserResponse: HTTPResponse {
var name: String?
}
let responseObj = try? JSONDecoder().decode(T.self, from: response.data!)
where T is an HTTPResponse decendent
I need to be able to create an 'empty' UserResponse object, an instance where all the class variables are nil
I changed HTTPResponse from protocol to class and added an empty required init() {} to it.
This allows me to invoke T() and get an instance of UserResponse but now JSONDecoder is no longer parsing the JSON, it also creates instances where all variables are nil
How can I achieve the end result where JSONDecoder works well, and I can create stub instances of my HTTPResponse decedent classes?
I'm working with swift 4.2
You can add the initialiser requirement to the protocol:
protocol HTTPResponse: Codable {
init()
}
Then, add a required init() { ... } to UserResponse and all the conformers. Each conformer can decide what a "stub" response means for them.
After that T() will work, given that T is a generic parameter with the constraint <T: HTTPResponse>. This might seem strange as you are initialising a protocol. But actually, since protocols don't conform to themselves, T will never be HTTPResponse, and will always be a concrete class that conforms to HTTPResponse.

Why doesn't this behave the way I expect (hope) it would?

I have a several protocols set up in my framework to deal with resources. In one of the protocols, I have set up an extension to provide a default implementation for a decode function. It's simpler to show the code and what happens (see calls to fatalError). There's a lot more code in the actual implementation, but this illustrates the issue:
This is the "base" protocol:
public protocol Resourceful {
associatedtype AssociatedResource
typealias ResourceCompletionHandler = (AssociatedResource?, Error?) -> Void
func fetch(_ completion: #escaping ResourceCompletionHandler)
}
This is a generic, concrete implementaion of Resourceful:
open class WebResourceApiCall<Resource>: Resourceful {
public typealias AssociatedResource = Resource
public typealias FetchedResponse = (data: Data?, urlResponse: URLResponse?)
public init() {
}
public func fetch(_ completion: #escaping ResourceCompletionHandler) {
try! decode(fetched: (data: nil, urlResponse: nil))
}
public func decode(fetched: FetchedResponse) throws -> Resource {
fatalError("It ends up here, but I don't want it to!")
}
}
extension WebResourceApiCall where Resource: Decodable {
public func decode(fetched: FetchedResponse) throws -> Resource {
fatalError("This is where I want it to go...")
}
}
This is how I'm attempting to use it:
public struct Something: Decodable { }
var apiCall = WebResourceApiCall<Something>()
apiCall.fetch { _, _ in } // Implictly calls decode... but not the decode I expected it to! See fatalError() calls...
Instead of calling decode on the extension, like I hoped it would, the "default" decode method with no constraints is always called.
Why doesn't this work the way I expect it to?
Thanks in advance!
Swift is a statically dispatched language, thus the address of the decode() function to be called is computed at compile time, and because the call happens inside the base definition of the class, the compiler picks the original implementation.
Now, if you call the method from a place where the compiler has enough information to pick the implementation you need, it will work:
var apiCall = WebResourceApiCall<Something>()
try apiCall.decode(fetched: (nil, nil))
The above code will call the method from the specialized extension, as at this point the compiler is a better position to know that it has a more specialized implementation to call.
It should be possible to achieve the behaviour you need if you move the decode() method in the dynamic dispatch world - i.e. at the protocol level.

Delegating to another initializer from a closure

I'm looking at ways to create a DispatchData instance out of a Data instance. I started with a static function:
static func dispatchData(fromData data: Data) -> DispatchData {
return data.withUnsafeBytes { (typedPtr: UnsafePointer<UInt8>) -> DispatchData in
let bufferPtr = UnsafeRawBufferPointer(start: UnsafeRawPointer(typedPtr), count: data.count)
return DispatchData(bytes: bufferPtr)
}
}
This works fine (and is coincidentally very similar to this answer). I then decided I'd like to add this as an initializer to DispatchData in an extension and wrote:
private extension DispatchData {
init(data: Data) {
data.withUnsafeBytes { (typedPtr: UnsafePointer<UInt8>) in // 1
let bufferPtr = UnsafeRawBufferPointer(start: UnsafeRawPointer(typedPtr), count: data.count)
self.init(bytes: bufferPtr)
}
}
}
At this, the compiler balked on the line marked // 1:
Variable 'self.__wrapped' captured by a closure before being initialized
It makes sense — the compiler doesn't want self to be captured before I've delegated to init(bytes: UnsafeRawBufferPointer), since stored variables — like __wrapped, in this case — aren't yet initialized. But I can't see another way to get the UnsafeRawBufferPointer; I can't return it from data.withUnsafeBytes, per the documentation:
Warning
The byte pointer argument should not be stored and used outside of the lifetime of the call to the closure.
I know I can just go back to using a static function to achieve what I want, but I'd prefer it if there were a way to add this initializer. Is there a way to make this initializer work as intended?
The problem with your extension is that although the compiler knows you're passing the closure as non-escaping argument to withUnsafeBytes(_:); it doesn't have any guarantee that it will be called once and only once (which is required for initialisation).
One simple solution is to, rather than chain to another initialiser, just construct a new instance of DispatchData yourself, and then assign this to self, thus completing initialisation:
import Foundation
private extension DispatchData {
init(data: Data) {
self = data.withUnsafeBytes { (typedPtr: UnsafePointer<UInt8>) in
DispatchData(bytes:
UnsafeRawBufferPointer(start: typedPtr, count: data.count)
)
}
}
}
Note also that there's an implicit conversion from UnsafePointer<UInt8> to UnsafeRawPointer? when passing it as an argument; so we don't need to wrap it in an initialiser call.
And in Swift 5, withUnsafeBytes gives you a UnsafeRawBufferPointer directly:
private extension DispatchData {
init(data: Data) {
self = data.withUnsafeBytes(DispatchData.init)
}
}

Obtaining a string representation of a protocol dynamically

I'm looking for a way to obtain a protocol name dynamically from the protocol type, without having to use the #objc attribute in the protocol declaration.
I know that this works:
func keyForProtocol(aProtocol: Protocol) -> String {
return NSStringFromProtocol(aProtocol)
}
but only if the protocol has the #obj attribute:
#objc protocol Test {}
var key = keyForProtocol(Test.self) // Key contains "TestApp.Test"
however as soon as I remove the #objc attribute, compilation fails with this error:
'Test.Protocol' is not convertible to 'Protocol'
Is there any way to achieve that?
Note: the reason why I want to avoid #objc is that it doesn't allow usage of associated types (i.e. generics in protocol) - in those cases compilation fails with this error: Method cannot be marked #objc because the type of parameter xx cannot be represented in Objective-C
I recently found a solution by implementing the keyForProtocol method as follows:
func keyForProtocol<P>(aProtocol: P.Type) -> String {
return ("\(aProtocol)")
}
It works with any type, not just for protocols, but I'm ok with that.
Some examples from a playground:
protocol Test {}
keyForProtocol(Test.self) // Prints "__lldb_expr_92.Test"
class MyClass {}
keyForProtocol(MyClass.self) // Prints "__lldb_expr_92.MyClass"
keyForProtocol(Int.self) // Prints "Swift.Int"
keyForProtocol(UIView.self) // Prints "UIView"
As Antonio said:
let protocolDescription = "\(aProtocol)"
will return a (sometime strange) protocol name.
I finally completed my task converting back this string to a protocol with:
let protocolReference = NSProtocolFromString(protocolDescription)
This technique is perfect if you need to retrieve the protocol type from a generic parameter like in this example (a lightweight/homemade dependency manager):
class DependencyManager<T> {
private static func inject(_ aProtocol: T.Type) -> T {
let application = UIApplication.shared.delegate as! LMApplication
let dependencies = application.applicationDependencies
let protocolReference = NSProtocolFromString("\(aProtocol)")
let dependency = dependencies!.object(for: protocolReference)
return dependency! as! T
}
}
Usage:
let object: Protocol = DependencyManager.inject(Protocol.self)