假设您有一些结构:
struct Tattoo {
var imageTorso:UIImage?
var imageTorsoURL:URL?
var imageArms:UIImage?
var imageArmsURL:URL?
}
struct Player {
var name:String = ""
var tattoos:[Tattoo] = []
}
struct Team {
var name:String = ""
var players:[Player] = []
}
现在想象一下,你有一个方法,它与一些玩家在团队价值中传递。你必须通过玩家和他们的纹身进行迭代,然后下载图像并将它们添加到纹身上的图像变量中。
如果您使用for in
循环,那么它将无法正常工作,因为循环的每个部分都会获得副本数组成员的迭代过度。即:
for player in team.players {
for tattoo in player.tattoos {
if let url = tattoo.imageTorsoURL {
MyNetFramework.requestImage(from: url, completion: { image in
tattoo.imageTorso = image
}
}
}
}
在完成所有迭代和完成块之后,原始team
变量仍然与执行任何此操作之前的变量相同。因为内圈得到的每个纹身都是玩家纹身阵中的副本。
现在我知道你可以使用&
在Swift中通过引用传递结构但是它非常沮丧。我也知道你可以使用inout
这样他们在进入函数时就不会被复制,这也是不鼓励的。
我也知道这些可以成为类来避免这种行为。
但是假设我在这个问题上没有选择 - 它们是结构 - 似乎唯一的方法就是这样:
for p in 0...team.players.count-1 {
for t in 0...team.players[p].tattoos.count-1 {
if let url = team.players[p].tattoos[t].imageTorsoURL {
MyNetFramework.requestImage(from: url, completion: { image in
team.players[p].tattoos[t].imageTorso = image
}
}
}
}
这感觉很丑陋和尴尬,但是我不知道如何解决这个问题,for in
循环给你一个你正在迭代的东西的副本。
任何人都可以启发我,或者这只是它在Swift中是什么?
答案 0 :(得分:2)
我认为您已经明白了这一点:“当您的要求修改数据时,最好使用class
。”
这是您的问题参考链接。 Why choose struct over class
struct
很快,您可以使用它们来防止创建一个庞大,凌乱的类。 struct
提供了不可变的功能,让我们更容易关注Function Programming
不可变数据的最大好处是没有竞争条件和死锁。这是因为您只读取数据而不用担心因更改数据而导致的问题。
但是,要回答你的问题,我几乎没有办法解决它。
// First, we need to add constructors for creating instances more easier.
struct Tattoo {
var imageTorso:UIImage?
var imageTorsoURL:URL?
var imageArms:UIImage?
var imageArmsURL:URL?
init(imageTorso: UIImage? = nil, imageTorsoURL: URL? = nil, imageArms: UIImage? = nil, imageArmsURL: URL? = nil) {
self.imageTorso = imageTorso
self.imageTorsoURL = imageTorsoURL
self.imageArms = imageArms
self.imageArmsURL = imageArmsURL
}
}
struct Player {
var name:String
var tattoos:[Tattoo]
init() {
self.init(name: "", tattoos: [])
}
init(name: String, tattoos: [Tattoo]) {
self.name = name
self.tattoos = tattoos
}
}
struct Team {
var name:String
var players:[Player]
init() {
self.init(name: "", players: [])
}
init(name: String, players: [Player]) {
self.name = name
self.players = players
}
}
for player in team.players {
for tattoo in player.tattoos {
if let url = tattoo.imageTorsoURL {
// Catch old UIImage for matching which Tattoo need to be updated.
({ (needChangeImage: UIImage?) -> Void in
MyNetFramework.requestImage(from: url, completion: { image in
// Reconstruct whole team data structure.
let newPlayers = team.players.map { (player) -> Player in
let newTattos = player.tattoos.map { (tattoo) -> Tattoo in
if needChangeImage == tattoo.imageTorso {
return Tattoo(imageTorso: image)
} else {
return tattoo
}
}
return Player(name: player.name, tattoos: newTattos)
}
team = Team(name: team.name, players: newPlayers)
})
})(tattoo.imageTorso)
}
}
}
这些代码很难看,对吧?并且每个网络响应通过整个数据不仅会导致严重的性能问题;另一个问题是可能导致保留周期。
重新设计您的数据结构,并使用Kingfisher帮助我们同步下载图片。
Kingfisher是有用的第三方库。它提供了简洁易用的方法,而且非常灵活。
let url = URL(string: "url_of_your_image")
imageView.kf.setImage(with: url)
但是,如果您不想使用Kingfisher
,我认为最好的方法是将您的声明从struct
更改为class
。
答案 1 :(得分:1)
不幸的是struct
的性质,而Swift并没有提供一种方法,可以在迭代时就地修改集合。但是,在迭代时,用户enumerated()
可以同时获取索引和元素:
for (p, player) in team.players.enumerated() {
for (t, tattoo) in player.tattoos.enumerated() {
if let url = tattoo.imageTorsoURL {
MyNetFramework.requestImage(from: url, completion: { image in
team.players[p].tattoos[t].imageTorso = image
}
}
}
}