Swift 中动手封装字节
今天,我想尝试封装 不过,我希望以更加 Swift 的方式来实现,有点类似 C 语言风格,实现迅速且不会引入任何相关性,解码器(decoder)也非常简单,可以在任何平台上实现。 PointerEncoder我们将在 struct PointEncoder { // 解码过程中,如果我们得到一个相当大的数值,可以假定为服务器拒绝或是损坏的数据 static let MaxPoints = 1_310_719 // 容量大小 private static let _sizeOfCount = sizeof(Int64.self) // 一个点由两个 Float32 类型数据组成,所占内存大小如下 private static let _sizeOfPair = 2 * sizeof(Float32.self) static func encodePoints(points: [CGPoint]) -> NSData? { static func decodePoints(data: NSData) -> [CGPoint] } 点数组的最大容量 接下来,我们需要获取以上类型所占内存大小。计算公式非常简单,一旦明确了不同类型所占内存的大小,就能集中在一个地方定义它们,而不是分散在各地调用 编码(encoding)下面让我们看看 guard !points.isEmpty && points.count < MaxPoints else { return nil } // 缓存区的最大容量值 let bufferLength = _sizeOfCount + (points.count * _sizeOfPair) precondition(bufferLength >= (_sizeOfCount + _sizeOfPair),"Empty buffer?") precondition(bufferLength < megabytes(10),"Buffer would exceed 10MB") 第一步确保编码内容不为空,且不超过容量最大值。 第二步计算缓存区的大小,不宜过大或过小。注意第一步中的 以上是我喜欢进行的额外安全检查之一,主要考虑到一些二把刀程序员的尿性。试想之后有人了重构代码,并意外引入一个错误,但优秀的程序员不太可能会删除 let rawMemory = UnsafeMutablePointer<Void>.alloc(bufferLength) // Failed to allocate memory guard rawMemory != nil else { return nil } 下一步开始真正创建缓存区,一旦创建失败就跳出。 控制程序应对内存不足的情况非常困难。如果是因为内存不足造成创建类实例失败,程序应该调用 考虑另外一种情况,分配大的缓存区有会可能失败,但堆碎片(heap fragmentation)可能还存在额外可用的内存。因此,如何优雅地处理它是一门学问(尤其像在 iOS 这种受限的环境中)。 UnsafeMutablePointer<Int64>(rawMemory).memory = Int64(points.count) let buffer = UnsafeMutablePointer<Float32>(rawMemory + sizeOfCount) 这里有一点要注意。等式右边将 等式左侧将 最后,我们将指针偏移 8 个字节(正如前面所说的),指针指向缓存区首地址。 for (index,point) in points.enumerate() { let ptr = buffer + (index * 2) // Store the point values. ptr.memory = Float32(point.x) ptr.advancedBy(1).memory = Float32(point.y) } 接下来进行遍历 因此,通过对基址进行 接着我使用了 return NSData( bytesNoCopy: rawMemory,length: bufferLength,deallocator: { (ptr,length) in ptr.destroy(length) ptr.dealloc(length) }) 最后要注意的,我们将数据返回给调用者。此时已经分配了一个合适的缓存区,接着使用 为什么要传递一个用作释放的闭包参数(deallocator)呢?从技术上讲,你或许可以使用 如果我们的缓存区恰巧需要存储一些复杂的 Swift 类型,适时的释放操作是必须的:你必须调用 解码(decoding)guard data.bytes != nil && data.length > (_sizeOfCount + _sizeOfPair) else { return [] } 首先,我们确保 let rawMemory = data.bytes let buffer = rawMemory + _sizeOfCount // 从内存中获取到 Int64 类型的点个数 let pointCount64 = UnsafePointer<Int64>(rawMemory).memory precondition( Int64(MaxPoints) < Int64(Int32.max),"MaxPoints would overflow on 32-bit platforms") precondition( pointCount64 > 0 && pointCount64 < Int64(MaxPoints),"Invalid pointCount = (pointCount64)") let pointCount = Int(pointCount64) 接下来设置我们的指针。再次将原始指针强制转换成 Int64 类型的指针,此时我们使用了非可变指针,这是出于只读操作的考虑。 注意到前面代码中我将点个数类型设置为 64 位,这样确保了 末行代码将点个数转换成 64 位平台上,绝对有可能超过 4GB 容量点数组的情况(数值超过大约42亿),代码需要进一步重构。不过对于我的需求来说无关紧要,所以这里采用了硬编码限制了容量。这也使得在 64 位系统上创建的值无法加载到 32 位系统当中(这只是理论上最大值的情况,实际我所使用的容量将会小得多)。 var points: [CGPoint] = [] points.reserveCapacity(pointCount) for ptr in (0..<pointCount).map({ UnsafePointer<Float32>(buffer) + (2 * $0) }) { points.append( CGPoint( x: CGFloat(ptr.memory),y: CGFloat(ptr.advancedBy(1).memory)) ) } return points 代码也很简单。我们设定数组的备用容量,以避免重新分配。这不会对性能造成太大影响,毕竟我们已经知道了最大限制容量,所以这么做没什么问题。 另外,指针类型为 关于测试毫无疑问,类似这种类型都应该使用 我从来不敢 100% 保证代码中任何有关线程或内存的部分不会出现纰漏。我甚至无法 100% 确定本文用例没有 bug。不过我使用 包括你在内,没有人优秀到写代码可以完全避免缓冲区溢出。 总结Swift 编译器始终重视安全问题,但它有时也令人心寒。如果你保证不做一些调皮的事情,它会完全信任你。如果你有必要做一些字节或 最终实现我已经在最终实现的用例 gist 中嵌入了要点和详细注释。如果对你有帮助的话,请尽情使用它。 // Written by Russ Bishop // MIT licensed,use freely. // No warranty,not suitable for any purpose. Use at your own risk! struct PointEncoder { // When parsing if we get a wildly large value we can // assume denial of service or corrupt data. static let MaxPoints = 1_310_719 // How big an Int64 is private static let _sizeOfCount = sizeof(Int64.self) // How big a point (two Float32s are) private static let _sizeOfPair = 2 * sizeof(Float32.self) static func encodePoints(points: [CGPoint]) -> NSData? { guard !points.isEmpty && points.count < MaxPoints else { return nil } // Total size of the buffer let bufferLength = _sizeOfCount + (points.count * _sizeOfPair) precondition(bufferLength >= (_sizeOfCount + _sizeOfPair),"Empty buffer?") precondition(bufferLength < megabytes(10),"Buffer would exceed 10MB") let rawMemory = UnsafeMutablePointer<Void>.alloc(bufferLength) // Failed to allocate memory guard rawMemory != nil else { return nil } // Store the point count in the first portion of the buffer UnsafeMutablePointer<Int64>(rawMemory).memory = Int64(points.count) // The remaining bytes are for the Float32 pairs let buffer = UnsafeMutablePointer<Float32>(rawMemory + _sizeOfCount) // Store the points for (index,point) in points.enumerate() { // Since buffer is UnsafeMutablePointer<Float32>,addition counts // the number of Float32s,*not* the number of bytes! let ptr = buffer + (index * 2) // Store the point values. ptr.memory = Float32(point.x) ptr.advancedBy(1).memory = Float32(point.y) } // We can tell NSData not to bother copying memory. // For consistency and since we can't guarantee the memory allocated // by UnsafeMutablePointer can just be freed,we provide a deallocator // block. return NSData( bytesNoCopy: rawMemory,length) in // If ptr held more complex types,failing to call // destroy will cause lots of leakage. // No one wants leakage. ptr.destroy(length) ptr.dealloc(length) }) } static func decodePoints(data: NSData) -> [CGPoint] { // If we don't have at least one point pair // and a size byte,bail. guard data.bytes != nil && data.length > (_sizeOfCount + _sizeOfPair) else { return [] } let rawMemory = data.bytes let buffer = rawMemory + _sizeOfCount // Extract the point count as an Int64 let pointCount64 = UnsafePointer<Int64>(rawMemory).memory // Swift is safer than C here; you can't // accidentally overflow/underflow and not // trigger a trap,but I am still checking // to provide better error messages. // In all cases,better to kill the process // than corrupt memory. precondition( Int64(MaxPoints) < Int64(Int32.max),"MaxPoints would overflow on 32-bit platforms") precondition( pointCount64 > 0 && pointCount64 < Int64(MaxPoints),"Invalid pointCount = (pointCount64)") // On 32-bit systems this would trap if // MaxPoints were too big and we didn't // check above. let pointCount = Int(pointCount64) precondition( _sizeOfPair + (_sizeOfCount * pointCount) <= data.length,"Size lied or buffer truncated") var points: [CGPoint] = [] // Small optimization since // we know the array size points.reserveCapacity(pointCount) for ptr in (0..<pointCount).map({ // buffer points past the size header // Again,since the pointer knows we are // counting Float32 values we want the // number of Float32s,*not* their size // in bytes! UnsafePointer<Float32>(buffer) + (2 * $0) }) { points.append( CGPoint( x: CGFloat(ptr.memory),y: CGFloat(ptr.advancedBy(1).memory)) ) } return points } } func kilobytes(value: Int) -> Int { return value * 1024 } func megabytes(value: Int) -> Int { return kilobytes(value * 1024) } func gigabytes(value: Int) -> Int { return megabytes(value * 1024) }
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |