-2

I was trying to make an extension to the Actor type coming from Learn to Code 2 in the Swift Playgounds for iPad as follows:

let theWorld = world

extension Actor {
    convenience init(_ x:Int, _ y:Int, facing direction: Direction = .east){
        self.init()
        theWorld.place(self, facing: direction, atColumn: x, row: y)
    }
}

when I call:

Actor(4,1)

the playground runs as expected.

But when I call:

// Expert is a subclass of Actor
Expert(4,1)

a compiler error appears. Why is that happening?

screenshot: playground screenshot

The code base (by Apple) behind this playground is quite large, so I just list the most relevant source codes (Actor and Expert type) as follows:

Actor type

//
//  Actor.swift
//  
//  Copyright © 2016-2019 Apple Inc. All rights reserved.
//

import SceneKit
import PlaygroundSupport

/**
 The type representing the different characters that can be present and move about in the world.
 - localizationKey: Actor
*/
public class Actor: Item, NodeConstructible {
    // MARK: Static

    static var commandSpeed: Float = WorldConfiguration.Actor.idleSpeed

    public static let identifier: WorldNodeIdentifier = .actor

    // MARK: Item

    public var id = Identifier.undefined

    public let node: NodeWrapper

    public weak var world: GridWorld? {
        didSet {
            if let world = world {
                // Create a new `WorldActionComponent` when setting the world.
                addComponent(WorldActionComponent(actor: self, world: world))
                if !scnNode.childNodes.isEmpty {
                    // Start idling when placed in the world (if the geometry is loaded).
                    idleQueue.start(breathingOnly: true)
                }
            }
            else {
                removeComponent(ofType: WorldActionComponent.self)
                stopContinuousIdle()
            }
        }
    }

    // MARK: Actor Properties

    var type: ActorType

    /// The action that is currently running, if any.
    fileprivate(set) var currentAction: Action?

    var components = [ActorComponent]()

    var result: PerformerResult?

    /// Indicates if the actor has components currently running.
    var isRunning: Bool {
        return runningComponents.isEmpty == false
    }

    /// A flag to indicate if this character is currently being presented in the character picker. 
    var isInCharacterPicker = false

    /// A driver which causes the actor to idle indefinitely.
    lazy var idleQueue: ContinuousIdleQueue = ContinuousIdleQueue(actor: self)

    var isIdle: Bool {
        return idleQueue.isRunning
    }

    lazy var actorCamera: SCNNode = {
        // Programmatically add an actor camera.
        let actorCamera = SCNNode()
        actorCamera.position = SCNVector3Make(0, 0.785, 3.25)
        actorCamera.eulerAngles.x = -0.1530727
        actorCamera.camera = SCNCamera()
        actorCamera.name = "actorCamera"
        self.scnNode.addChildNode(actorCamera)

        return actorCamera
    }()

    // Private properties

    /// Used to synchronously access `runningComponents`.
    private let componentsQueue = DispatchQueue(label: "com.LTC.RunningComponents")
    private var _runningComponents = [ActorComponent]()

    /// Returns the components that are still running for this actor.
    /// Note: This synchronously blocks for access to the underlying array.
    fileprivate var runningComponents: [ActorComponent] {
        get {
            var runningComponents: [ActorComponent]!
            componentsQueue.sync { runningComponents = _runningComponents }
            return runningComponents
        }

        set {
            componentsQueue.sync { _runningComponents = newValue }
        }
    }

    // MARK: Initialization

    /// Creates a character (or actor) with the specified name. The character can then be placed in the world.
    ///
    /// Example usage:
    /// ````
    /// let blu = Character(name: .blu)
    /// ````
    ///
    /// - Parameter name: The name of the character chosen from the `CharacterName` enumeration. If you leave out `name`, the saved character will be used.
    ///
    /// - localizationKey: Actor(name:)
    public init(name: CharacterName? = nil) {
        self.type = name?.type ?? ActorType.loadDefault()
        node = NodeWrapper(identifier: .actor)

        commonInit()
    }

