I'm testing the following code in an Xcode Playground.
struct Coordinate: Codable {
var latitude: Double
var longitude: Double
}
struct Landmark: Codable {
var name: String
var foundingYear: Int
var location: Coordinate
}
var MyCoordinate: Coordinate = Coordinate.init(latitude: 30.1, longitude: 10.2)
var LandmarkA: Landmark = Landmark.init(name: "My Landmark", foundingYear: 2017, location: MyCoordinate)
var LandmarkB: Landmark = Landmark.init(name: "My Landmark 2", foundingYear: 2016, location: MyCoordinate)
LandmarkA.location.latitude = 12.1
print (LandmarkA.location.latitude) // 12.1
print (LandmarkB.location.latitude) // 30.1
Now if I convert this code to classes instead of structs.
class Coordinate {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
class Landmark {
var name: String
var foundingYear: Int
var location: Coordinate
init(name: String, foundingYear: Int, location: Coordinate) {
self.name = name
self.foundingYear = foundingYear
self.location = location
}
}
var MyCoordinate: Coordinate = Coordinate.init(latitude: 30.1, longitude: 10.2)
var LandmarkA: Landmark = Landmark.init(name: "My Landmark", foundingYear: 2017, location: MyCoordinate)
var LandmarkB: Landmark = Landmark.init(name: "My Landmark 2", foundingYear: 2016, location: MyCoordinate)
LandmarkA.location.latitude = 12.1
print (LandmarkA.location.latitude) // 12.1
print (LandmarkB.location.latitude) // 12.1
Is it possible to get structs to behave the same way as classes in terms of what location is equal to? If I change the coordinate either by doing LandmarkA.location.latitude or MyCoordinate.latitude it should set both LandmarkA and LandmarkB. This behavior works with classes but I can't use Codable in classes and there is less code needed to setup structs.
The last two print statements in the first example I would like to both be 12.1 and the same since it's both referencing the same object.
This is the only thing I can't figure out with structs. structs have everything else I need, Codeable, functions within the struct, minimal setup code, etc.
I would really prefer to use structs but if this isn't possible I will probably settle for using classes.
Any thoughts of how to achieve this?
Related
I'm trying to get the hang of how to make my code the most efficient using Structs / Classes, and I'm trying to understand it via the following example.
I'd be really grateful if someone could correct me or guide me about the same:
Just as an example, I'll use Harry Potter. There are four houses, and each house has certain characteristics.
So now I have a struct for 2 of them:
struct Gryffindor {
let name = "Gryffindor"
let characteristic = "Brave"
let image = Image("Lion")
}
struct Slytherin {
let name = "Slytherin"
let characteristic = "Cunning"
let image = Image("Snake")
}
Now if I wish to have a wizard struct as follows, but I don't know how to include a House property within, such that when I try to create an instance of a wizard, I can call the properties from their respective houses.
struct Wizard {
let name: String
var house: ?
}
let harryPotter = Wizard(name: "Harry", house: Gryffindor)
Basically, I wish to be able to refer to harry's house using the harryPotter instance, as such:
print(harryPotter.characteristic) //should print "Brave"
Is what I'm trying to achieve even possible?
First of all you are mixing types with objects so you should have a type House
struct House {
let name: String
let characteristic: String
let image: Image
}
And then use that in the Wizard struct
struct Wizard {
let name: String
var house: House
}
And now you create first a House object for the Wizard and then the Wizard object
let gryffindor = House(name: "Gryffindor", characteristic: "Brave", image: Image("Lion"))
let harryPotter = Wizard(name: "Harry", house: gryffindor)
or all in one call
let harryPotter = Wizard(name: "Harry",
house: House(name: "Gryffindor", characteristic: "Brave", image: Image("Lion")))
Use protocol & generics, like below. Tested with Xcode 11.4.
protocol House {
var name: String { get }
var characteristic: String { get }
var image: Image { get }
}
struct Gryffindor: House {
let name = "Gryffindor"
let characteristic = "Brave"
let image = Image("Lion")
}
struct Wizard<H: House> {
let name: String
var house: H
}
let harryPotter = Wizard(name: "Harry", house: Gryffindor())
This question already has answers here:
Is Swift Pass By Value or Pass By Reference
(10 answers)
Swift can change struct declared with let if using an index but not if using a loop
(3 answers)
Closed 3 years ago.
If I loop through an array of Class objects I can make changes to a property on it
class Country {
var name: String?
var region: String?
init(name: String?, region: String?) {
self.name = name
self.region = region
}
}
let canada = Country(name: "Canada", region: "North America")
let mexico = Country(name: "Mexico", region: "North Ameria")
let france = Country(name: "France", region: "Europe")
let korea = Country(name: "Korea", region: "Asia")
var countryArr = [canada, mexico, france, korea]
// this works fine
let transformed = countryArr.map { $0.name = "Random" }
But if I try this with Struct objects I get
Cannot assign to property: '$0' is immutable
struct Country {
var name: String?
var region: String?
}
var canada = Country(name: "Canada", region: "North America")
var mexico = Country(name: "Mexico", region: "North Ameria")
var france = Country(name: "France", region: "Europe")
var korea = Country(name: "Korea", region: "Asia")
var countryArr = [canada, mexico, france, korea]
// this gets an error
let transformed = countryArr.map { $0.name = "Random" }
The issue is caused by the fact that structs are value types, so mutating any properties of the struct mutates the struct instance itself as well and the closure input arguments in map are immutable. So when you try to mutate a property $0 in the closure of map, you are trying to mutate $0 itself in case map is called on a collection of value types.
On the other hand, classes are reference types, so mutating a property of a class instance doesn't mutate the instance itself.
A solution for your problem is to create a mutable copy of the struct instance in the map, mutate its name property and return that. There are two solutions, if you have a small number of properties on your type, calling its memberwise initialiser is easier, but if you have a lot of properties and only want to mutate a few, copying the struct and then modifying the necessary properties is the better choice.
let transformed = countryArr.map { Country(name: "Random", region: $0.region) }
let transformed2 = countryArr.map { country->Country in
var copy = country
copy.name = "Random"
return copy
}
I wrote the code below. The error I'm getting is at the end of my convenience initializer when I attempt to call self.init. What's wrong with my logic or syntax? Or how would I debug this? The error Xcode is giving is "cannot invoke with an argument list of type".
Thank you for any help on this
import Foundation
import UIKit
class Item: NSObject {
var name: String
var valueInDollars: Int
var serialNumber: String?
let dateCreated: NSDate
var stolen: Bool?
init(name: String, valueInDollars: Int, serialNumber: String?, dateCreated: NSDate, stolen: Bool?) {
self.name = name
self.valueInDollars = valueInDollars
self.serialNumber = serialNumber
self.dateCreated = NSDate()
self.stolen = stolen
//below why did I have to call super.init for this custom class that inherits from NSObject? Doesn't it automatically get called anyway once the custom object is initialized since it inherits from NSObject? It just seems like more work on my behalf which isn't fair. it should do it automatically. Why wouldn't it do it automatically if it inherits from NSObject?
super.init()
}
convenience init(random: Bool = false) {
if random {
let adjectives = ["Fluffy", "Rusty", "Shiny"]
let nouns = ["MacBook Pro", "Red Tribe Bike", "Vegan Pizzas"]
//take a variable that's random; the highest value for this random number will be the number of ojbects in the adjectives array
var idx = arc4random_uniform(UInt32(adjectives.count))
//now use this random variable and let it be the index of the adjectives array...so basically it'll be a random object from the adjectives array
let randomAdjective = adjectives[Int(idx)]
//AWESOME!! Now that the random adjective is stored in the randomAdjective constant, let's re-use the idx variable...Ayyyyeeeee re-use!
//we'll re-use it by doing the same process or close to the same process for nouns
idx = arc4random_uniform(UInt32(nouns.count))
let randomNoun = nouns[Int(idx)]
//now let's concatenate these two clever words, shall we!!
let randomName = "\(randomAdjective) \(randomNoun)"
//yayyy we're programmmminnngg!
//now let's ....whad de fuk....
let randomValue = Int(arc4random_uniform(100))
let randomSerialNumber = NSUUID().uuidString.components(separatedBy: "-").first!
let betterNotBeStolen: Bool = false
self.init(name: randomName, valueInDollars: randomValue, serialNumber: randomSerialNumber, stolen: betterNotBeStolen)
}
}
}
You got the error
"Cannot invoke 'Item.init' with an argument list of type '(name:
String, valueInDollars: Int, serialNumber: String, stolen: Bool)'"
because you missed the the dateCreated param in the self.init(params...).
So you need to replace this line
self.init(name: randomName, valueInDollars: randomValue, serialNumber: randomSerialNumber, stolen: betterNotBeStolen)
with this one
self.init(name: randomName, valueInDollars: randomValue, serialNumber: randomSerialNumber,dateCreated: NSDate(), stolen: betterNotBeStolen)
The next error which you will see after is
Self.init isn't called on all paths before returning from initializer
So you need to add else statement because the initializer don't know what to do when the random param is false.
convenience init(random: Bool = false) {
if random {
let adjectives = ["Fluffy", "Rusty", "Shiny"]
let nouns = ["MacBook Pro", "Red Tribe Bike", "Vegan Pizzas"]
//take a variable that's random; the highest value for this random number will be the number of ojbects in the adjectives array
var idx = arc4random_uniform(UInt32(adjectives.count))
//now use this random variable and let it be the index of the adjectives array...so basically it'll be a random object from the adjectives array
let randomAdjective = adjectives[Int(idx)]
//AWESOME!! Now that the random adjective is stored in the randomAdjective constant, let's re-use the idx variable...Ayyyyeeeee re-use!
//we'll re-use it by doing the same process or close to the same process for nouns
idx = arc4random_uniform(UInt32(nouns.count))
let randomNoun = nouns[Int(idx)]
//now let's concatenate these two clever words, shall we!!
let randomName = "\(randomAdjective) \(randomNoun)"
//yayyy we're programmmminnngg!
//now let's ....whad de fuk....
let randomValue = Int(arc4random_uniform(100))
let randomSerialNumber = NSUUID().uuidString.components(separatedBy: "-").first!
let betterNotBeStolen: Bool = false
self.init(name: randomName, valueInDollars: randomValue, serialNumber: randomSerialNumber,dateCreated: NSDate(), stolen: betterNotBeStolen)
} else {
self.init(name: "SomeName", valueInDollars: 3, serialNumber: "123", dateCreated: NSDate(), stolen: true)
}
}
I wrote a little playground to demonstrate how to sort an array coordinates to find the 5 closest. I use higher-order functions, mapping the coordinates to a struct that also contained a distance, sorted it, and then picked the top 5 items. However, I can't do the final part of picking the top 5 as part of the same compound statement.
Below is the code:
import Foundation
import CoreLocation
let currentLatitide = 19.1553902
let currentLongitude = 72.8528602
struct CoordStruct: CustomStringConvertible {
let coord: CLLocationCoordinate2D
let distance: Double
var description: String {
return "lat: " + String(format: "%.4f",coord.latitude) +
", long: " + String(format: "%.4f",coord.longitude) + ", distance: " + distance.description
}
}
let location = CLLocation(latitude: currentLatitide, longitude: currentLongitude)
let points = [[19.5,71.0],[18.5,72.0],[19.15,72.85],[19.1,75.0],[19.2,70.0],[19.3,70.0],[19.4,70.0],[19.6,70.0],[19.7,70.2],[19.9,70.3],[25,62.0],[24.5,73.4],[23.5,65.0],[21.5,68.0],[20.5,69.0]]
let structs: [CoordStruct] = points.map //This is where the error shows up.
{
let thisCoord = CLLocationCoordinate2D(latitude: $0[0], longitude: $0[1])
let thisLocation = CLLocation(latitude:$0[0], longitude: $0[1])
let distance = location.distance(from: thisLocation)
return CoordStruct(coord: thisCoord, distance: distance)
}
.sorted { $0.distance < $1.distance }
//----------------------------------
.prefix(5) //This won't compile
//----------------------------------
let first5Structs = structs.prefix(5)
first5Structs.forEach { print($0) }
let test = points.map { $0[1] }
.sorted { $0 < $1 }
.prefix(5)
print(test)
See the line marked //This won't compile. With that line un-commented, the compiler says "ambiguous reference to member 'map'" in reference to the map statement. If I comment out the offending line and do a prefix on a separate line, the compiler is happy. The error message makes no sense, which seems typical of the Swift compiler, but the code seems like it SHOULD compile. What am I missing?
The error is a bit misleading, the issue is caused by a return type mismatch.
The annotated type is Array<CoordStruct>
prefix returns ArraySlice<CoordStruct>
It compiles if you change the line to
let structs : ArraySlice<CoordStruct> = points.map { ...
class someClass
{
// This is a list of restaurant names
var restaurantNames = ["Cafe Deadend", "Homei", "Teakha", "Cafe Loisl", "Petite Oyster", "For Kee Restaurant", "Po's Atelier", "Bourke Street Bakery", "Haigh's Chocolate", "Palomino Espresso", "Upstate", "Traif", "Graham Avenue Meats", "Waffle & Wolf", "Five Leaves", "Cafe Lore", "Confessional", "Barrafina", "Donostia", "Royal Oak", "Thai Cafe"]
// This is an array to record the restaurants I have visited
var restaurantIsVisited:[Bool] = [Bool](count: 21, repeatedValue: false)
// This is a function to set the restaurants I have visited
func setTheVisitedRestaurants(somenumber:Int)
{
self.restaurantIsVisited[somenumber] = true
}
}
let thisVisit = someClass()
thisVisit.setTheVisitedRestaurants(1)
let somearray = thisVisit.restaurantIsVisited
Above code has no error in playground. But what if I don't know the number of the total restaurants is 21 (It could be hundreds and I don't want to count). I tried the following code to create an immutable computed property. But it won't work because later on the property will be changed by function setTheVisitedRestaurants and it will return an error.
// var restaurantIsVisited:[Bool]{
// return [Bool](count: restaurantNames.count, repeatedValue: false)
// }
In a word, the question is like how to create an variable property based on the other property in the class. I am a beginner and I really tried. Please help!
Declare restaurantIsVisited with the lazy keyword. This will insure that it isn't created until it is accessed the first time, and by that time you will be able to ask restaurantNames for its count:
class someClass
{
// This is a list of restaurant names
var restaurantNames = ["Cafe Deadend", "Homei", "Teakha", "Cafe Loisl", "Petite Oyster", "For Kee Restaurant", "Po's Atelier", "Bourke Street Bakery", "Haigh's Chocolate", "Palomino Espresso", "Upstate", "Traif", "Graham Avenue Meats", "Waffle & Wolf", "Five Leaves", "Cafe Lore", "Confessional", "Barrafina", "Donostia", "Royal Oak", "Thai Cafe"]
// This is an array to record the restaurants I have visited
lazy var restaurantIsVisited:[Bool] = [Bool](count: self.restaurantNames.count, repeatedValue: false)
// This is a function to set the restaurants I have visited
func setTheVisitedRestaurants(somenumber:Int)
{
self.restaurantIsVisited[somenumber] = true
}
}
let thisVisit = someClass()
thisVisit.setTheVisitedRestaurants(1)
let somearray = thisVisit.restaurantIsVisited
println(somearray.count) // "21"
Why not use a struct to represent a restaurant, for example:
struct Restaurant {
let name: String
var visited: Bool
init(name: String, visited: Bool = false) {
self.name = name
self.visited = visited
}
}
Now it's much easier to keep track of which restaurants have been visited and which ones haven't because you haven't got to worry about information on each restaurant being separated into two arrays.
struct SomeStruct {
var restaurants = [Restaurant(name: "Cafe Deadend"),
Restaurant(name: "Homei"),
Restaurant(name: "Cafe Loisl")]
}
Usage:
var thisVisit = SomeStruct()
thisVisit.restaurants[0].visited = true