Swift - 向下转换一系列协议以使用unsafeBitCast致命错误键入崩溃

时间:2015-09-01 15:36:58

标签: ios swift swift2

我正在尝试投射一个协议类型的“过滤”数组

我有一系列符合几种不同协议的结构(评估,级别和门) - 可分级,可重复和可测试:

protocol Stageable
{
    var index : Int { get }
    var steps : [Step] { get }
}

protocol Testable
{
    var threshold : Float { get }
}

protocol Repeatable
{
    var sessions: Int { get }
}

struct Gate : Stageable, Testable, Repeatable
{
    private(set) var index : Int
    private(set) var steps : [Step]

    private(set) var threshold : Float

    private(set) var sessions : Int
}

struct Assessment : Stageable, Testable
{
    private(set) var index : Int
    private(set) var steps : [Step]

    private(set) var threshold : Float
}

struct Level : Stageable, Repeatable
{
    private(set) var index : Int
    private(set) var steps : [Step]

    private(set) var sessions : Int
}

Step是另一个结构。没有使用任何课程。

这些结构在添加到数组之前就已填充。 数组通常采用[Assessment,Gate,Level,Level]的形式。所有结构数据都是从XML文件中填充的。

在某些情况下,我只想查看数组中的“级别”,所以我这样做:

// stages = [Assessment, Gate, Level, Level, ...]
let levels = stages.filter{ $0 is Level }

如果我查询这个,它看起来很好,例如levels.count是我的期望。 但是,如果我现在想要将数组转换为[Level],它会崩溃并出现以下错误:

fatal error: can't unsafeBitCast between types of different sizes

这是因为我从协议转换为结构类型吗?我也觉得我已经错过了协议的主要好处,必须有更好的方法来做到这一点。

目前正在使用Xcode 7 beta 5.

2 个答案:

答案 0 :(得分:6)

转换结构数组是有问题的,因为结构是值类型。这意味着结构数组的每个元素占用内存中结构的大小。这对于标准对象的数组是不同的,因为它们通过引用传递。对象数组中的每个元素都是一个引用(指向特定内存区域的指针)。

为了证明这一点,请考虑以下

class ABC {
    private var i = 0
    private var j = 1
    private var k = 2
}

print(sizeof(UIViewController))
print(sizeof(UIImage))
print(sizeof(NSObject))
print(sizeof(ABC))

每个print语句在我的平台上输出8,这是内存地址的大小(显然不同于此类实例占用的内存量)。

另一方面,当我从您的问题中获取代码并执行

print(sizeof(Stageable))
print(sizeof(Level))

我分别得到4024,它们是内存中这些结构的实例大小。这意味着类型[Stageable]的数组由40字节元素的块组成,而类型[Level]的数组由24字节元素的块组成。因此,您无法在这些数组类型之间进行转换,因为这需要重写数组的内存。

作为替代方法,您可以使用map方法强制进行类型转换:

let levels = stages.filter({ $0 is Level }).map({ $0 as! Level })

通过利用flatMap方法:

,也可以简化上述内容
let levels = stages.flatMap({ $0 as? Level })

答案 1 :(得分:0)

好吧,当你执行这段代码时:

let levels = stages.filter{ $0 is Level }

您的levels类型变为[Stageable]。现在,要将[Stageable]转换为[Level],您可以使用以下代码:

var l = levels.map{ $0 as! Level }