    public required init?(node: SCNNode) {
        guard node.identifier == .actor
            && node.identifierComponents.count >= 2 else { return nil }
        guard let type = ActorType(rawValue: node.identifierComponents[1]) else { return nil }
        self.type = type
        self.node = NodeWrapper(node)

        commonInit()
    }

    func commonInit() {
        addComponent(AnimationComponent(actor: self))

        // Check if audio is enabled.
        if Persisted.areSoundEffectsEnabled {
            addComponent(AudioComponent(actor: self))
        }

        scnNode.categoryBitMask = WorldConfiguration.characterLightBitMask
    }

    // MARK: Components

    /// Adds an ActorComponent to this Actor.
    /// There may only be one component instance for each component type.
    func addComponent<T: ActorComponent>(_ component: T) {
        // Remove any component of the same type.
        removeComponent(ofType: T.self)

        components.append(component)
    }

    func component<T : ActorComponent>(ofType componentType: T.Type) -> T? {
        for component in components where component is T {
            return component as? T
        }
        return nil
    }

    @discardableResult
    func removeComponent<T : ActorComponent>(ofType componentType: T.Type) -> T? {
        guard let index = components.firstIndex(where: { $0 is T }) else { return nil }

        let component = components.remove(at: index) as? T

        // If a current action is running, make sure this component gets cancelled.
        if let action = currentAction {
            component?.cancel(action)
        }

        return component
    }

    // MARK: Geometry

    public func loadGeometry() {
        guard scnNode.childNodes.isEmpty else { return }
        scnNode.addChildNode(type.createNode())

        if #available(iOS 13.0, *) {
            updateAppearance(traitCollection: UITraitCollection.current)
        }
    }

    public func updateAppearance(traitCollection: UITraitCollection) {
        guard #available(iOS 13.0, *) else { return }

        if let geoBody = scnNode.childNode(withName: "geoBody", recursively: true) {
            if let mat = geoBody.firstGeometry?.firstMaterial {
                mat.selfIllumination.updateAppearance(traitCollection: traitCollection)
            }
        }
    }

    // MARK: Creating Commands

    /// Convenience to create an `Command` by bundling in `self` with the provided action.
    func add(action: Action) {
        guard let world = world else { return }
        let command = Command(performer: self, action: action)
        world.commandQueue.append(command, applyingState: true)
    }

    func animationCommands(_ types: [EventGroup]) -> [Command] {
        return types.map { animationCommand($0) }
    }

    func animationCommand(_ group: EventGroup, variation: Int? = nil) -> Command {
        let highPriority = group == .victory || group == .celebration
        return Command(performer: self, action: .run(group, variation: variation), isHighPriorityCommand: highPriority)
    }

    // MARK: Animation

    /// Starts running the continuous idle. Reset by asking the scene to run.
    func startContinuousIdle() {
        guard !idleQueue.isRunning || idleQueue.isBreathingOnly else { return }

        idleQueue.start(initialAnimations: [.idle])
    }

    func stopContinuousIdle() {
        idleQueue.stop()
    }

    /// Stops the continuous idle,
    /// clears the SCNNode for the actor of actions and animations,
    /// and cancels the running action.
    public func reset() {
        // Stop any upcoming idle animations.
        stopContinuousIdle()

        // Make sure all animations are cleared. 
        let animation = component(ofType: AnimationComponent.self)
        animation?.removeAnimations()
        scnNode.removeAllActions()

        // Cancel any `currentAction` that is running.
        guard let action = currentAction else { return }
        cancel(action)
        currentAction = nil
    }

    // MARK: CharacterPicker Swap

    func swap(with actor: Actor) {
        actor.scnNode.removeAllAnimations()
        actor.scnNode.removeAllActions()

        for child in scnNode.childNodes { child.removeFromParentNode() }
        for child in actor.scnNode.childNodes { scnNode.addChildNode(child) }

        type = actor.type
    }

    // MARK: Jump

    /**
     Instructs the character to jump forward and either up or down one block. 

     If the block the character is facing is one block higher than the block the character is standing on, the character will jump on top of it.
     If the block the character is facing is one block lower than the block the character is standing on, the character will jump down to that block.
     - localizationKey: Actor.jump()
     */
    @discardableResult
    public func jump() -> Coordinate {
        return _jump()
    }
}

