首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >由于Swift中的“私有”保护级别,在子类中创建超类初始化程序失败

由于Swift中的“私有”保护级别,在子类中创建超类初始化程序失败
EN

Stack Overflow用户
提问于 2022-11-16 17:35:33
回答 2查看 31关注 0票数 1

我有一个名为“来自框架SwiftySound的声音”的类和一个名为“SoundWrapper”的子类,这使得它成为可理解的。现在,我将字符串属性"imageName“添加到子类中,以便向每个声音添加一个图像。因此,我需要使用super.init为超类编写一个初始化器。

但是,当我尝试这样做时,错误会显示:“url”由于“私有”保护级别而无法访问。

有什么办法解决这个问题吗?

明确地说,这是我第一次在Swift中创建子类,所以我在这里可能会犯一些明显的错误,可能需要一种完全不同的方法。

这是我的子类:

代码语言:javascript
运行
复制
class SoundWrapper:Sound, Hashable{
    let id = UUID()
    let imageName : String
    
    init(imageName: String) {
           self.imageName = imageName
           super.init(url: url)
// Error: 'url' is inaccessible due to 'private' protection level
       }
    
    
    public func hash(into hasher: inout Hasher) {
            hasher.combine(0)
        }
    
    static func == (lhs: SoundWrapper, rhs: SoundWrapper) -> Bool {
        return lhs.id == rhs.id
    }
    
}

如何初始化子类:

代码语言:javascript
运行
复制
let backgroundAudio1 = SoundWrapper(imageName: "image1", url: getFileUrl(name: "backgroundAudio1"))

我是如何得到网址的:

代码语言:javascript
运行
复制
func getFileUrl (name: String) -> URL {
    let filePath = Bundle.main.path(forResource: name, ofType: "wav")!
    return URL(fileURLWithPath: filePath)
}

这是一个SwiftySound框架,我从这里获得声音类:

代码语言:javascript
运行
复制
import Foundation
import AVFoundation

#if os(iOS) || os(tvOS)
/// SoundCategory is a convenient wrapper for AVAudioSessions category constants.
    public enum SoundCategory {

        /// Equivalent of AVAudioSession.Category.ambient.
        case ambient
        /// Equivalent of AVAudioSession.Category.soloAmbient.
        case soloAmbient
        /// Equivalent of AVAudioSession.Category.playback.
        case playback
        /// Equivalent of AVAudioSession.Category.record.
        case record
        /// Equivalent of AVAudioSession.Category.playAndRecord.
        case playAndRecord

        fileprivate var avFoundationCategory: AVAudioSession.Category {
            get {
                switch self {
                case .ambient:
                    return .ambient
                case .soloAmbient:
                    return .soloAmbient
                case .playback:
                    return .playback
                case .record:
                    return .record
                case .playAndRecord:
                    return .playAndRecord
                }
            }
        }
    }
#endif


/// Sound is a class that allows you to easily play sounds in Swift. It uses AVFoundation framework under the hood.
open class Sound {

    // MARK: - Global settings

    /// Number of AVAudioPlayer instances created for every sound. SwiftySound creates 5 players for every sound to make sure that it will be able to play the same sound more than once. If your app doesn't need this functionality, you can reduce the number of players to 1 and reduce memory usage. You can increase the number if your app plays the sound more than 5 times at the same time.
    public static var playersPerSound: Int = 10 {
        didSet {
            stopAll()
            sounds.removeAll()
        }
    }

    #if os(iOS) || os(tvOS)
    /// Sound session. The default value is the shared `AVAudioSession` session.
    public static var session: Session = AVAudioSession.sharedInstance()

    /// Sound category for current session. Using this variable is a convenient way to set AVAudioSessions category. The default value is .ambient.
    public static var category: SoundCategory = {
        let defaultCategory = SoundCategory.ambient
        try? session.setCategory(defaultCategory.avFoundationCategory)
        return defaultCategory
        }() {
        didSet {
            try? session.setCategory(category.avFoundationCategory)
        }
    }
    #endif

    private static var sounds = [URL: Sound]()

    private static let defaultsKey = "com.moonlightapps.SwiftySound.enabled"

    /// Globally enable or disable sound. This setting value is stored in UserDefaults and will be loaded on app launch.
    public static var enabled: Bool = {
        return !UserDefaults.standard.bool(forKey: defaultsKey)
        }() { didSet {
            let value = !enabled
            UserDefaults.standard.set(value, forKey: defaultsKey)
            if value {
                stopAll()
            }
        }
    }

    private let players: [Player]

    private var counter = 0

