I have a problem creating a convenience init method that then calls a designated init on a class with generic type parameters. Here is the swift 3.1 XCode Version 8.3.2 (8E2002) playground
protocol A {
var items: [String] { get set }
func doSomething()
}
struct Section : A {
var items: [String] = []
func doSomething() {
print("doSomething")
items.forEach { print($0) }
}
}
class DataSource<T: A> {
var sections: [T]
init(sections: [T]) {
self.sections = sections
}
func process() {
sections.forEach { $0.doSomething() }
}
convenience init() {
var section = Section()
section.items.append("Goodbye")
section.items.append("Swift")
self.init(sections: [section])
}
}
/*: Client */
var section = Section()
section.items.append("Hello")
section.items.append("Swift")
let ds = DataSource(sections: [section])
ds.process()
If no convenience init exists, then the code beneath the /*: Client */ section compiles and executes without issue. If I add in the convenience init I get the following compilation error:
cannot convert value of type '[Section]' to expected argument type '[_]'
self.init(sections: [section])
I wouldn't think that this would be an issue since in the convenience init I am creating a Section struct which implements the protocol A which satisfies the generic constraint on the DataSource class. The convenience init is performing the same operations as the client code, yet it is unable to convert a [Section] into a [A]. Is this an initialization sequencing issue?
Generic placeholders are satisfied at the usage of the given generic type – therefore inside your convenience init, you cannot assume that T is a Section. It's an arbitrary concrete type that conforms to A.
For example, it would be perfectly legal for the caller to define a
struct SomeOtherSection : A {...}
and then call your convenience initialiser with T being SomeOtherSection.
The solution in this case is simple, you can just add your convenience initialiser in an extension of DataSource, where T is constrained to being Section – therefore allowing you to call init(sections:) with a [Section]:
extension DataSource where T == Section {
convenience init() {
var section = Section()
section.items.append("Goodbye")
section.items.append("Swift")
self.init(sections: [section])
}
}
// ...
// compiler will infer that T == Section here.
let ds = DataSource()
Related
I'm trying to get a container that implements a set of protocols that i pass as parameter to a function on the original container.
struct Container {
let someProperty: String
let otherProperty: String
}
// Subcontainers
protocol Base {}
protocol SomePropertyContainer: Base {
var someProperty: String { get }
}
protocol OtherPropertyContainer: Base {
var otherProperty: String { get }
}
extension Container: SomePropertyContainer, OtherPropertyContainer {}
// Sub Container Provisioning Protocol
protocol SubContainerProviderProtocol {
func subContainer<T: Base>(protos: T.Type) -> T?
}
extension Container: SubContainerProviderProtocol {
func subContainer <T: Base>(protos: T.Type) -> T? {
return self as? T
}
}
// Example container
let subContainerProvider: SubContainerProviderProtocol = Container(someProperty: "Why does this not work!", otherProperty: "Seriously.")
Getting this up and running would allow me to inject the ContainerProviderProtocol into consumers while giving them the possibility to specify themselves which SubContainer they actually want.
E.g. a class that would be interested in only the someProperty could look like this
// Example Container Provider consumer
class SomeClass {
let subContainerProvider: SubContainerProviderProtocol
init(subContainerProvider: SubContainerProviderProtocol) {
self.subContainerProvider = subContainerProvider
}
func printSomeProperty() {
let someProperty = subContainerProvider
.subContainer(protos: SomePropertyContainer.self)?
.someProperty
print(someProperty)
}
}
// Example call
let someClass = SomeClass(subContainerProvider: subContainerProvider)
someClass.printSomeProperty() // "Why does this not work!"
This solution would be incredible for dependency injection & testability.
However the restriction T: Base is causing the compiler error
In argument type 'SomePropertyContainer.Protocol', 'SomePropertyContainer' does not conform to expected type 'Base'
Not specifying conformance to Base will compile, but would also allow to pass any type as T.
I've tried with associated types within an additional protocol etc, however have not figured it out. And while this issue is incredibly fun, I'm running out of ideas.
Possibly related to (but not exactly same) https://bugs.swift.org/browse/SR-55
Here's the problem: at some point you have to start working with actual types, and not just protocols. Your line:
func container<T: Base>(protos: T.Type) -> T?
is telling the compiler that you're going to give this function a type, generically T, that conforms to the protocol Base, not another protocol. You need something like this:
class SPC: SomePropertyContainer {
var someProperty: String = ""
}
class SomeClass {
let containerProvider: ContainerProviderProtocol
init(containerProvider: ContainerProviderProtocol) {
self.containerProvider = containerProvider
}
func printSomeProperty() {
let someProperty = containerProvider
.container(protos: SPC.self)?
.someProperty
print(someProperty)
}
}
SPC is a type that conforms to the SomePropertyContainer protocol, which itself conforms to the Base protocol, so this is what your code is expecting.
I have to modify an existing static method with return type Self.
I am using Self as it is required to work for subclasses of A as well. As the modification potentially needs a dispatch sync block to create the data for the returnee, I have to introduce a local variable of type Self.
Error:
'Self' is only available in a protocol or as the result of a method in a class;
class A {
//...
}
class B:A {
//...
}
extension A {
static private func foo() -> Self {
var myVar: Self? //Error: 'Self' is only available in a protocol or as the result of a method in a class;
// Get data for myVar, potentially in a dispatch sync block on another queue
guard let safeVar = myVar else {
return someGarbagr
}
return myVar
}
}
Intended usage:
func someFunctionSomewhere() {
let instanceOfB = B.foo()
// use instanceOfB
}
I have tried all I can think of already:
type(of:Self)
Self.Type
...
I would like to avoid modifying it to a generic method for several reasons. The main reason is that we would have to mention the type explicitly to make a generic version be able to refer the return type:
let instanceOfB: B = B.foo()
I have some swift structs for which protocol compliance is generated with individual extensions with equal methods names which just differ in their return types which are struct dependent. On top of That I want to use them in a generic function which Calls a protocol conforming function for a generic type).
I tried to accomplish this like that:
//: Playground - noun: a place where people can play
import UIKit
protocol FooProt {
typealias T;
static func createMe<T>()->T;
}
struct FooStruct{
}
extension FooStruct: FooProt{
typealias T = FooStruct;
static func createMe () -> FooStruct{
return FooStruct();
}
}
class Creator{
fun createOne<T where T:FooProt>(type:T.Type){
let instance = T.createMe();
}
}
Unfortunately I get the following error :
/var/folders/sn/78_zvfd15d74dzn01mdv258h0000gq/T/./lldb/3741/playground6.swift:7 :17: note: protocol requires function 'createMe()' with type ' () -> T' (aka '<τ_1_0> () -> τ_1_0')
static func createMe()->T;
What exactly doesn't comply here and is there a workaround ?
There are several problems with your code. On the one hand you have defined a protocol with an associated type. However, you define your createMe() method as a generic which uses some other type. I don't think that was your intent. I think your intent was to have a createMe() method that returns the same type as the protocol's associated type. In this case you need to remove the from the createMe() method. Also, the name createMe() implies that you aren't just returning any type, but the type of the object on which this method is being called. In this case, you don't even need an associated type protocol. You just need a protocol with a Self constraint which allows your code to be a bit simpler. In your Creator's createOne method, your type constraint is more complex than needed.
I think you want the following code:
protocol FooProt {
static func createMe()->Self;
}
struct FooStruct{
}
extension FooStruct: FooProt {
static func createMe() -> FooStruct {
return FooStruct();
}
}
class Creator{
func createOne<T:FooProt>(type: T.Type) -> T {
return T.createMe()
}
}
let foo = Creator().createOne(FooStruct.self)
Here is an alternate solution using an initializer in the protocol instead of a static method.
protocol FooProt {
init()
}
struct FooStruct{
}
extension FooStruct: FooProt {
}
class Creator{
func createOne<T:FooProt>(type: T.Type) -> T {
return T.init()
}
}
let foo = Creator().createOne(FooStruct.self)
The following code produces a compile error of "Generic parameter "T" cannot be bound to non-#objc protocol type 'AAA' on the fail line. When I use a class instead of a protocol, it works ok. Also, if I add an #objc to the protocol it also works, but only in 6.4 beta. Any suggestions would be helpful.
protocol AAA {
var id: String { get set }
}
class BBB: AAA {
var id: String = ""
}
class BBBService {
func getAll<T:AAA>() -> [T] {
var returnArray:[T] = [T]()
return returnArray
}
}
class TestIt
{
func myTest() {
var service = BBBService()
var fail:[AAA] = service.getAll() // fails with Generic parameter "T" cannot be bound to non-#objc protocol type AAA
var succeed:[BBB] = service.getAll()
}
}
this also fails:
<T where T:AAA>
Update - from a practical perspective, adding the #objc causes other problems in my app. So, that is not an option at this point and time.
The trouble is with this line:
getAll<T: AAA>() -> [T]
you are declaring that T must be a concrete type that implements the protocol AAA. It’s important to distinguish between the protocol AAA, i.e. code like this:
func getAll() -> [AAA] {
var returnArray: [AAA] = [BBB()]
return returnArray
}
which works fine (returns an array of references to AAA-conforming objects, which could be of type BBB, or type CCC), and this:
func getAll<T: AAA>() -> [T] {
var returnArray: [T] = [] // what is T? No idea.
return returnArray
}
in which you are saying to the compiler “write me a version of getAll, in which T can be replaced by any specific type that implements AAA”.
This is why your second version compiles - you’re fixing T to be the actual type BBB.
Bear in mind, T might be a struct. In which case the array returned must be sized specifically for whatever struct is being held, right there as a value within the array. As opposed to if the protocol was #objc in which case it would at least be known to only be a class reference of a fixed size.
If what you actually want is an array of protocols, you should remove the generic placeholder and just return an array of protocols:
func getAll() -> [AAA] {
var returnArray: [AAA] = []
return returnArray
}
I defined a protocol LLNodeType:
protocol LLNodeType {
typealias T
var firstNode: LLNode<T>? { get }
}
LLNode<T> is just a simple generic class, which contains a stored property of type N.
class LLNode<N> {
var content: N
...
}
To conform to the LLNodeType protocol I therefore extended LLNode as follows:
extension LLNode: LLNodeType {
typealias T = N
var firstNode: LLNode<T>? {
return self
}
}
I also defined a generic class LLLinkedList containing a few properties and functions using the generic type L:
class LLLinkedList<L> {
var rootNode: LLNode<L>?
...
}
I extended this class to conform to LLNodeType:
extension LLLinkedList: LLNodeType {
typealias T = L
var firstNode: LLNode<T>? {
return self.rootNode
}
}
I found a way of passing LLNodeType to methods of LLLinkedList as a regular type and used it on the append method:
func append<NT: LLNodeType>(nodeSequence: NT) {
let nodes = LLLinkedList(nodeSequence: nodeSequence)
...
}
As seen in the first statement of the append method, I have also defined an initializer for LLLinkedList, that takes a parameter nodeSequence of type LLNodeType:
convenience init<NT: LLNodeType where NT.T == L>(nodeSequence: NT) {
self.init()
self.rootNode = nodeSequence.firstNode
}
The initializer only takes a nodeSequence which conforms to LLNodeType, which is constrained though to use a type T equal to L.
The firstNode property of a nodeSequence conforming to these conditions should therefore return an LLNode<L>?.
Therefore the statement self.rootNode = nodeSequence.firstNode should be completely possible, since self.rootNode is of type LLNode<L>?.
When I try to compile the code, I get the error:
<stdin>:501:33: error: extra argument 'nodeSequence' in call
let nodes = LLLinkedList(nodeSequence: nodeSequence)
501:33 refers to the first statement of the append method.
How can there be an extra argument 'nodeSequence' if I defined the initializer with an argument called nodeSequence?
Here's a code sample containing only the parts relevant to the question:
SwiftStub-Code
The problem is that your append function is not mandating the type of LLNodeType.T for the sequence you're appending:
func append<NT: LLNodeType>(nodeSequence: NT) {
// here, nodeSequence could be of a String, an Int, who knows...
let nodes = LLLinkedList(nodeSequence: nodeSequence)
...
// but here you try to append it to the existing list...
// what if it's not a list of the same type?
if self.isEmpty {
self.rootNode = nodes.rootNode
} else {
/// appends a node by pointing the last nodes pointer ('nextNode' property) to the new node
self.lastNode!.nextNode = nodes.rootNode
}
}
You can resolve this by mandating you only append sequences of the same type as your
func append<NT: LLNodeType where NT.T == L>(nodeSequence: NT) {
// etc...