extension Actor: Equatable{}

public func ==(lhs: Actor, rhs: Actor) -> Bool {
    return lhs.type == rhs.type && lhs.node === rhs.node
}

extension Actor {
    // MARK: Performer

    func applyStateChange(for action: Action) {
        for performer in components {
            performer.applyStateChange(for: action)
        }
    }

    /// Cycles through the actors components allowing each component to respond to the action.
    func perform(_ action: Action) -> PerformerResult {
        // Clear the `currentAction`.
        if isRunning, let currentAction = currentAction {
            cancel(currentAction)
        }
        currentAction = nil
        runningComponents.removeAll()

        // If the `currentCommand` is running for this actor, ensure the `idleQueue` is stopped.
        let command = world?.commandQueue.currentCommand
        if command?.performer === self {
            stopContinuousIdle()
        }

        // Not all commands apply to the actor, return immediately if there is no action.
        guard let event = action.event else {
            fatalError("The actor has been asked to perform \(action), but there is no valid event associated with this action.")
        }
        currentAction = action

        // Mark all the components as running.
        runningComponents = components

        let index = action.variationIndex
        for component in components {
            let componentResult = component.perform(event: event, variation: index)
            componentResult.completionHandler = { [weak self] performer in
                self?.performerFinished(performer)
            }
        }

        result = PerformerResult(self, isAsynchronous: isRunning)
        return result!
    }

    /// Performs the event only as an animation.
    func perform(event: EventGroup, variation: Int? = nil) -> PerformerResult {
        return perform(.run(event, variation: variation))
    }

    /// Cancels all components.
    func cancel(_ action: Action) {
        result = nil

        // A lot of components don’t hold as running, but need to be reset with cancel.
        for performer in components {
            performer.cancel(action)
        }

        currentAction = nil
    }

    // MARK: Performer Finished

    func performerFinished(_ performer: Performer) {
        // Match the finished performer with the remaining `runningComponents`.
        guard let index = runningComponents.firstIndex(where: { $0 === performer }) else {
            log(message: "\(performer) reported it was finished, but does not belong to \(self)")
            return
        }
        runningComponents.remove(at: index)

        if !isRunning {
            currentAction = nil
            result?.complete()

            let nextCommand = world?.commandQueue.pendingCommands.first
            if !isIdle, nextCommand?.performer !== self {
                // Enter a neutral idle while waiting for the next command.
                idleQueue.start(breathingOnly: true)
            }
        }
    }
}

extension Actor {
    // MARK: Movement Commands

    /**
     Moves the character forward by a certain number of tiles, as determined by the `distance` parameter value.

     Example usage:
     ````
     move(distance: 3)
     // Moves forward three tiles.
     ````

     - parameters:
        - distance: Takes an `Int` value specifying the number of times to call `moveForward()`.
     - localizationKey: Actor.move(distance:)
     */
    public func move(distance: Int) {
        for _ in 1 ... distance {
            moveForward()
        }
    }

