子类是不是从 Swift 中的超类扩展继承便利初始化程序?

Posted

技术标签:

【中文标题】子类是不是从 Swift 中的超类扩展继承便利初始化程序?【英文标题】:Does a subclass inherit convenience initializers from a superclass extension in Swift?子类是否从 Swift 中的超类扩展继承便利初始化程序? 【发布时间】:2020-04-15 16:25:09 【问题描述】:

我试图在 iPad 的 Swift Playgounds 中对来自 Learn to Code 2 的 Actor 类型进行扩展,如下所示:

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)
    

当我打电话时:

Actor(4,1)

游乐场按预期运行。

但是当我打电话时:

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

出现编译器错误。 为什么会这样?

截图:

这个操场背后的代码库(Apple)非常大,所以我只列出最相关的源代码(ActorExpert 类型)如下:

演员类型

//
//  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.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))
        
    

【问题讨论】:

请edit 您的问题以minimal reproducible example 的形式包含所有相关代码。另外,您遇到了什么编译器错误? a compiler error appears. Why is that happening? 我们怎么知道?您没有发布错误:p 另外,使用您的代码的简化版本,我无法在 Xcode 操场上重现这一点。对标题中问题的简短回答是肯定的,所以如果您遇到错误,那是由其他原因引起的。 @DávidPásztor ,很遗憾我已经提供了我所有的代码,请看我上面附上的截图。 @lochiwei 这不可能是你的全部代码。 Actor 的声明在哪里? world 是什么? 【参考方案1】:

Learn to Code 2 游乐场(Apple)的source code,我们可以找出所涉及类型的部分类继承层次结构:

Subclasses do not inherit their superclass initializers by default,但如果子类为任何新存储的属性提供默认值,则适用以下两条规则:

(规则 1):如果子类没有定义任何指定的初始化器,它会自动继承其所有超类的指定初始化器。 (规则 2):如果子类提供其所有父类指定初始化器的实现,则它自动继承所有超类便利初始化器。

即使子类添加了更多便利初始化器,这些规则也适用。

下图展示了ActorExpert类的初始化器:

从源代码我们知道Expert没有引入任何新属性,但是有一个超类指定初始化器init(name:)Expert没有实现,这意味着Expert不能继承它的任何超类初始化器。

更糟糕的是,我们无法在扩展中为 init(name:) 初始化器提供 convenience override init,因为 Swift 不支持覆盖扩展中的声明。

因此,Expert 无法继承其任何超类初始值设定项。

【讨论】:

以上是关于子类是不是从 Swift 中的超类扩展继承便利初始化程序?的主要内容,如果未能解决你的问题,请参考以下文章

通过首先将二维数组发送到 C++ 中的超类来初始化子类?

Swift类构造器的继承和重写

Java中的间接子类无法访问的超类中的受保护成员

`attemptRecovery(fromError:optionIndex:)` 在 NSDocument 的 Swift 子类的超类中找不到

从 PHP 中的超类获取子类命名空间

在 Swift 中覆盖具有不同类型的超类属性