i am somewhat new to swift and still trying to get a grasp on the knowledge. which is probably why i've run into waht feels like a simple mistake. i know that class instances are reference types but even knowing that im not entirely sure how to get the result im after. i want to change my SHIPTO without modifying my BILLTO. does anyone know a fix for this?
class Home {
var house: String
var street: String
var postalCode: String
init(house: String, street: String, postalCode: String) {
self.house = house
self.street = street
self.postalCode = postalCode
}
}
class DeliveryAdress {
var orderNumber: Int
var Shipto: Home
var Billto: Home
init(orderNumber: Int, Shipto: Home, Billto: Home) {
self.orderNumber = orderNumber
self.Shipto = Shipto
self.Billto = Billto
}
}
var homeAddress = Home(house: "appartment style", street: "Pizza Pie Road", postalCode: "90210")
var pizzaDelivery = DeliveryAdress(orderNumber: 001, Shipto: homeAddress, Billto: homeAddress)
pizzaDelivery.Shipto.street = "123 Street"
print(pizzaDelivery.Billto.street)
// this should NOT print "123 street"
print(pizzaDelivery.Shipto.street)
// this SHOULD print 123 street
You have 2 solutions:
Use value types - change class to struct
Create separate Home instances for shipTo and billTo
I'd suggest sticking with structs, since unless you explicitly need reference-type behaviour, you should always prefer structs over classes in Swift.
struct Home {
var house: String
var street: String
var postalCode: String
}
struct DeliveryAddress {
var orderNumber: Int
var shipTo: Home
var billTo: Home
}
var homeAddress = Home(house: "appartment style", street: "Pizza Pie Road", postalCode: "90210")
var pizzaDelivery = DeliveryAddress(orderNumber: 001, shipTo: homeAddress, billTo: homeAddress)
pizzaDelivery.shipTo.street = "123 Street"
print(pizzaDelivery.billTo.street)
print(pizzaDelivery.shipTo.street)
There are a couple of general issues with your code:
The Swift naming convention is lowerCamelCase for variable+function names, and UpperCamelCase for type names. So shipTo and billTo, not Shipto.
By default, everything should be immutable, only use mutable properties when you really need them to be mutable.
Use struct instead of class unless you explicitly need reference-type behaviour.
As you said classes are reference type so the property of street has an adress lets say "A". pizzaDelivery.Shipto.street and pizzaDelivery.billTo.street refers to same address , its A. If you change pizzaDelivery.billTo.street this value .Its actually changing value which address is A . So In that situations You need to use Struct. If you change Home type yo Struct , issue gonna be fixed
struct Home {
var house: String
var street: String
var postalCode: String
}
Related
as shown in the code below:
struct Person {
var name: String
}
struct Group {
var person: Person
func callAsFunction() -> Person {
// Person is immutable value
person
}
}
var james = Person(name: "James")
var group = Group(person: james)
group().name = "Wong" //ERROR: Cannot assign to property: function call returns immutable value
group() return an immutable value, that can't be changed! So Is there any way to make the callAsFunction() method return a mutable value?
Thanks ;)
Updated:
My idea is to transfer all the calls and visits of the Group to the Person object in the Group, just like using Person directly.
I can't use dynamicMemberLookup because I don't know what method or property there will be in Person. For example, there may be 100 methods and properties in Person (not only one name property as demonstrated), and it is impossible for me to write 100 subscript methods with dynamicMemberLookup.
My needs are a bit like proxy objects in the Ruby language. Accessing an object (Group) actually accesses another object (Person) inside it, as if the Group does not exist.
ruby proxy patterns:
https://refactoring.guru/design-patterns/proxy/ruby/example
CallAsFunction is the closest implementation so far, but requires that Person cannot be a Struct, otherwise it cannot be assigned to its properties.
Maybe it's not possible to implement this feature in Swift yet?
You're using the wrong dynamic method. What you want is dynamicMemberLookup. Watch closely. First, the preparation:
struct Person {
var name: String
}
#dynamicMemberLookup
struct Group {
var person: Person
subscript(dynamicMember kp:WritableKeyPath<Person,String>) -> String {
get { self.person[keyPath:kp] }
set { self.person[keyPath:kp] = newValue }
}
}
Now look at what that allows you to say:
var group = Group(person: Person(name: "James"))
group.name = "Wong"
print(group.person) // Person(name: "Wong")
Do you see? We set the name of the Group even though it has no name property, and the result was that we set the name of the Group's person which does have a name property.
The callAsFunction simply returns (a copy of the) Person, which is a value type. You cannot then mutate the property of it like that. It is equivalent to the following:
struct Person {
var name: String
}
Person(name: "Foo").name = "Bar"
That returns the same error:
If Person was a reference type, it would have worked, but not for a value type. And even if you took your value type, and first assigned it to a variable before mutating it, you would only be mutating your copy, not the original.
If you want the behavior you want, you would use a #dynamicMemberLookup as suggested by matt (+1) and outlined in SE-0195.
You said:
I can't use dynamicMemberLookup because I don't know what method or property there will be in Person. For example, there may be 100 methods and properties in Person (not only one name property as demonstrated), and it is impossible for me to write 100 subscript methods with dynamicMemberLookup.
You do not need “100 subscript methods.” It is the motivating idea behind #dynamicMemberLookup, namely that the properties will be determined dynamically. E.g., here is Person with two properties, but Group only has the one #dynamicMemberLookup.
struct Person {
var name: String
var city: String
}
#dynamicMemberLookup
struct Group {
var person: Person
subscript(dynamicMember keyPath: WritableKeyPath<Person, String>) -> String {
get { person[keyPath: keyPath] }
set { person[keyPath: keyPath] = newValue }
}
}
var group = Group(person: Person(name: "James", city: "New York"))
group.name = "Wong"
group.city = "Los Angeles"
print(group.person) // Person(name: "Wong", city: "Los Angeles")
If you want to handle different types, make it generic:
struct Person {
var name: String
var city: String
var age: Int
}
#dynamicMemberLookup
struct Group {
var person: Person
subscript<T>(dynamicMember keyPath: WritableKeyPath<Person, T>) -> T {
get { person[keyPath: keyPath] }
set { person[keyPath: keyPath] = newValue }
}
}
And
var group = Group(person: Person(name: "James", city: "New York", age: 41))
group.name = "Wong"
group.city = "Los Angeles"
group.age = 42
print(group.person) // Person(name: "Wong", city: "Los Angeles", age: 42)
This will not happen if I just change the properties instead of replacing the reference to a new object.
Here is a class Person, which is a reference type,
class Person {
var firstName: String
var lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
Here is an instance of Person,
var someone = Person(firstName: "Johnny", lastName: "Appleseed")
then I make an array containing values of type Person
var lotsOfPeople = [someone, someone, someone]
I suppose lotsOfPeople containing 3 references to someone.
But if I change the third value in lotsOfPeople,
lotsOfPeople[2] = Person(firstName: "Lucy", lastName: "Swift")
someone itself isn't changed.
print(someone.firstName) // Johnny
I think it means lotsOfPeople[2] is not a reference to someone.
How could this happen?
The problem is that you are replacing the reference at lotsOfPeople[2] to point to a new object. That is why the original Person is not changed.
If you did lotsOfPeople[2].firstName = "Lucy" then it would change.
Or do:
let person = lotsOfPeople[2]
person.firstName = "Lucy"
then you would also see the original change.
Teacher & TeamMate are two protocols. The class Coach conforms to those protocols.
protocol Teacher {
var firstName: String { get }
var lastName: String { get }
var title: String { get }
}
protocol TeamMate {
var firstName: String { get }
func role()
}
class Coach: Teacher, TeamMate {
var firstName: String
var lastName: String
var title: String
func role() {
print("coach the team")
}
init(firstName: String, lastName: String, title: String){
self.firstName = firstName
self.lastName = lastName
self.title = title
}
}
var member: TeamMate = Coach(firstName: "Izumi", lastName: "Yakuza", title: "LA Coach")
I've created a variable named member of type coach meanwhile it conforms to the TeamMate type (Please correct me if the description is not accurate)
I need to initialise all the properties defined in the Coach class when I create the member object. I.e.(firstName: "Izumi", lastName: "Yakuza", title: "LA Coach"). However, in the end, there is only one property firstName and one method role() inside the member instance.
Question: How the properties (lastName: "Yakuza", title: "LA Coach") were processed? Is that they were created firstly and then cut away or just hidden?
Thanks a lot for your kind help and time.
I've created a variable named member of type coach meanwhile it conforms to the TeamMate type
var member: TeamMate = Coach(firstName: "Izumi", lastName: "Yakuza", title: "LA Coach")
I suspect this line of code isn't doing what you think it's doing. Your member variable isn't of type Coach, you've upcast to the abstract type TeamMate (a protocol which Coach conforms to). Therefore when using your variable, you can only access members that TeamMate defines (firstName and role()) – which is the exact behaviour you're seeing.
Unless you need to you shouldn't upcast your variables, as upcasting loses type information. In most cases, you should just let Swift infer the correct type for you. In your case member should almost certainly be of type Coach (you can always freely pass it to anything expecting a TeamMate, as Coach conforms to this protocol).
Therefore, you just want to drop the explicit type annotation:
var member = Coach(firstName: "Izumi", lastName: "Yakuza", title: "LA Coach")
You should also make it a let constant if you don't plan on mutating it.
I've created the following class
class Person {
var firstName: String
var lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
func fullName() -> String {
return "\(firstName) \(lastName)"
}
}
Then I instantiated a constant value from the class
let john = Person(firstName: "Johnny", lastName: "Applessed")
Question: Why I can change the content of the variable john? Isn't it a constant? Can someone explain that for me, thanks a lot.
john.firstName = "John"
print(john.firstName) // -> John
As #Wain has said – it's due to the nature of reference types. The instance being a let constant only means you cannot assign a new reference to it – but says nothing about the actual mutability of the instance itself.
If you change your class to a struct, you'll see how the behaviour differs with value types, as changing a property changes the actual value of your Person – therefore you are unable to do so if it's a let constant. However I somewhat doubt you'll want to make your Person a struct, as two people with the same name shouldn't be considered to be the same person.
If you only wish your properties to be assigned upon initialisation (and then read-only for the lifetime of the instance), then I would recommend making them let constants (instead of making their setters private). This will ensure that you cannot even change their value from within your class, once assigned.
The rule is as long you give a property a value before the super.init() call – you can make it a let constant (in this case, you just have to assign them in the initialiser before using self).
class Person {
let firstName: String
let lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
...
The class instance itself is a constant, so you can't change it to reference another instance, but the instance is mutable because it's properties are created as vars.
Change firstName to have a private setter and see what you can do:
private(set) var firstName: String
When you're using a constant instance of a class in swift, doesn't mean you can't change the class attributes. It' means you can't instantiate a new object in this constant
let person = Person(firstName: "Johnny", lastName: "Appleseed")
person = Person(firstName: "John", lastName: "Appleseed") //--->It gets error: Cannor assign to value: 'person' is a 'let' constant
But you can create a constant inside class and set this values in the init
class Person {
let firstName: String
let lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
func fullName() -> String {
return "\(firstName) \(lastName)"
}
}
//Tip: Don't init the class constants in declaration time or will get the same above error. Just init this constants at constructor/initialization of class.
And Now you have the expected result you want, even if create a 'var' instance of this object
var person = Person(firstName: "Johnny", lastName: "Appleseed")
person.firstName = "John" //--->It gets error: Cannor assign to value: 'person' is a 'let' constant
person = Person(firstName: "John", lastName: "Snow")
person.firstName = "Johnny" //--->It gets error: Cannor assign to value: 'person' is a 'let' constant
Your thinking was not wrong, but a little confuse cause you would be totally right if it was a struct instead a class.
I'm looking at the example from the “Unowned References and Implicitly Unwrapped Optional Properties” section of the book “The Swift Programming Language.”
Their example code is
class Country {
let name: String
let capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
This works if I want to deal exclusively with Countries and the only purpose of the City type is to be a capital of a Country. But what happens if I want to create a City?
This creates a runtime exception because no reference to the City's Country is retained since it is an unowned variable:
var chicago = City(name:"Chicago", country: Country(name: "USA", capitalName: "Washington DC"))
chicago.country.name // Playground execution failed: error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=EXC_I386_GPFLT).
How would I allow something like this without creating a Strong Reference Cycle?
There are two typical solutions:
If you want to primarily deal with cities, invert the relationship so that City has a strong reference to Country, and Country points back to an unowned instance.
If you want to have cities and countries as primary objects that cross reference each other, put all cities and countries into collections (or other form of store that owns them), and make both references weak. That way they don't own each other, and you don't have a cycle.
The best way to avoid retain cycles is to consider who owns every object. Objects can own each other, but that should be a clear hierarchy (i.e. a tree). If you have connections that go sidewards and and upwards in the hierarchy, make them weak or unowned.
Solution one is the upwards case, solution two is sidewards case.
Edit
A third option is, to have Country own a collection of all its cities. I think that makes most sense in this simple case, but it means the Country needs to create all cities in it's initialization, or have a method that adds cities.
Here's an example for the second case. It's quite complex, probably too much so for this simple case, but it illustrates extracting a common owner. I would normally use it if there are a lot of cross references. (Think relational database. The records don't own each other.)
class Country {
let name: String
weak var capitalCity: City?
init(name: String) {
self.name = name
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country, isCapital: Bool) {
self.name = name
self.country = country
if isCapital {
country.capitalCity = self
}
}
}
class Planet {
var countries: [Country] = []
var cities: [City] = []
}
let earth = Planet()
earth.countries = [
Country(name: "USA"),
Country(name: "Canada"),
]
earth.cities = [
City(name: "Washington DC", country: earth.countries[0], isCapital: true),
City(name: "Chicago", country: earth.countries[0], isCapital: false),
City(name: "Ottawa", country: earth.countries[1], isCapital: true),
]
In your example, nobody is owning the Country instance. That means it gets deallocated (freed) immediately.
var country = Country(name: "USA", capitalName: "Washington DC")
var chicago = City(name:"Chicago", country: country)
chicago.country.name
will fix it because our coutry variable will keep USA from deallocating
If you use an unowned reference, you always have to keep a strong reference somewhere else.