    /**
     Moves the character forward one tile.
     - localizationKey: Actor.moveForward()
     */
    @discardableResult
    public func moveForward() -> Coordinate {
        guard let world = world else { return coordinate }
        let movementObstacle = world.movementObstacle(heading: heading, from: position)

        guard movementObstacle.isEmpty else {
            if movementObstacle.isCollisionHazard {
                add(action: .fail(.intoWall))
            }
            else {
                assert(movementObstacle.isFallingHazard, "Unhandled movement case: \(movementObstacle)")
                add(action: .fail(.offEdge))
            }
            return coordinate
        }

        let nextCoordinate = nextCoordinateInCurrentDirection

        // Check for stairs.
        let yDisplacement = position.y + heightDisplacementMoving(to: nextCoordinate)
        let point = nextCoordinate.position

        let destination = SCNVector3Make(point.x, yDisplacement, point.z)
        let displacement = Displacement(from: position, to: destination)
        add(action: .move(displacement, type: .walk))

        // Check for portals.
        addCommandForPortal(at: nextCoordinate)

        return nextCoordinate
    }

    // `_jump()` included only for organizational purposes
    // (`jump()` is overridden by Expert).
    fileprivate func _jump() -> Coordinate {
        guard let world = world else { return coordinate }
        let movementResult = world.movementObstacle(heading: heading, from: position)

        let nextCoordinate = nextCoordinateInCurrentDirection
        let deltaY = heightDisplacementMoving(to: nextCoordinate)

        // Determine if the y displacement is small enough such that the character can
        // cover it with a jump.
        let toleranceY = abs(deltaY) - WorldConfiguration.heightTolerance
        let isJumpableDisplacement = toleranceY < WorldConfiguration.levelHeight

        switch movementResult {
        case [],
             [.raisedTile] where isJumpableDisplacement,
             [.loweredTile] where isJumpableDisplacement:

            let point = nextCoordinate.position
            let destination = SCNVector3Make(point.x, position.y + deltaY, point.z)
            let displacement = Displacement(from: position, to: destination)
            add(action: .move(displacement, type: .jump))

            // Check for portals.
            addCommandForPortal(at: nextCoordinate)

            return nextCoordinate
        default:
            // Error cases evaluate to the same result as `moveForward()`.
            return moveForward()
        }
    }

    /**
     Turns the character left.
     - localizationKey: Actor.turnLeft()
     */
    public func turnLeft() {
        turnBy(90)
    }

    /**
     Turns the character right.
     - localizationKey: Actor.turnRight()
     */
    public func turnRight() {
        turnBy(-90)
    }

    // MARK: Movement Helpers

    /// Creates a new command if a portal exists at the specified coordinate.
    private func addCommandForPortal(at coordinate: Coordinate) {
        let portal = world?.existingItem(ofType: Portal.self, at: coordinate)
        if let destinationPortal = portal?.linkedPortal, portal!.isActive {
            let displacement = Displacement(from: position, to: destinationPortal.position)

            add(action: .move(displacement, type: .teleport))
        }
    }

    /**
     Rotates the actor by `degrees` around the y-axis.

     - turnLeft() = 90
     - turnRight() = -90/ 270
     */
    @discardableResult
    private func turnBy(_ degrees: Int) -> SCNFloat {
        // Convert degrees to radians.
        let nextDirection = (rotation + degrees.toRadians).truncatingRemainder(dividingBy: 2 * π)

        let currentDir = Direction(radians: rotation)
        let nextDir = Direction(radians: nextDirection)

        let clockwise = currentDir.angle(to: nextDir) < 0
        let displacement = Displacement(from: rotation, to: nextDirection)
        add(action: .turn(displacement, clockwise: clockwise))

        return nextDirection
    }

    /// Returns the next coordinate moving forward 1 tile in the actors `currentDirection`.
    var nextCoordinateInCurrentDirection: Coordinate {
        return coordinateInCurrentDirection(displacement: 1)
    }

    func coordinateInCurrentDirection(displacement: Int) -> Coordinate {
        let heading = Direction(radians: rotation)
        let coordinate = Coordinate(position)

        return coordinate.advanced(by: displacement, inDirection: heading)
    }

    func heightDisplacementMoving(to coordinate: Coordinate) -> SCNFloat {
        guard let world = world else { return 0 }
        let startHeight = position.y
        let endHeight = world.nodeHeight(at: coordinate)

        return endHeight - startHeight
    }
}

