6

I would like to use Self in init parameters like so:

class A {
    public init(finishBlock: ((_ operation: Self) -> Void)? = nil) {...}
}

I know I could use "A" in this place, but I would like to achieve that if some class inherits from A, then it's initializer would know operation as it's class type and not as just A. So for example if I wrote:

class B: A {
    public init(finishBlock: ((_ operation: Self) -> Void)? = nil) {...}
    public func fooOnlyInB() {}
}

I could then use:

let b = B { (operation) in
    operation.fooOnlyInB()
}

Is this somehow possible?

Damian Dudycz
  • 2,622
  • 19
  • 38
  • Interesting! I wanted to see whether this could work by making a protocol with a `Self` requirement in `init`, but I came across the error mentioned in [this similar question](http://stackoverflow.com/q/32999293/3769927). – kabiroberai Oct 15 '16 at 07:11

3 Answers3

2

Instead of using Self or A in each of the initialisers, you can simply override each subclass' initialiser to use its own type as operation.

This works because A's initialiser states that operation should be a type that conforms to A, and when you override it you have the liberty to use a subclass of A as operation instead. However, if you change operation to an unrelated type such as String or Int, the compiler will not override the existing initialiser.

Firstly, define A with its init:

class A {
    init(finishBlock: ((_ operation: A) -> Void)?) {...}
}

Now to create a subclass, you must override init using the subclass' type as operation instead. In your call to super.init, force upcast operation ($0) to your subclass' type, and call finishBlock with this casted operation.

class B: A {
    override init(finishBlock: ((_ operation: B) -> Void)?) {
        // Perform custom initialisation...
        super.init { finishBlock?($0 as! B) }
    }

    func fooOnlyInB() {
        print("foo")
    }
}

B's initialiser now passes B as operation, which means that you don't need to cast it yourself anymore! This is thanks to the fact that you can override an init with a more specific type, in this case B.

let b = B { operation in
    operation.fooOnlyInB() // prints "foo"
}
kabiroberai
  • 2,930
  • 19
  • 34
  • You are right, this is some solution, although not perfect. I don't like the fact that I need to explicitly override init to have this working, and also that instead of just passing finishBlock as an argument I need to create another block that executes finishBlock from subclass. But still this is some solution which is better then nothing. I will definitely use it, but it's a shame it is required to do such a workaround. Thank you for your answer. – Damian Dudycz Oct 15 '16 at 17:39
2

As others have already said, you cannot do this directly, as Self is currently only available in protocols or as the return type of a method in a class.

However, you can workaround this by using a protocol extension in order to declare the initialiser:

protocol AProtocol : A {}
// Pre Swift 5:
// protocol AProtocol {
//   init()
// }

extension AProtocol {
  init(completion: ((Self) -> Void)? = nil) {
    self.init()
    completion?(self)
  }
}

class A : AProtocol {
  required init() {}
}

class B : A {
  func fooOnlyInB() {
    print("foo in B")
  }
}


let a = A(completion: {
  print($0) // A
})

let b = B(completion: {
  $0.fooOnlyInB() // foo in B
})

This isn't however a perfect solution, as it makes it difficult for B to add its own stored properties, as they'll have to be given default values in order to satisfy the required init().

But really, as far as I'm aware, there's no real technical reason why you shouldn't be able to use Self as a parameter of a closure method argument in a class.

Function parameters are contravariant, so (A) -> Void is a subtype of (B) -> Void. Therefore an ((A) -> Void) -> T can be overridden by an ((B) -> Void) -> T (applying contravariance again), so the use of Self as the parameter of a function passed to a method should be perfectly legal.

It's just something the language doesn't fully support yet (SR-10121).

Hamish
  • 78,605
  • 19
  • 187
  • 280
1

Nope, I don't think it is possible because you can use Self only in protocols as it is just a placeholder for the type that is going to conform to that protocol. It is not a real type like A or B so you can't use that upon defining classes as if it is Any or AnyObject.

So let's create a protocol which forces conforming types to implement init(finishBlock:) initializer:

protocol BlockInitializable {
  init(finishBlock: ((_ operation: Self) -> Void)?)
}

class A: BlockInitializable {
  init() {}

  convenience required init(finishBlock: ((_ operation: A) -> Void)?) {
    self.init()
    finishBlock?(self)
  }
}

and you'll get the following error:

Protocol "Blocked" requirement init(finishBlock:) can not be satisifed by a non-final class (A) because it uses "Self" in a non-parameter, non-result type position.

and what's worse you'll lose the generic type Self of parameter operation.

To fix this, you should mark your class as final or use struct which is a final type. However, you'll lose the ability of subclassing those types. The reason why you have to do that and why you can't use Self in subclassed types is explained here so I recommend you go take a look at it.

I'll go with the latter option and use struct:

protocol Initializable {
  init()
}

protocol BlockInitializable: Initializable {
  init(finishBlock: ((_ operation: Self) -> Void)?)
}

extension BlockInitializable {
  init(finishBlock: ((_ operation: Self) -> Void)?) {
    self.init()
    finishBlock?(self)
  }
}

then define A and B as struct:

struct A: BlockInitializable {

}

struct B: BlockInitializable {
  func fooOnlyInB() {
    print("Only in B")
  }
}

and you'll be able to do the following:

let blockA = A { operation in
  print("Only in A")
}

let blockB = B { operation in
  operation.fooOnlyInB()
}

You can download the playground from here.

Community
  • 1
  • 1
Ozgur Vatansever
  • 49,246
  • 17
  • 84
  • 119
  • The whole point of this is that this class needs inheritance, so I guess there is nothing I can do about it - parameter "operation" needs to stay defined as A and in subclasses I need to cast it to preferred B (operation as? B). Too bad, it would be nice to have this working. Thank you for your answer. – Damian Dudycz Oct 15 '16 at 07:52
  • Yes that's a bummer. The basic reason of that is Swift strictly needs to know the type of `Self` in compile time. The other link I shared is explaining it much better. – Ozgur Vatansever Oct 15 '16 at 07:54