【发布时间】:2018-10-26 00:54:21
【问题描述】:
我对 guard let 语句有疑问,它的行为很奇怪。整个代码如下。即使 readData 和 sizeOfData 不是 nil,方法 readActivity(subdata: Data) 中的其他语句块 guard let data = readData, let size = sizeOfData else ... 也会错误地执行。
代码
import Foundation
enum ActivityDataReaderError: Error {
case activityIsReadingOtherCentral
case bluetooth(Error?)
case staleData
}
protocol ActivityDataReaderDelegate: class {
func didReadActivity(data: Data)
func didFailToReadActivity(error: ActivityDataReaderError)
}
final class ActivityDataReader {
private var sizeOfData: Int?
private var isOtherDeviceReading: Bool {
// 0xFFFF
return sizeOfData == 65535
}
private var readData: Data?
var isEmpty: Bool {
return sizeOfData == nil
}
weak var delegate: ActivityDataReaderDelegate?
static func timestampValue(_ timestamp: UInt32) -> Data {
var value = timestamp
return Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
func reset() {
readData = nil
sizeOfData = nil
NSLog("reset() -- \(Thread.current)")
}
func readActivity(data: Data?, error: Error? = nil) {
guard let data = data else {
delegate?.didFailToReadActivity(error: .bluetooth(error))
return
}
let isFirstChunk = readData == nil
if isFirstChunk {
let sizeData = data.subdata(in: 0..<2)
sizeOfData = sizeData.withUnsafeBytes { $0.pointee }
guard !isOtherDeviceReading else {
delegate?.didFailToReadActivity(error: .activityIsReadingOtherCentral)
return
}
NSLog(String("readActivity() Size of data: \(String(describing: sizeOfData))"))
let subdata = data.subdata(in: 2..<data.count)
readActivity(subdata: subdata)
} else {
readActivity(subdata: data)
}
}
private func readActivity(subdata: Data) {
if let lastReadData = readData {
readData = lastReadData + subdata
} else {
readData = subdata
}
guard let data = readData, let size = sizeOfData else {
NSLog("WTF? data:\(String(describing: readData)), "
+ "sizeOfData: \(String(describing: sizeOfData)), "
+ "thread: \(Thread.current)")
assertionFailure("WTF")
return
}
NSLog("subdata: \(String(describing: subdata)), "
+ "totalReadBytes: \(data.count), "
+ "size: \(size)")
if data.count == size {
delegate?.didReadActivity(data: data)
reset()
}
}
}
测试
由于assertionFailure("WTF"),有时会通过有时会崩溃的测试。
class ActivityDataServiceReaderTests: XCTestCase {
var service: ActivityDataReader?
override func setUp() {
super.setUp()
service = ActivityDataReader()
}
override func tearDown() {
service = nil
super.tearDown()
}
func testBufferIsNotEmpty() {
NSLog("testBufferIsNotEmpty thread: \(Thread.current)")
guard let service = service else { fatalError() }
let firstDataBytes = [UInt8.min]
let data1 = Data(bytes: [7, 0] + firstDataBytes)
service.readActivity(data: data1)
XCTAssertFalse(service.isEmpty)
service.reset()
XCTAssertTrue(service.isEmpty)
}
}
崩溃时的控制台日志
2018-10-25 14:53:30.033573+0200 GuardBug[84042:11188210] WTF? data:Optional(1 bytes), sizeOfData: Optional(7), thread: <NSThread: 0x600003399d00>{number = 1, name = main}
环境
- Xcode10
- 带有旧版构建系统的 swift 4.1
- swift 4.2
在我看来,在方法 readActivity(subdata: Data) 的 guard let else 块中执行 else 块中的代码是不可能的。一切都在主线程上运行。我错过了什么吗?怎么可能有时测试通过有时崩溃?
感谢您的帮助。
编辑:
guard let + data.withUnsafeBytes 更窄的问题:
func testGuardLet() {
let data = Data(bytes: [7, 0, UInt8.min])
let sizeData = data.subdata(in: 0 ..< 2)
let size: Int? = sizeData.withUnsafeBytes { $0.pointee }
guard let unwrappedSize = size else {
NSLog("failure: \(size)")
XCTFail()
return
}
NSLog("success: \(unwrappedSize)")
}
日志:
2018-10-25 16:32:19.497540+0200 GuardBug[90576:11351167] failure: Optional(7)
【问题讨论】:
-
尝试选择性地分别包装每个值,并在发生崩溃时检查每个值的日志
-
欢迎来到 StackOverflow。请在您的问题中仅包含相关代码。不要只是转储整个课程,并让人们阅读与您所要求的内容无关的代码。请阅读minimal reproducible example 和edit 你的问题以显示演示问题的最小代码。
-
我在这里看到的唯一可能导致类似您所描述的事情的事情似乎是 sizeOfBytes 的竞争条件。当您在
readActivity(data:error:)中分配给它时,您正在使用闭包。我建议在didSet中为 sizeOfBytes 写一条日志语句,看看这是否真的是原因。 -
@jlowe didSet 在主线程上被调用,所以这不是由竞争条件引起的。
-
@Tobi @jlowe 当我尝试分别解开每个值时,问题在于
sizeOfData。这导致执行else块,但在日志中我可以看到它的值不是nil