extension Actor {
    // MARK: Item Commands

    /**
     Instructs the character to collect a gem on the current tile.
     - localizationKey: Actor.collectGem()
     */
    @discardableResult
    public func collectGem() -> Bool {
        guard let item = world?.existingGems(at: [coordinate]).first else {
            add(action: .fail(.missingGem))
            return false
        }

        add(action: .remove([item.id]))
        return true
    }

    /**
     Instructs the character to toggle a switch on the current tile.
     - localizationKey: Actor.toggleSwitch()
     */
    @discardableResult
    public func toggleSwitch() -> Bool {
        guard let switchNode = world?.existingItem(ofType: Switch.self, at: coordinate) else {
            add(action: .fail(.missingSwitch))
            return false
        }

        // Toggle switch to the opposite of it’s original value.
        let oldValue = switchNode.isOn
        let cont = Controller(identifier: switchNode.id, kind: .toggle, state: !oldValue)
        add(action: .control(cont))

        return true
    }
}

extension Actor {
    // MARK: Boolean Commands

    /**
     Condition that checks whether the character is on a tile with a gem on it.
     - localizationKey: Actor.isBlocked
     */
    public var isBlocked: Bool {
        guard let world = world else { return false }
        return !world.isValidActorTranslation(heading: heading, from: position)
    }

    /**
     Condition that checks whether the character is blocked on the left.
     - localizationKey: Actor.isBlockedLeft
     */
    public var isBlockedLeft: Bool {
        return isBlocked(heading: .west)
    }

    /**
     Condition that checks whether the character is blocked on the right.
     - localizationKey: Actor.isBlockedRight
     */
    public var isBlockedRight: Bool {
        return isBlocked(heading: .east)
    }

    func isBlocked(heading: Direction) -> Bool {
        guard let world = world else { return false }
        let blockedCheckDir = Direction(radians: rotation - heading.radians)

        return !world.isValidActorTranslation(heading: blockedCheckDir, from: position)
    }

    // MARK: isOn

    /**
     Condition that checks whether the character is currently on a tile with that contains a WorldNode of a specific type.
     - localizationKey: Actor.isOnItem
     */
    public func isOnItem<Node: Item>(ofType type: Node.Type) -> Bool {
        return itemAtCurrentPosition(ofType: type) != nil
    }

    /**
     Condition that checks whether the character is on a tile with a gem on it.
     - localizationKey: Actor.isOnGem
     */
    public var isOnGem: Bool {
        return isOnItem(ofType: Gem.self)
    }

    /**
     Condition that checks whether the character is on a tile with an open switch on it.
     - localizationKey: Actor.isOnOpenSwitch
     */
    public var isOnOpenSwitch: Bool {
        if let switchNode = itemAtCurrentPosition(ofType: Switch.self) {
            return switchNode.isOn
        }
        return false
    }

    /**
    Condition that checks whether the character is on a tile with a closed switch on it.
     - localizationKey: Actor.isOnClosedSwitch
     */
    public var isOnClosedSwitch: Bool {
        if let switchNode = itemAtCurrentPosition(ofType: Switch.self) {
            return !switchNode.isOn
        }
        return false
    }

    func itemAtCurrentPosition<T: Item>(ofType type: T.Type) -> T?  {
        guard let world = world else { return nil }
        return world.existingItem(ofType: type, at: coordinate)
    }
}

extension Actor {
    // MARK: Dance Actions

    /**
     Causes the character to bust out a fancy move.
     - localizationKey: Actor.danceLikeNoOneIsWatching()
     */
    public func danceLikeNoOneIsWatching() {
        add(action: .run(.victory, variation: 1))
    }

    /**
     The character receives a burst of energy, turning it up by several notches.
     - localizationKey: Actor.turnUp()
     */
    public func turnUp() {
        add(action: .run(.victory, variation: 0))
    }

