withUnsafeBytes +通用类型行为

时间:2018-11-06 22:25:22

标签: swift pointers generics

我有一个函数,可以使用通用类型从二进制文件读取数字(整数,双精度等)。例如,如果我期望一个Int64,则il将读取8个字节...

// A simple function that read n bytes from a FileHandle and returns
// the data

public func read(chunkSize: Int) -> Data {
   return self.handle!.readData(ofLength: chunkSize)
}

// A function that reads the proper amount of bytes specified
// by the return type which in my case would be an integer 

public func readNumber<I>() -> I? {
   let data: Data = self.read(chunkSize: MemoryLayout<I>.size)
   if data.count == 0 {
       return nil
   }
   return data.withUnsafeBytes { $0.pointee }
}

readNumber无缘无故地随机返回nil。不是来自count检查,而是来自最后一行。

但是当我像这样投射到I时,它完美地起作用了:

return data.withUnsafeBytes { $0.pointee } as I

那是为什么?

编辑:

我使用Playgrounds复制了此内容:

class Test {

    public func read(chunkSize: Int) -> Data {
        return Data(repeating: 1, count: chunkSize)
    }

    public func readNumber<T>() -> T? {
        let data: Data = read(chunkSize: MemoryLayout<T>.size)
        if data.count == 0 {
            return nil
        }
        return data.withUnsafeBytes { $0.pointee }
    }

    public func example() {
        let value2: Double = readNumber()!
        print(value2)
    }
}

let test = Test()

for i in 0..<1000 {
    test.example()
}

1 个答案:

答案 0 :(得分:4)

似乎我需要稍微纠正我的评论。即使Swift始终按照编程方式工作,但是当您遇到一些内存问题(如访问范围超出限制)时,结果似乎也会随机变化。

首先为UnsafePointer准备一个神奇的扩展名:

extension UnsafePointer {
    var printingPointee: Pointee {
        print(Pointee.self) //<- Check how Swift inferred `Pointee`
        return self.pointee
    }
}

并稍微修改您的 EDIT 代码:

class Test {

    public func read(chunkSize: Int) -> Data {
        return Data(repeating: 1, count: chunkSize)
    }

    public func readNumber<T>() -> T? {
        let data: Data = read(chunkSize: MemoryLayout<T>.size)
        if data.count == 0 {
            return nil
        }
        print(T.self) //<- Check how Swift inferred `T`
        return data.withUnsafeBytes { $0.printingPointee }
    }

    public func example() {
        let value2: Double = readNumber()!
        print(value2)
    }
}

let test = Test()

for _ in 0..<1000 {
    test.example()
}

输出:

Double
Optional<Double>
7.748604185489348e-304
Double
Optional<Double>
     

线程1:致命错误:展开包装时意外发现nil   可选值

显示多少对DoubleOptional<Double>似乎是随机的,但是这种现象的原因很明显。

在此行return data.withUnsafeBytes { $0.printingPointee }中,Swift将$0的类型推断为UnsafePointer<Optional<Double>>

在Swift的当前实现中,Optional<Double>在内存中占据9个字节:

print(MemoryLayout<Optional<Double>>.size) //-> 9

因此,$0.pointee从指针开始访问9个字节,尽管指针指向8字节的区域:

|+0|+1|+2|+3|+4|+5|+6|+7|+8|
+--+--+--+--+--+--+--+--+
 01 01 01 01 01 01 01 01 ??
 <-taken from the Data->

您知道,多余的第9个(+8)字节是不可预测的,而且看似随机的,这是nilOptional<Double>的指示。

在您的代码中确实有相同的推断。在您的readNumber<T>()中,返回类型明确声明为T?,因此,在return data.withUnsafeBytes { $0.pointee }行中,Swift将$0.pointee的类型推断为{{ 1}}又称Double?

您知道可以通过添加Optional<Double>来控制此类型推断。