    /// The class that is used to create `Player` instances. Defaults to `AVAudioPlayer`.
    public static var playerClass: Player.Type = AVAudioPlayer.self

    /// The bundle used to load sounds from filenames. The default value of this property is Bunde.main. It can be changed to load sounds from another bundle.
    public static var soundsBundle: Bundle = .main

    // MARK: - Initialization

    /// Create a sound object.
    ///
    /// - Parameter url: Sound file URL.
    public init?(url: URL) {
        #if os(iOS) || os(tvOS)
            _ = Sound.category
        #endif
        let playersPerSound = max(Sound.playersPerSound, 1)
        var myPlayers: [Player] = []
        myPlayers.reserveCapacity(playersPerSound)
        for _ in 0..<playersPerSound {
            do {
                let player = try Sound.playerClass.init(contentsOf: url)
                myPlayers.append(player)
            } catch {
                print("SwiftySound initialization error: \(error)")
            }
        }
        if myPlayers.count == 0 {
            return nil
        }
        players = myPlayers
        NotificationCenter.default.addObserver(self, selector: #selector(Sound.stopNoteRcv), name: Sound.stopNotificationName, object: nil)
    }

    deinit {
        NotificationCenter.default.removeObserver(self, name: Sound.stopNotificationName, object: nil)
    }

    @objc private func stopNoteRcv() {
        stop()
    }

    private static let stopNotificationName = Notification.Name("com.moonlightapps.SwiftySound.stopNotification")

    // MARK: - Main play method

    /// Play the sound.
    ///
    /// - Parameter numberOfLoops: Number of loops. Specify a negative number for an infinite loop. Default value of 0 means that the sound will be played once.
    /// - Returns: If the sound was played successfully the return value will be true. It will be false if sounds are disabled or if system could not play the sound.
    @discardableResult public func play(numberOfLoops: Int = 0, completion: PlayerCompletion? = nil) -> Bool {
        if !Sound.enabled {
            return false
        }
        paused = false
        counter = (counter + 1) % players.count
        let player = players[counter]
        return player.play(numberOfLoops: numberOfLoops, completion: completion)
    }

    // MARK: - Stop playing

    /// Stop playing the sound.
    public func stop() {
        for player in players {
            player.stop()
        }
        paused = false
    }

    /// Pause current playback.
    public func pause() {
        players[counter].pause()
        paused = true
    }


    /// Resume playing.
    @discardableResult public func resume() -> Bool {
        if paused {
            players[counter].resume()
            paused = false
            return true
        }
        return false
    }

    /// Indicates if the sound is currently playing.
    public var playing: Bool {
        return players[counter].isPlaying
    }

    /// Indicates if the sound is paused.
    public private(set) var paused: Bool = false

    // MARK: - Prepare sound

    /// Prepare the sound for playback
    ///
    /// - Returns: True if the sound has been prepared, false in case of error
    @discardableResult public func prepare() -> Bool {
        let nextIndex = (counter + 1) % players.count
        return players[nextIndex].prepareToPlay()
    }

    // MARK: - Convenience static methods

    /// Play sound from a sound file.
    ///
    /// - Parameters:
    ///   - file: Sound file name.
    ///   - fileExtension: Sound file extension.
    ///   - numberOfLoops: Number of loops. Specify a negative number for an infinite loop. Default value of 0 means that the sound will be played once.
    /// - Returns: If the sound was played successfully the return value will be true. It will be false if sounds are disabled or if system could not play the sound.
    @discardableResult public static func play(file: String, fileExtension: String? = nil, numberOfLoops: Int = 0) -> Bool {
        if let url = url(for: file, fileExtension: fileExtension) {
            return play(url: url, numberOfLoops: numberOfLoops)
        }
        return false
    }

    /// Play a sound from URL.
    ///
    /// - Parameters:
    ///   - url: Sound file URL.
    ///   - numberOfLoops: Number of loops. Specify a negative number for an infinite loop. Default value of 0 means that the sound will be played once.
    /// - Returns: If the sound was played successfully the return value will be true. It will be false if sounds are disabled or if system could not play the sound.
    @discardableResult public static func play(url: URL, numberOfLoops: Int = 0) -> Bool {
        if !Sound.enabled {
            return false
        }
        var sound = sounds[url]
        if sound == nil {
            sound = Sound(url: url)
            sounds[url] = sound
        }
        return sound?.play(numberOfLoops: numberOfLoops) ?? false
    }

    /// Stop playing sound for given URL.
    ///
    /// - Parameter url: Sound file URL.
    public static func stop(for url: URL) {
        let sound = sounds[url]
        sound?.stop()
    }

    /// Duration of the sound.
    public var duration: TimeInterval {
        get {
            return players[counter].duration
        }
    }

    /// Sound volume.
    /// A value in the range 0.0 to 1.0, with 0.0 representing the minimum volume and 1.0 representing the maximum volume.
    public var volume: Float {
        get {
            return players[counter].volume
        }
        set {
            for player in players {
                player.volume = newValue
            }
        }
    }

    /// Stop playing sound for given sound file.
    ///
    /// - Parameters:
    ///   - file: Sound file name.
    ///   - fileExtension: Sound file extension.
    public static func stop(file: String, fileExtension: String? = nil) {
        if let url = url(for: file, fileExtension: fileExtension) {
            let sound = sounds[url]
            sound?.stop()
        }
    }

    /// Stop playing all sounds.
    public static func stopAll() {
        NotificationCenter.default.post(name: stopNotificationName, object: nil)
    }

    // MARK: - Private helper method
    private static func url(for file: String, fileExtension: String? = nil) -> URL? {
        return soundsBundle.url(forResource: file, withExtension: fileExtension)
    }

}

/// Player protocol. It duplicates `AVAudioPlayer` methods.
public protocol Player: AnyObject {