    /**
     The character starts to feel real funky, breaking it down for all to witness.
     - localizationKey: Actor.breakItDown()
     */
    public func breakItDown() {
        add(action: .run(.celebration, variation: nil))
    }


    // MARK: Defeat Actions

    /**
     The character feels a bit bummed.
     - localizationKey: Actor.grumbleGrumble()
     */
    public func grumbleGrumble() {
        add(action: .run(.defeat, variation: 0))
    }

    /**
     The character feels a wave of horror.
     - localizationKey: Actor.argh()
     */
    public func argh() {
        add(action: .run(.defeat, variation: 1))
    }

    /**
     The character performs a head scratch.
     - localizationKey: Actor.headScratch()
     */
    public func headScratch() {
        add(action: .run(.defeat, variation: 2))
    }

    // MARK: Misc Actions

    /**
     The character hits a metaphorical wall.
     - localizationKey: Actor.bumpIntoWall()
     */
    public func bumpIntoWall() {
        add(action: .run(.bumpIntoWall, variation: nil))
    }
}

extension Actor: MessageConstructor {
    // MARK: MessageConstructor

    var message: PlaygroundValue {
        return .array(baseMessage + stateInfo)
    }

    var stateInfo: [PlaygroundValue] {
        return [.string(type.rawValue)]
    }
}

Expert type

//
//  Expert.swift
//  
//  Copyright © 2016-2019 Apple Inc. All rights reserved.
//

import SceneKit

/**
 The type representing the expert; capable of turning locks.
 - localizationKey: Expert
 */
public final class Expert: Actor {

    public convenience init() {
        let node = WorldNodeIdentifier.actor.makeNode()
        node.name! += "-" + ActorType.expert.rawValue
        self.init(node: node)!
    }

    public required init?(node: SCNNode) {
        super.init(node: node)

        self.node.isModular = true
    }

    @available(*, unavailable, message:"The Expert can't jump . Only the Character type can use the `jump()` method.")
    @discardableResult
    public override func jump() -> Coordinate {
        return coordinate
    }

    public override func loadGeometry() {
        guard scnNode.childNodes.isEmpty else { return }
        scnNode.addChildNode(ActorType.expert.createNode())

        if #available(iOS 13.0, *) {
            updateAppearance(traitCollection: UITraitCollection.current)
        }
    }

    override public func updateAppearance(traitCollection: UITraitCollection) {
        guard #available(iOS 13.0, *) else { return }

        if let geoGroup = scnNode.childNode(withName: "GEOgrp", recursively: true) {
            for node in geoGroup.childNodes {
                if let mat = node.firstGeometry?.firstMaterial {
                    mat.selfIllumination.updateAppearance(traitCollection: traitCollection)
                }
            }
        }
    }
}

extension Expert {

    /**
    Method that turns a lock up, causing all linked platforms to rise by the height of one block.
     - localizationKey: Expert.turnLockUp()
     */
    public func turnLockUp() {
        turnLock(up: true)
    }

    /**
     Method that turns a lock down, causing all linked platforms to fall by the height of one block.
     - localizationKey: Expert.turnLockDown()
     */
    public func turnLockDown() {
        turnLock(up: false)
    }

    /**
    Method that turns a lock up or down a certain number of times.

     Example usage:
     ````
     turnLock(up: false, numberOfTimes: 3)
     // Turns the lock down three times.

     turnLock(up: true, numberOfTimes: 4)
     // Turns the lock up four times.
     ````

     - parameters:
        - up: Takes a Boolean specifying whether the lock should be turned up (`true`) or down (`false`).
        - numberOfTimes: Takes an `Int` specifying the number of times to turn the lock.
     - localizationKey: Expert.turnLock(up:numberOfTimes:)
     */
    public func turnLock(up: Bool, numberOfTimes: Int) {
        for _ in 1...numberOfTimes {
            turnLock(up: up)
        }
    }

