Swift: Return class constrained with generic type - swift

I have a basic generic class:
class SharedClass<T> {}
And a builder for it:
class SharedClassBuilder {
func build<T>() -> SharedClass<T>? {
return ...
}
}
What I want to be able to do, is return an instance that inherits SharedClass, and conforms to T. For example:
protocol MyObject {
func doIt()
}
var result: SharedClass<MyObject>? = SharedClassBuilder().build()
result?.doIt()
Unfortunately, the Swift compiler complains that the returned type does not have a member named doIt.
Is there a way to achieve what I'm looking for?

I suspect it's not so much that you want the returned class to be constrained by the generic type, as you're asking the returned class to be an instance of the constrained type. In your snippet, you're expecting the unwrapped result to conform to MyObject. Taking this a step further, it means that the conformance of SharedClass is determined entirely from how it was constructed. As far as I know this isn't supported in Swift.
However, there's nothing stopping you having a member of SharedClass that is a T. Something along the lines of:
class SharedClass<T> {
var v : T?
}
class SharedClassBuilder {
func build<T>() -> SharedClass<T>? {
return SharedClass()
}
}
protocol MyObject {
func doIt()
}
var result: SharedClass<MyObject>? = SharedClassBuilder().build()
result?.v?.doIt()

Related

Implement generic protocol method with but use generic for whole class

I'm am trying to implement a protocol method that has a generic argument, but then use the generic type for my entire class instead of just on the method, something like this
protocol FirstProtocol {
}
protocol SecondProtocol {
func foo<T: FirstProtocol>(argument: T)
}
class MyType<T: FirstProtocol>: SecondProtocol {
var value: T? = nil
func foo<T>(argument: T) {
value = argument // ERROR: Cannot assign value of type 'T' to type 'T?'
}
}
So the swift compiler accepts that foo<T>(argument:T) matches the method of SecondProtocol, if I comment out the error line it compiles fine, but it will not let me assign argument to value even though value and argument should be the same type, the compiler complains as if they are different types.
The type of argument and value are indeed different types. The T generic parameter in foo is just an identifier, and I can change it to anything else:
class MyType<T: FirstProtocol>: SecondProtocol {
var value: T? = nil
func foo<AnythingElse>(argument: AnythingElse) {
// MyType still conforms to SecondProtocol
}
}
The T in foo is a brand new generic parameter, different from the T in MyType. They just so happens to have the same name.
Note that when you declare a generic method, it's the caller that decides what the generic type is, not the generic method. What foo is trying to say here is "I want the T in foo to be the same type as the T in MyType", but it can't say that about its own generic parameters!
One way to fix it is to make SecondProtocol have an associated type:
protocol SecondProtocol {
// name this properly!
associatedtype SomeType: FirstProtocol
func foo(argument: SomeType)
}
class MyType<T: FirstProtocol>: SecondProtocol {
typealias SomeType = T // here is where it says "I want 'SomeType' to be the same type as 'T'!"
var value: T? = nil
func foo(argument: T) {
value = argument
}
}
it will not let me assign argument to value even though value and argument should be the same type, the compiler complains as if they are different types.
think about this case:
class A: FirstProtocol {
}
class B: FirstProtocol {
}
class A and B is the acceptable generic type for func foo(argument: T){}, but can you assign an instance of class A to class B?
class MyType<T: FirstProtocol>: SecondProtocol
remove ": FirstProtocol"should work, or use a base class to replace FirstProtocol

Why do I need to force cast a property to a generic method with the same signature as the property?

This is my code:
class GenericClass<T: UITableViewCell> {
let enumProperty = SomeEnum.myValue
enum SomeEnum {
case myValue
}
func callOtherClass() {
OtherClass.handle(property: enumProperty) // Compile error
}
}
class OtherClass {
static func handle(property: GenericClass<UITableViewCell>.SomeEnum) {}
}
Why do I get the compile error:
Cannot convert value of type 'GenericClass.SomeEnum' to expected
argument type 'GenericClass.SomeEnum'
Ofcourse, the fix would be adding the cast:
as! GenericClass<UITableViewCell>.SomeEnum
which results in this ugly code:
func callOtherClass() {
OtherClass.handle(property: enumProperty) as! GenericClass<UITableViewCell>.SomeEnum
}
But why do I need to cast? self is defined as GenericClass where T always is a UITableViewCell. The method handle expects that signature.
Is there any case this cast is needed because in some situations this will/can fail? I would not expect Swift just randomly asking me to insert a force cast. I expect Swift can just infer the types and sees it is safe, but somehow, Swift doesn't agree with me.
The problem here is that SomeEnum is actually GenericClass<T>.SomeEnum. There's no promise that T is exactly UITableViewCell, so it's not compatible with GenericClass<UITableViewCell> (generics are not covariant).
Typically in this case, what you want to do is move SomeEnum outside of GenericClass, since nothing about it is actually generic:
enum SomeEnum {
case myValue
}
class GenericClass<T: UITableViewCell> {
let enumProperty = SomeEnum.myValue
func callOtherClass() {
OtherClass.handle(property: enumProperty) // Compile error
}
}
class OtherClass {
static func handle(property: SomeEnum) {}
}
But if there's a reason for it to be generic, see Robert Dresler's answer, which is how you would specialize the function correctly:
class OtherClass {
static func handle<T: UITableViewCell>(property: GenericClass<T>.SomeEnum) {}
}
Make your static method also generic and create generic constrain for parameter inheriting from UITableViewCell. Then use this generic parameter in method parameter
class OtherClass {
static func handle<T: UITableViewCell>(property: GenericClass<T>.SomeEnum) {}
}

