在Swift

时间:2017-07-25 22:38:57

标签: swift struct collections

假设您有一些结构:

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中是什么

2 个答案:

答案 0 :(得分:2)

我认为您已经明白了这一点:“当您的要求修改数据时,最好使用class。” 这是您的问题参考链接。 Why choose struct over class

struct很快,您可以使用它们来防止创建一个庞大,凌乱的类。 struct提供了不可变的功能,让我们更容易关注Function Programming

不可变数据的最大好处是没有竞争条件和死锁。这是因为您只读取数据而不用担心因更改数据而导致的问题。

但是,要回答你的问题,我几乎没有办法解决它。

1。更新整个数据。

// 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)
        }
    }
}

这些代码很难看,对吧?并且每个网络响应通过整个数据不仅会导致严重的性能问题;另一个问题是可能导致保留周期。

2。不要在数据数组中保存UIImage。

重新设计您的数据结构,并使用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
            }
        }
    }
}