代码之家  ›  专栏  ›  技术社区  ›  Chris

它都以“protocol只能用作泛型约束”开头,因为它具有自身或关联的类型需求`

  •  0
  • Chris  · 技术社区  · 7 年前

    我正在解决一个小问题:同时获取一组有序数据块并以正确的顺序输出(具体来说,获取 .ts 文件,通过管道传递 ffmpeg 按照正确的顺序,但这是偶然的)。为了将重新排序(按顺序交付片段)和管道(让ffmpeg来做这件事)分开,我将其编写为两个对象:

    struct Aggregator<ChunkType> {
    
        private var currentIdx = 0
        private var chunks: [Int:ChunkType] = [:]
        private let serializer: Serializer
    
        init(serializer: Serializer) where Serializer.ChunkType == ChunkType {
            // self.serializer = serializer
        }
    
        private let queue = DispatchQueue(label: "Serial")
    
        mutating func accept(chunk:ChunkType, forIndex idx: Int) {
            // One accessor at a time
            queue.sync {
                chunks[idx] = chunk
    
                // Forward zero or more chunks
                while chunks[currentIdx] != nil {
                    //serializer.append(chunk: chunks.removeValue(forKey: currentIdx))
                    currentIdx += 1
                }
            }
        }
    }
    

    以及:

    protocol Serializer {
        associatedtype ChunkType
        func append(chunk: ChunkType)
    }
    

    有两种实现:

    // For capturing the output of an Aggregator<Int> to check it's working as intended
    class IntArraySerializer: Serializer {
        typealias ChunkType = Int
    
        var allInts = [Int]()
    
        func append(chunk: ChunkType) {
            allInts.append(chunk)
        }
    }
    
    // For piping output through ffmpeg
    class FFMPEGSerializer: Serializer {
        typealias ChunkType = Data
    
        private let ffmpegBin = "/usr/local/bin/ffmpeg"
        private let ffmpeg: Process
        private let stdin: Pipe
    
        init(args: [String]) throws {
            ffmpeg = Process()
            ffmpeg.launchPath = ffmpegBin
            ffmpeg.arguments = args
            stdin = Pipe()
            ffmpeg.standardInput = stdin
        }
    
        func append(chunk: Data) {
            stdin.fileHandleForWriting.write(chunk)
        }
    }
    

    最终目标是使用类似于:

    let ffmpeg = FFMPEGSerializer(["-i", "-", "-c", "copy", "-bsf:a aac_adtstoasc", "output.mp4"])
    let aggregator = Aggregator(serializer: ffmpeg)
    for (idx, url) in someListOfUrls.enumerated() {
        concurrentQueue.async {
            fetch(url) { data in
                aggregator.accept(chunk: data)
            }
        }
    }
    

    然而,我对相关类型、泛型和可能的类型擦除(如果这确实是这里的解决方案)的理解阻碍了我最终确定 Aggregator ,可以从各种注释行中看到。我读过一些关于类型擦除的内容(例如。 this Big Nerd Ranch article ),但我没有掌握如何或是否应该尝试将其应用到我的案例中。

    归结为一句话:我想要 ChunkType Serializer 通知 聚合器

    1 回复  |  直到 7 年前
        1
  •  0
  •   OOPer    7 年前

    由于错误消息明确指出,您不能使用该协议 Serializer 注释特性或参数的类型。

    我不明白你的意思 类型擦除 ,所以我可能误解了您的意思,但就我阅读您的代码而言,您可以轻松地修复 Aggregator 使用泛型:

    //Make `S` a generic parameter constrained to `Serializer`
    struct Aggregator<S: Serializer> {
        //`ChunkType` needs always to be the same type as `S.ChunkType`
        typealias ChunkType = S.ChunkType
    
        private var currentIdx = 0
        private var chunks: [Int: ChunkType] = [:]
        //You can use `S` as a type of properties...
        private let serializer: S
    
        //...or a type of parameters.
        init(serializer: S) {
            self.serializer = serializer
        }
    
        private let queue = DispatchQueue(label: "Serial")
    
        mutating func accept(chunk: ChunkType, forIndex idx: Int) {
            // One accessor at a time
            queue.sync {
                chunks[idx] = chunk
    
                // Forward zero or more chunks
                while chunks[currentIdx] != nil {
                    serializer.append(chunk: chunks.removeValue(forKey: currentIdx)!)
                    currentIdx += 1
                }
            }
        }
    }