Swift Implicitly pass self as inout parameter for reference type

Is it possible to implicitly pass self as an inout parameter to modify a reference variable in place?
Here is a method which can convert an abstract base class into one of its concrete subclasses. My question is, must I always have that first argument, obj: inout AbstractBaseClass, or can I implicitly pass self. I realize that this might also be expressed as a static method.
func convertTo(_ obj: inout AbstractBaseClass, _ type: ConcreteClassTypes) {
switch type {
case .concreteClass1: obj = ConreteClass1()
case .concreteClass2: obj = ConcreteClass2()
}
}
Here is the full code:
class AbstractClass {
enum ConcreteType {
case concreteClass1
case concreteClass2
}
var id: Int = 0
fileprivate init() { }
func convert(_ obj: inout AbstractClass, to type: ConcreteType) {
let oldId = obj.id
switch type {
case .concreteClass1: obj = ConcreteClass1()
case .concreteClass2: obj = ConcreteClass2()
}
obj.id = oldId
}
class ConcreteClass1: AbstractClass {
override init() { super.init() }
}
class ConcreteClass2: AbstractClass {
override init() { super.init() }
}
}
var obj: AbstractClass = AbstractClass.ConcreteClass1()
obj.convert(&obj, to: .concreteClass2) //is there any way to eliminate this first argument?
Like matt, I'm not convinced that inout is the right tool for the job in this case.
Although that being said, if you insist on it, one way to achieve what you want is to (ab)use protocol extensions. They allow the definition of mutating methods, which pass the implicit self parameter as inout (to allow the mutation of adopting value types).
So you could say:
protocol AbstractClassProtocol {}
class AbstractClass : AbstractClassProtocol {
enum ConcreteType {
case concreteClass1
case concreteClass2
}
fileprivate init() {}
class ConcreteClass1: AbstractClass {
override init() { super.init() }
}
class ConcreteClass2: AbstractClass {
override init() { super.init() }
}
}
extension AbstractClassProtocol where Self == AbstractClass {
mutating func convert(to type: AbstractClass.ConcreteType) {
switch type {
case .concreteClass1:
self = AbstractClass.ConcreteClass1()
case .concreteClass2:
self = AbstractClass.ConcreteClass2()
}
}
}
var obj: AbstractClass = AbstractClass.ConcreteClass1()
obj.convert(to: .concreteClass2)
print(obj) // AbstractClass.ConcreteClass2
But it's a bit of a hack, and I'd be wary about using it.
...to modify a reference variable in place? Here is a method which can convert an abstract base class into one of its concrete subclasses...
You are not "modifying" or "converting" anything. You are substituting one object for another. Thus, there is no self that could be passed here; the idea of what you are doing is to destroy one self and provide another in its place.
That said, it's a little unclear what the inout variable is for. Why don't you just assign the new object in place of the old object?
func giveMeA( _ type: AbstractClass.ConcreteType) -> AbstractClass {
switch type {
case .concreteClass1: return AbstractClass.ConcreteClass1()
case .concreteClass2: return AbstractClass.ConcreteClass2()
}
}
var obj: AbstractClass = AbstractClass.ConcreteClass1()
obj = giveMeA(.concreteClass2)
The effect is identical to what you're doing. If you think it's not, you're just kidding yourself about what the inout parameter is doing.
I'm going to propose a completely different way of looking at what you're trying to do.
Don't have an abstract superclass. Don't have multiple subclasses. Have one class with multiple functional variants. The functional variants are expressed by a helper object — a struct — owned by the class instance.
So to change functionalities, you just set the helper to a different type of helper. Basically, you give your object a personality transplant.
I have an app that works that way. I have four view controllers that present slightly different info in slightly different ways. But in fact they are one view controller and an enum with four cases that dictates those differences. Thus, at any time the view controller can manifest itself as any of the four types.

