在包含对象引用的Swift结构中维护值语义

在包含对象引用的Swift结构中维护值语义,swift,swift2,Swift,Swift2,我有一个Swift结构,其中包含一个用于内部存储的对象。如何确保结构具有值语义 public struct Times { private let times = NSMutableIndexSet() mutating func addTimeRange(openTime: Int, closeTime: Int) { self.times.addIndexesInRange(NSRange(location: openTime, length: closeT

我有一个Swift结构,其中包含一个用于内部存储的对象。如何确保结构具有值语义

public struct Times {
    private let times = NSMutableIndexSet()

    mutating func addTimeRange(openTime: Int, closeTime: Int) {
        self.times.addIndexesInRange(NSRange(location: openTime, length: closeTime - openTime))
    }
}

存储NSIndexSet而不是NSMutableIndexSet。这正是不可变超类存在的原因

public struct Times {
    private var times = NSIndexSet()
    mutating func addTimeRange(openTime: Int, closeTime: Int) {
        let t = NSMutableIndexSet(indexSet:self.times)
        t.addIndexesInRange(NSRange(location: openTime, length: closeTime - openTime))
        self.times = NSIndexSet(indexSet:t)
    }
}
如果这是一个类而不是一个结构,则可以通过将
times
声明为
@NSCopying
并使用简单赋值,自动执行最后一步:

public class Times {
    @NSCopying private var times = NSIndexSet()
    func addTimeRange(openTime: Int, closeTime: Int) {
        let t = NSMutableIndexSet(indexSet:self.times)
        t.addIndexesInRange(NSRange(location: openTime, length: closeTime - openTime))
        self.times = t // ensure immutable copy
    }
}

可以选择使用Swift的原生
Set
类型,该类型具有内置的值语义,因为它本身就是一个结构

public struct Times {
    private var times = Set<Int>()
    mutating func addTimeRange(openTime: Int, closeTime: Int) {
        (openTime ..< closeTime).map({ index -> Void in self.times.insert(index) })
    }
}

let t1 = Times()
var t2 = t1
t2.addTimeRange(0, closeTime: 3)

println(t1.times) // []
println(t2.times) // [2, 0, 1]
公共结构时间{
私有变量时间=Set()
变异func addTimeRange(openTime:Int,closeTime:Int){
(openTime..Void in self.times.insert(index)})
}
}
设t1=Times()
变量t2=t1
t2.添加时间范围(0,关闭时间:3)
println(t1.次)//[]
println(t2.次)/[2,0,1]

Swift 3更新

SWIFT 3从基础框架中包含了多种类型的价值类型。现在有一个IndexSet结构,它连接到NSIndexSet。内部实施类似于下面的Swift 2解决方案

有关新的基础值类型的更多信息,请参阅:

Swift 2中的旧方法


写时拷贝方法是正确的解决方案。但是,如果只有一个结构实例引用NSMutableIndexSet,则无需创建其副本。Swift提供了一个名为
isuniquelyreferencednobjc()
的全局函数,以确定纯Swift对象是否只被引用一次

由于我们不能将此函数用于Objective-C类,因此需要将NSMutableIndexSet封装在Swift类中

public struct Times {
    private final class MutableIndexSetWrapper {
        private let mutableIndexSet: NSMutableIndexSet

        init(indexSet: NSMutableIndexSet) {
            self.mutableIndexSet = indexSet
        }

        init() {
            self.mutableIndexSet = NSMutableIndexSet()
        }
    }

    private let times = MutableIndexSetWrapper()

    mutating func addTimeRange(openTime: Int, closeTime: Int) {
        // Make sure our index set is only referenced by this struct instance
        if !isUniquelyReferencedNonObjC(&self.times) {
            self.times = MutableIndexSetWrapper(indexSet: NSMutableIndexSet(indexSet: self.times.mutableIndexSet))
        }

        let range = NSRange(location: openTime, length: closeTime - openTime)

        self.times.mutableIndexSet.addIndexesInRange(range)
    }
}

关于何时选择结构而不是类的指导原则之一是“该结构存储的任何属性本身都是值类型,它们也应该被复制而不是引用。”。另见,谢谢你,马特。WWDC 2015年讲座“在Swift中构建具有价值类型的更好应用程序”中也提到了这种解决方案,该讲座将于30分钟左右开始。但是,不需要创建可变索引集的不可变副本。所以,最后一行可以是self.times=indexSet。这节省了另一个对象创建。“但是,不必创建可变索引集的不可变副本”,我认为是这样。这并不保证包装的索引集不会被多次引用。这不是问题。这与苹果内部使用的Swift数组、字典等技术相同。有关更多信息,请参阅本文:这正是您在原始问题中提出的问题。如果包装的索引集是一个可变的索引集,那么您试图解决的问题仍然没有解决—它可能在您背后发生变异。解决方案就是我给出的解决方案:使用NSIndexSet.mutableindexetwrapper是一个私有类,包装的NSMutableIndexSet永远不会公开。这就是为什么它不能在你背后改变。我知道这就是你所希望的。但是像隐私这样的事情是契约性的;这种隐私可能会受到侵犯。如果每个人都表现良好,你所做的一切都会起作用——但当然,即使没有隐私,这也是真的!希望和保证是有区别的。你的方法是一种希望。我的方法是一种保证。