I was advised to use structs as much as possible in Swift, because so many things are structs already (Int, String, Array, Dictionary, almost everything is).
But if I have a relationship (with a reverse) to deal with, I couldn't come up with a satisfying solution, especially in the case of datasource for an UITableView. An example out of many: a Game object with multiple Player, and each Player has multiple Game.
approach one
I store all the instances of the same struct in a list, I give an id to each instance, and I fetch the lists every time I need related objects.
var allGames: [Game] = []
struct Game {
let id: String
var playerIds: [String]
var players: [Player] {
get { return allPlayers.filter({ playerIds.contains($0.id) }) }
}
}
var allPlayers: [Player] = []
struct Player {
let id: String
var gameIds: [String]
var games: [Game] {
get { return allGames.filter({ gameIds.contains($0.id) }) }
}
}
approach two
I move away from struct.
class Game {
var players: [Player] = []
}
class Player {
var games: [Game] = []
}
question
How to deal with relationships with structs in Swift? Is one of the two approaches above a better way than the other, or is there an even better way?
You were told to use structs instead of classes? You've been had. Or you just didn't understand whatever advice you got properly.
structs are value types. Classes are reference types. That means you can't have references to structs. Two structs = two different objeccts = twice the memory. You can have references to class instances.
Say you have 1000 players all playing the same game. With structs, you have 1000 copies of the game. Worse, the game would have a copy of each player, which each would have a copy of the game, which would have a copy of each player, which each would have a copy of the game, and so on forever.
That's why you make Player and Game classes.
Related
I have a Swift Combine question. Let’s say I have an ObservableObject with a few properties like this:
class AppState: ObservableObject{
static let shared = AppState()
#Published var a: Object?
#Published var b: Object?
#Published var c = [Object]()
}
I know I can be notified if a single object changes like this:
myCancellable = AppState.shared.$a.sink { a in
//Object 'a' changed
}
But is there a way to watch multiple properties and respond if any of them change?
Something like:
myCancellable = AppState.shared.[$a, $b, $c].sink { a, b, c in
//Objects 'a', 'b', or 'c' changed
}
The possible variant is to observe any published changes of entire object, like
myCancellable = AppState.shared
.objectWillChange.sink {
// react here
}
Assuming that a, b, and c are the same type (which Asperi pointed out to me they are not in the example, since c is an array), you can use Publishers.MergeMany:
myCancellable = Publishers.MergeMany([$a,$b,$c]).sink { newValue in
}
There are also more specific versions of this function for a set number of arguments. For example, in your case, you could use Publishers.Merge3
Often, in examples I've seen online, you'll see Publishers.MergeMany followed by collect(), which gathers results from all of the publishers before moving on, publishing the results as an array. From your question, though, it doesn't sound like this will meet your needs, as you want a notification each time a singular member changes.
Thanks for the great answers. Another idea I stumbled across was to just store my objects in a struct and observe that.
struct Stuff{
var a: Object?
var b: Object?
var c = [Object]()
}
Then in my AppState class:
class AppState: ObservableObject{
static let shared = AppState()
#Published var stuff: Stuff!
}
Then in the publisher:
myCancellable = AppState.shared.$stuff.sink { stuff in
print(stuff.a)
print(stuff.b)
print(stuff.c)
}
I like this approach since I don't want to observe everything that might change in the AppState class (which is probably an indication I should break it into smaller pieces). This seems to be working well so far.
When you have a Realm model that looks something like:
class Thing {
var id: String?
var kids: List<String>
}
And have Thing objects like: Thing(id: "thingyOne", kids: List<"Momo", "Jojo">). You can easily query for objects that have id of "thingyOne" using Realm().objects(Thing.self).filter("id == 'thingyOne'"). How do you check which objects have kids named "Jojo" in their kids list?
I'm currently using this method:
let things = Realm().objects(Thing.self)
for thing in things {
if thing.kids.contains("Jojo") {
// Success
}
}
This makes the app extremely slow because I have thousands of Realm objects. How do you do it the correct way?
I have a base class (Spaceship) that has some variables and an initializer.
I also have a subclass (UFO) that only needs one of the variables of the superclass.
When I try and create a basic UFO object, I get: "missing argument for parameter 'weapon' in call var c = UFO()"
I realize that error message is asking for a weapon parameter, but how do I (and is it even possible?) to create a basic UFO object that doesn't need a weapon parameter?
Have spent hours tinkering with "override", "init" and "super" commands. I've read a lot of the articles here on SO and other sites, but have not seen any examples similar to my situation. Again, I'm assuming that what I want to do is possible to begin with.
class Spaceship {
var isUFO = false
var weapon:String
init(weapon:String) {
self.weapon = weapon
}
func printSomething() {
print("Test")
}
}
class UFO: Spaceship {
}
var a = Spaceship(weapon:"Laser")
a.printSomething() //OUTPUT: Test
var b = UFO(weapon:"")
//This runs, but I don't want to have to fill in parameters
b.printSomething() //OUTPUT: Test
var c = UFO() //Ultimately this is what I want to do
c.printSomething()
//OUTPUT: missing argument for parameter 'weapon' in call var c = UFO()
What you can do is mark weapon as optional String. Also, in init(weapon:) use default value of weapon as nil, i.e
class Spaceship {
var isUFO = false
var weapon: String? //here...
init(weapon: String? = nil) { //here...
self.weapon = weapon
}
func printSomething() {
print("Test")
}
}
Now, you can simply create the object of class UFO both with and without weapon, i.e.
var a = Spaceship(weapon:"Laser")
var b = UFO(weapon:"Another Weapon")
var c = UFO()
First, when using inheritence, you'll have to think about an "is a" relationship: An UFO is a spaceship. So when each spaceship has a weapon, and an UFO is a spaceship, then every UFO also has to have a weapon.
What you could do is make your weapon an optional, but I think this is somehow a mis-usage. Also, re-think your isUFO property:
A spaceship should not make assumptions about special subclasses
You could use the is operator to check the dynamic type of a Spacehip
If you don't want this inheritence behaviour, you might better think about something else, like protocols, where you only specify certain behaviour but no memory layout.
I created an iOS app to arbitrate jujitsu/judo fights. My app has a Fighter class. In this Fighter class I have an opponent property that is of type Fighter as well. So I have something like this:
class Fighter {
...
var opponent: Fighter?
...
}
var fighter1 = Fighter()
var fighter2 = Fighter()
fighter1.opponent = fighter2
fighter2.opponent = fighter1
I declared it as optional but I'm stuck because I don't want it to be optional, a Fighter must have an opponent. It works fine this way but I don't like it and I end up with a lot of unnecessary if let or !. Plus it's ugly.
How can I do that in a better and safer way?
Instead of having a Fighter contain another Fighter, create a Match object, that contains two Fighter objects. A type should rarely contain itself.
For example:
struct Match{
let fighterOne: Fighter
let fighterTwo: Fighter
}
You could declare opponont as implicitly unwrapped optional, but I'd rather suggest creating a Match class or something similar, where you store the two opponents rather than creating a circular dependency between your Fighters. That would also cause a strong dependency cycle unless you declared the property as weak/unowned.
Try something like this:
struct Fight {
let fighter1: Fighter
let fighter2: Fighter
var date: Date
}
struct Fighter {
let name: String
let weight: Float
let category: AnyOtherCustomObjectThatYouWant
}
Using Structs instead of Class you will have the constructors for free
I would like to mutate a property in a Swift struct, stored within an array.
I have done a reassignment dance, but that it doesn't feel right.
I'm encouraged to use Struct's where possible, however this relatively simple use case (below) is pushing me towards using Classes (Reference Types).
Should I be using Classes for Game and/or Player?
Please find below a code sample .. with accompanying UnitTest
Test Summary
• Create a Game
• Create two Players
• Add both Players to Game
• Send message to Game to decrementPlayer
• Game iterates over collection (players)
• Finds player and sends message decrementScore
• Test Failed - Players' scores were not as expected (60 & 70 respectively)
struct Game {
fileprivate(set) var players = [Player]()
}
extension Game {
mutating func addPlayer(_ player: Player) {
players.append(player)
}
mutating func decrementPlayer(_ decrementPlayer: Player, byScore: Int) {
for var player in players {
if player == decrementPlayer {
player.decrementScore(by: byScore)
}
}
}
}
struct Player {
var name: String
var score: Int
init(name: String, score: Int) {
self.name = name
self.score = score
}
mutating func decrementScore(by byScore: Int) {
self.score -= byScore
}
}
extension Player: Equatable {
public static func ==(lhs: Player, rhs: Player) -> Bool {
return lhs.name == rhs.name
}
}
class GameTests: XCTestCase {
var sut: Game!
func testDecrementingPlayerScores_isReflectedCorrectlyInGamePlayers() {
sut = Game()
let player1 = Player(name: "Ross", score: 100)
let player2 = Player(name: "Mary", score: 100)
sut.addPlayer(player1)
sut.addPlayer(player2)
XCTAssertEqual(2, sut.players.count) // Passes
sut.decrementPlayer(player1, byScore: 40)
sut.decrementPlayer(player2, byScore: 30)
XCTAssertEqual(60, sut.players[0].score) // Fails - score is 100 .. expecting 60
XCTAssertEqual(70, sut.players[1].score) // Fails - score is 100 .. expecting 70
}
}
I'm encouraged to use Struct's where possible
Yes, that is problematic. You should be encouraged to use structs where appropriate. Generally speaking, I find that structs aren't always as appropriate as fashion dictates.
Your problem here is that the for var player ... statement actually makes a mutable copy of each player as it iterates and amends the copy. If you want to stick with structs, you'll probably need to adopt a more functional approach.
mutating func decrementPlayer(_ decrementPlayer: Player, byScore: Int) {
players = players.map {
return $0 == decrementPlayer ? $0.scoreDecrementedBy(by: byScore) : $0
}
}
Or a more traditional (and almost certainly more efficient) way would be to find the index of the player you want
mutating func decrementPlayer(_ decrementPlayer: Player, byScore: Int) {
if let index = players.index(of: decrementPlayer)
{
players[index].decrementScore(by: byScore)
}
}
I would like to mutate a property in a Swift struct, stored within an array. I have done a reassignment dance, but that it doesn't feel right.
Well, that's what you would have to do, since structs are not mutable in place. If you want to be able to mutate the object in place (within the array), you do need a class.
The question that should be asked when deciding between value types (structs) and reference types (classes) is if makes sense to have duplicate instances.
For example take number 5, which is an Int. You can copy it as many times, it's cheap (as structs are), and doesn't pose problems if copied.
Now let's consider a FileHandle. Can it be a struct? Yes. Should it be a struct? No. Making it a struct means the handle will get copied whenever passed as argument or stored as property. Meaning everyone will have a different reference to that handle (intentionally ignoring copy-on-write). If one reference holder decides to close the handle, this indirectly invalidate all the other FileHandle references, which will likely trigger unexpected behaviour.
In your particular case, I'd say to make Player, a class. I assume the player will have some UI effects also, so it makes sense to be a reference type. However, if Player is used only for statistical purposes, then make it a struct.