Returning a generic from a protocol-defined function in Swift

I declared a protocol with a generic function, but it seems that the type inference isn't working properly after implementing it.
protocol SearchableRealmModel {
static func search<Self: Object>(needle: String) -> Results<Self>?
}
class Thing: Object, SearchableRealmModel {
class func search<Thing>(needle: String) -> Results<Thing>? {
return realm()?.objects(Thing).filter("name == '\(needle)'")
}
}
let things = Thing.search("hello") // works but inferred type is Results<Object>?
The problem here is that the inferred type of things is Results<Object>?. I realize these variations can be used,
let things: Results<Thing>? = Thing.search("hello")
let things = Thing.search("hello") as Results<Thing>?
but having to specify the type every time is quite repetitive.
In my tests, using other types than Results<..>? kept the type inference intact. And this could be caused by having to specify the parent class in Self: Object (which is required because of Results).
Any help is appreciated.
This is a limitation of Swift's generics machinery. The compiler can generate a concrete signature for static func search(needle: String) -> Results<Object>? which satisfies the type constraint because Object subclasses will match this. You could probably file a bug towards bugs.swift.org because I think the Swift core team would also consider this to be a bug, if not very unexpected behavior.
However, you can modify your code to use protocol extensions to do what you want:
protocol SearchableRealmModel {}
extension SearchableRealmModel where Self: Object {
static func search(needle: String) -> Results<Self> {
return try! Realm().objects(Self).filter("name == '\(needle)'")
}
}
class Thing: Object, SearchableRealmModel {
dynamic var name = ""
}
let result = Thing.search("thing1") // => inferred as Results<Thing>
print(result.first?.name)
If you want custom implementations of search for other Realm models, you can reimplement the function there, which the compiler will prioritize over the protocol extension version:
class OtherThing: Object, SearchableRealmModel {
dynamic var id = ""
static func search(needle: String) -> Results<OtherThing> {
return try! Realm().objects(OtherThing).filter("id == '\(needle)'")
}
}

Swift: Generics and type constraints, strange behavior

I can’t figure out how to perform a cast which would let me eventually to introduce some sort of dynamism working with generics.
Next piece of code does not compile. It shows this error:
Cannot invoke 'createContainer' with an argument list of type
'(FooProtocol)' Expected an argument list of type '(T)'
protocol FooProtocol {
func doSomething()
}
class Foo : FooProtocol {
func doSomething() {}
}
class Container<T : FooProtocol> {
let someDataConformingFooProtocol : T
init(someDataConformingFooProtocol : T) {
self.someDataConformingFooProtocol = someDataConformingFooProtocol
}
}
class AllTogether {
init () {
createContainer(Foo()) //So far, so good
let foo2Mask : AnyObject = Foo()
if let foo2MaskChecked = foo2Mask as? FooProtocol {
createContainer(foo2MaskChecked)
//ERROR: Cannot invoke 'createContainer' with an argument list of type '(FooProtocol)'
//Expected an argument list of type '(T)'
}
}
func createContainer<T : FooProtocol>(data: T){
Container<T>(someDataConformingFooProtocol: data)
}
}
Is this really the expected behaviour? Because personally I can’t understand what or why the compiler is complaining about it.
What would be the appropriate cast? Without referencing to the concrete class, I mean NOT like this:
if let foo2MaskChecked = foo2Mask as? Foo
Thanks!
What it comes down to is the difference between T: FooProtocol and FooProtocol as #Hamish mentioned in his comment.
When I have a function like this:
func foo(_ i: FooProtocol)
I'm taking in an instance of type FooProtocol, so I can pass in a value that conforms to FooProtocol or its type is FooProtocol. This is similar to subclassing where you can pass both SuperClass and SubClass into SuperClass.
But if you have a function like this:
func foo<T>(_ i: T) where T: FooProtocol
I can pass in any type that conforms to FooProtocol, but because FootProtocol does not conform to itself, you can't pass that in.
So in your question, you are trying to pass in a type FooProtocol where the types must conform to FooProtocol. You could probably fix this by doing:
class Container {
let someDataConformingFooProtocol: FooProtocol
init(someDataConformingFooProtocol: FooProtocol) {
self.someDataConformingFooProtocol = someDataConformingFooProtocol
}
}
// Later
func createContainer(data: FooProtocol){
Container(someDataConformingFooProtocol: data)
}