    func turnLock(up: Bool) {
        let lock = world?.existingItem(ofType: PlatformLock.self, at: nextCoordinateInCurrentDirection)

        if let lock = lock, lock.height == height {
            let controller = Controller(identifier: lock.id, kind: .movePlatforms, state: up)

            add(action: .control(controller))
        }
        else {
            add(action: .fail(.missingLock))
        }
    }
}
lochiwei
  • 1,240
  • 9
  • 16
  • 1
    Please [edit] your question to include all relevant code in the form of a [mcve]. Also, what compiler error are you getting? – Dávid Pásztor Apr 15 '20 at 16:28
  • 1
    `a compiler error appears. Why is that happening?` How should we know? You didn't post the error :p – Alexander Apr 15 '20 at 16:28
  • Also, using a simplified version of your code, I cannot reproduce this in an Xcode playground. The short answer to the question in your title is yes, so if you get an error, it's caused by something else. – Dávid Pásztor Apr 15 '20 at 16:32
  • @DávidPásztor , sadly I have provided all my code already, please see the screenshot I attached above. – lochiwei Apr 15 '20 at 17:01
  • @lochiwei that cannot be all your code. Where's the declaration for `Actor`? What's `world`? – Dávid Pásztor Apr 16 '20 at 08:28
  • @DávidPásztor those source codes are provided by Apple. Have you ever played with those "playgrounds" in the Swift Playgrounds for iPad? Those source codes can be found in the top right ... menu > Advanced > View Auxiliary Source Files. – lochiwei Apr 16 '20 at 08:50
  • @lochiwei even if they're provided by Apple, to make your question on-topic here on SO, all relevant code needs to be included in the __body of your question__, not just via an external link and especially not embedded in an app. – Dávid Pásztor Apr 16 '20 at 08:58
  • @DávidPásztor I have included the most relevant source code in the body of my question, hope that helps. – lochiwei Apr 16 '20 at 11:05
  • @lochiwei your question is still not on-topic, since it doesn't contain the actual error you get AND now instead of containing too little code to be able to test, it contains a lot of unrelated code. Please try to reduce your code into a [mcve] as I've already suggested in my first comment in order to make your question on topic. – Dávid Pásztor Apr 16 '20 at 11:08
  • @DávidPásztor I'm afraid I'm not capable enough to make a minimal reproducible example by my self, I don't own the code of those underlying types, their inner working looks like a black box to me, if I could reproduce the problem myself, maybe it's because I already know what the problem is. But it's not hard to reproduce the error actually, just get an iPad, open the Swift Playgrounds for iPad app, go to the *Learn to Code 2* playground file, and type in my code in the original post above (no more than 10 lines) , and the error will appear. – lochiwei Apr 16 '20 at 11:50
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/211840/discussion-between-lochiwei-and-david-pasztor). – lochiwei Apr 16 '20 at 23:58

1 Answers1

0

From the source code of the Learn to Code 2 playground (by Apple), we can figure out part of the class inheritance hierarchy of the types involved: inheritance hierarchy

Subclasses do not inherit their superclass initializers by default, but if a subclass provides default values for any new stored properties, the following two rules apply:

  • (Rule 1): if the subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
  • (Rule 2): if the subclass provides an implementation of all of its superclass designated initializers, then it automatically inherits all of the superclass convenience initializers.

These rules apply even if the subclass adds further convenience initializers.

The following diagram shows the initializers of Actor and Expert class: initializers

From the source code we know that Expert doesn't introduce any new properties, but there is a superclass designated initializer init(name:) that Expert doesn't implement, which means Expert cannot inherit any of its superclass initializers.

Even worse, we can't provide a convenience override init for that init(name:) initializer in an extension, since overriding declarations in extensions is not supported in Swift.

So there's no way for Expert to inherit any of its superclass initializers.

lochiwei
  • 1,240
  • 9
  • 16