将代码从 Objective-C 转换为 Swift 后测试用例失败

Posted

技术标签:

【中文标题】将代码从 Objective-C 转换为 Swift 后测试用例失败【英文标题】:Testcase failed after converting codes from Objective-C to Swift 【发布时间】:2016-04-16 09:22:22 【问题描述】:

我正在用 Swift 风格做一些按位运算,这些代码最初是用 Objective-C/C 编写的。我使用UnsafeMutablePointer 声明内存地址的起始索引,并使用UnsafeMutableBufferPointer 访问范围内的元素。

你可以访问原始的Objective-C文件Here.

public init(size: Int) 
    self.size = size
    self.bitsLength = (size + 31) / 32
    self.startIdx = UnsafeMutablePointer<Int32>.alloc(bitsLength * sizeof(Int32))
    self.bits = UnsafeMutableBufferPointer(start: startIdx, count: bitsLength)


/**
 * @param from first bit to check
 * @return index of first bit that is set, starting from the given index, or size if none are set
 *  at or beyond its given index
 */
public func nextSet(from: Int) -> Int 
    if from >= size  return size 
    var bitsOffset = from / 32
    var currentBits: Int32 = bits[bitsOffset]
    currentBits &= ~((1 << (from & 0x1F)) - 1).to32
    while currentBits == 0 
        if ++bitsOffset == bitsLength 
            return size
        
        currentBits = bits[bitsOffset]
    
    let result: Int = bitsOffset * 32 + numberOfTrailingZeros(currentBits).toInt
    return result > size ? size : result


func numberOfTrailingZeros(i: Int32) -> Int 
    var i = i
    guard i != 0 else  return 32 
    var n = 31
    var y: Int32
    y = i << 16
    if y != 0  n = n - 16; i = y 
    y = i << 8
    if y != 0  n = n - 8; i = y 
    y = i << 4
    if y != 0  n = n - 4; i = y 
    y = i << 2
    if y != 0  n = n - 2; i = y 
    return n - Int((UInt((i << 1)) >> 31))

测试用例:

func testGetNextSet1() 
    // Passed
    var bits = BitArray(size: 32)
    for i in 0..<bits.size 
        XCTAssertEqual(32, bits.nextSet(i), "\(i)")
    
    // Failed
    bits = BitArray(size: 34)
    for i in 0..<bits.size 
        XCTAssertEqual(34, bits.nextSet(i), "\(i)")
    

谁能指导我为什么第二个测试用例失败但objective-c版本通过?

编辑:正如@vacawama 所说:如果您将 testGetNextSet 分成 2 个测试,则都通过。

Edit2:当我使用xctool 运行测试时,调用BitArraynextSet() 的测试在运行时会崩溃。

【问题讨论】:

.toInt.to32 是如何实现的? @vacawama 是 Int public var to32:Int32{return Int32(truncatingBitPattern:self)}的扩展 这个说法很麻烦:return n - Int((UInt((i &lt;&lt; 1)) &gt;&gt; 31))。如果i 为负数,则会崩溃。 【参考方案1】:

Objective-C 版本的numberOfTrailingZeros

// Ported from OpenJDK Integer.numberOfTrailingZeros implementation
- (int32_t)numberOfTrailingZeros:(int32_t)i 
    int32_t y;
    if (i == 0) return 32;
    int32_t n = 31;
    y = i <<16; if (y != 0)  n = n -16; i = y; 
    y = i << 8; if (y != 0)  n = n - 8; i = y; 
    y = i << 4; if (y != 0)  n = n - 4; i = y; 
    y = i << 2; if (y != 0)  n = n - 2; i = y; 
    return n - (int32_t)((uint32_t)(i << 1) >> 31);

在翻译numberOfTrailingZeros 时,您将返回值从Int32 更改为Int。没关系,但函数的最后一行在您翻译时无法正常运行。

numberOfTrailingZeros 中,替换为:

return n - Int((UInt((i << 1)) >> 31))

有了这个:

return n - Int(UInt32(bitPattern: i << 1) >> 31)

转换为UInt32 会删除除低 32 位之外的所有位。由于您正在投射到UInt,因此您并没有删除这些位。有必要使用bitPattern 来实现这一点。

【讨论】:

看起来像 XCTAssertEqual failed: ("Optional(34)") is not equal to ("Optional(32)") 显示了 34 次。 如果我在 64 位设备(iPhone 5S 及更高版本)上测试,它对我有用。您能否更新您的问题以显示您的所有代码(包括.toInt.to32 的实现以及BitArray 实现的其余部分。也许我做的不同。 我刚刚上传了一个示例 repo here,请查看更多信息。 首先,testGetNextSet 中的第二个断言不应该是 XCTAssertEqual(34, array.nextSet(i), "\(i)") 吗?为什么引用bitArray。循环也是如此。但即使有这种变化testGetNextSet 也会失败。奇怪的。如果您将 testGetNextSet 分成 2 个测试,则都通过。 没错,我只是打错字了,抱歉这个愚蠢的错误。【参考方案2】:

最后我发现startIdx只需要在分配后初始化。

self.startIdx = UnsafeMutablePointer<Int32>.alloc(bitsLength * sizeof(Int32))
self.startIdx.initializeFrom(Array(count: bitsLength, repeatedValue: 0))

或者使用calloc,只需一行代码:

self.startIdx = unsafeBitCast(calloc(bitsLength, sizeof(Int32)), UnsafeMutablePointer<Int32>.self)

此外,我使用lazy varUnsafeMutableBufferPointer 的初始化推迟到第一次使用该属性。

lazy var bits: UnsafeMutableBufferPointer<Int32> = 
   return UnsafeMutableBufferPointer<Int32>(start: self.startIdx, count: self.bitsLength)
()

另一方面,别忘了deinit

deinit 
    startIdx.destroy()
    startIdx.dealloc(bitsLength * sizeof(Int32))

【讨论】:

以上是关于将代码从 Objective-C 转换为 Swift 后测试用例失败的主要内容,如果未能解决你的问题,请参考以下文章

将json调用语法从Objective-C转换为Swift的正确方法是啥?

再次将 textKit 从 Objective-C 转换为 Swift

将UILabel子类从Objective-C转换为Swift

如何将 .hour() 和 .minute() 从 Swift 转换为 Objective-C?

将方法从 Objective-C 转换为 Swift 时的区别

有没有工具可以自动将简单的 .xib 转换为 Objective-C 代码?