    /// Play the sound.
    ///
    /// - Parameters:
    ///   - numberOfLoops: Number of loops.
    ///   - completion: Complation handler.
    /// - Returns: true if the sound was played successfully. False otherwise.
    func play(numberOfLoops: Int, completion: PlayerCompletion?) -> Bool

    /// Stop playing the sound.
    func stop()

    /// Pause current playback.
    func pause()

    /// Resume playing.
    func resume()

    /// Prepare the sound.
    func prepareToPlay() -> Bool

    /// Create a Player for sound url.
    ///
    /// - Parameter url: sound url.
    init(contentsOf url: URL) throws

    /// Duration of the sound.
    var duration: TimeInterval { get }

    /// Sound volume.
    var volume: Float { get set }

    /// Indicates if the player is currently playing.
    var isPlaying: Bool { get }
}

fileprivate var associatedCallbackKey = "com.moonlightapps.SwiftySound.associatedCallbackKey"

public typealias PlayerCompletion = ((Bool) -> ())

extension AVAudioPlayer: Player, AVAudioPlayerDelegate {

    public func play(numberOfLoops: Int, completion: PlayerCompletion?) -> Bool {
        if let cmpl = completion {
            objc_setAssociatedObject(self, &associatedCallbackKey, cmpl, .OBJC_ASSOCIATION_COPY_NONATOMIC)
            self.delegate = self
        }
        self.numberOfLoops = numberOfLoops
        return play()
    }

    public func resume() {
        play()
    }

    public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        let cmpl = objc_getAssociatedObject(self, &associatedCallbackKey) as? PlayerCompletion
        cmpl?(flag)
        objc_removeAssociatedObjects(self)
        self.delegate = nil
    }

    public func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
        print("SwiftySound playback error: \(String(describing: error))")
    }

}

#if os(iOS) || os(tvOS)
/// Session protocol. It duplicates `setCategory` method of `AVAudioSession` class.
public protocol Session: AnyObject {
    /// Set category for session.
    ///
    /// - Parameter category: category.
    func setCategory(_ category: AVAudioSession.Category) throws
}

extension AVAudioSession: Session {}
#endif
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2022-11-16 18:04:39

Sound提供了一个需要URL参数的公共初始化程序。您的包装程序还需要接受一个URL参数,以便它可以传递它。

代码语言:javascript
运行
复制
init(imageName: String, url: URL) {
    self.imageName = imageName
    super.init(url: url)
}

创建Sound实例时需要一个URL。在创建SoundWrapper实例时,需要相同的URL。

还请注意,对于所有实例,您的hash使用0的效率都比较低。为什么不使用id

代码语言:javascript
运行
复制
public func hash(into hasher: inout Hasher) {
    hasher.combine(id)
}
票数 1
EN

Stack Overflow用户

发布于 2022-11-16 17:48:30

您不会在url的任何地方定义SoundWrapper,因此Swift编译器正在试图找到在其他地方定义url的位置。我相信它是在Soundprivate static func url(for file: String, fileExtension: String? = nil) -> URL?上找到私有方法,并告诉您它是私有的。

所以问题是,您实际上想要传递给Sound基类初始化器的url是什么?

我设想您的SoundWrapper初始值将更改为如下所示:

代码语言:javascript
运行
复制
    init(imageName: String) {
           self.imageName = imageName
           super.init(url: URL(string: "example.com")!)
           // or use a URL defined on your class or somewhere else
       }
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/74464839

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档