How to destructure variable into function's parameter? - swift

How can I pass the value of all key from object as functions parameters? like python did.
I have one function getNameInfo with parameter with default value firstName, lastName, age and one object with key firstName, lastName, age how is the best way for me to descture the pbject and pass all value of key into function? in python i can do something like getNameInfo(**a.__dict__.values()) but
class Person {
public var firstName = "firstName"
public var lastName = "lastName"
public var age = 12
public init(firstName: String, lastName: String, age: Int){
self.firstName = firstName
self.lastName = lastName
self.age = age
}
}
let a = Person(firstName: "firstName", lastName: "lastName", age: 12)
func getNameInfo(firstName: String = "i am first", lastName: String = "I am lat", age: Int = 50) {
print("\(fName), \(lName), \(age)")
}
getNameInfo()
// getNameInfo(a) // expect to print out firstName, lastName, 12

There is no similar feature in Swift. The closest version is to use protocols:
protocol NameInfoProviding {
var firstName: String { get }
var lastName: String { get }
var age: Int { get }
}
extension Person: NameInfoProviding {}
func getNameInfo(_ nameInfo: some NameInfoProviding) {
print("\(nameInfo.firstName), \(nameInfo.lastName), \(nameInfo.age)")
}
getNameInfo(a)
But yes, this is a different feature and used in different ways.

Related

Type of expression is ambiguous class error

I am fairly new to the swift language I am just taking a course I bought and I found myself running into the same error code a lot. "Type is ambiguous without more context" and I understand what it means (i think), SO. I am trying to assign a variable that has a class stored inside to another parameter of a class if that makes any sense. I understand that I'm trying to assign a class to a string, but I guess my question is how can I make this work? is there a type I don't know about in swift that would allow me to do such a thing?
class Adress {
let street: String
let city: String
let postalCode: String
init(street: String, city: String, postalCode: String) {
self.street = street
self.city = city
self.postalCode = postalCode
}
}
class PurchaseOrder {
var id: Int
var shippingAdress: (String)
var billingAdress: (String)
init(id: Int, shippingAdress: String, billingAdress:String) {
self.id = id
self.shippingAdress = shippingAdress
self.billingAdress = billingAdress
}
}
var defaultAddress = Adress(street: "555 North Pole Ave.", city: "North Pole City", postalCode: "H0H 0H0")
var aliceGift = PurchaseOrder(id: 001, shippingAdress: defaultAddress, billingAdress: defaultAddress)
// THE ERROR IS HERE ^^^^^^.
aliceGift.shippingAdress = "1000 North Pole Blvd."
print(aliceGift.billingAdress)
print(aliceGift.shippingAdress)
You have a custom Adress class so you should use it in your code. Change PurchaseOrder to
class PurchaseOrder {
var id: Int
var shippingAdress: Adress
var billingAdress: Adress
init(id: Int, shippingAdress: Adress, billingAdress:Adress) {
self.id = id
self.shippingAdress = shippingAdress
self.billingAdress = billingAdress
}
}
Of course you need to change the rest of the code as well so you assign an Adress object and not a String if you want to change any adress property
You've declared the types of shippingAddress and billingAddress incorrectly, as String instead of Address.
class Address {
let street: String
let city: String
let postalCode: String
init(street: String, city: String, postalCode: String) {
self.street = street
self.city = city
self.postalCode = postalCode
}
}
class PurchaseOrder {
var id: Int
var shippingAddress: Address
var billingAddress: Address
init(id: Int, shippingAddress: Address, billingAddress: Address) {
self.id = id
self.shippingAddress = shippingAddress
self.billingAddress = billingAddress
}
}
var defaultAddress = Address(street: "555 North Pole Ave.", city: "North Pole City", postalCode: "H0H 0H0")
var aliceGift = PurchaseOrder(id: 001, shippingAddress: defaultAddress, billingAddress: defaultAddress)

Keep value semantics on structs (containing an array of reference types)

Let's say (just as an example) I have a class Person and a struct House:
class Person {
var name: String
var lastName: String
init(name: String, lastName: String) {
self.name = name
self.lastName = lastName
}
}
struct House {
var address: String
var squareMeters: Float
var owner: Person
init(withAddress address: String, squareMeters: Float, andOwner owner: Person) {
self.address = address
self.squareMeters = squareMeters
self.owner = owner
}
}
To keep the value semantics on House is pretty easy in this case, if I'm not mistaken I can just do something like:
struct House {
var address: String
var squareMeters: Float
private var owner: Person //owner is now private
init(withAddress address: String, squareMeters: Float, andOwner owner: Person) {
self.address = address
self.squareMeters = squareMeters
//init now creates a copy of the owner passed
self.owner = Person(name: owner.name, lastName: owner.lastName)
}
//every time the owner gets modified from the outside we make sure to create a copy if needed
private var ownerToWrite: Person {
mutating get {
if !isKnownUniquelyReferenced(&owner) {
owner = Person(name: owner.name, lastName: owner.lastName)
}
return owner
}
}
var ownerName: String {
get {
return owner.name
}
mutating set {
ownerToWrite.name = newValue
}
}
var ownerLastName: String {
get {
return owner.name
}
mutating set {
return ownerToWrite.lastName = newValue
}
}
}
Ok, this should work. But what if House contained an array of Person? For instance:
struct House {
var address: String
var squareMeters: Float
var tenants: [Person]
init(withAddress address: String, squareMeters: Float, andTenants tenants: [Person]) {
self.address = address
self.squareMeters = squareMeters
self.tenants = tenants
}
}
How could I maintain the value semantics on the new House struct? Thank you in advance.

Protocol with Empty Init

I am using a framework that has a 'user' protocol with all the desired properties listed, the protocol also has an empty init method. I need to create a user but any instance I create using the protocol complains that the init doesnt initialize all properties
Framework protocol
public protocol User {
/// Name
public var firstname: String { get set }
/// Lastname
public var lastname: String { get set }
///Init
public init()
}
How would I create my own struct utilizing this protocol and adding values to the params on init ?
thanks in advance!
You can use as following:
struct MyAppUser: User {
// default init coming from protocol
init() {
self.firstname = ""
self.lastname = ""
}
// you can use below init if you want
init(firstName: String, lastName: String) {
self.firstname = firstName
self.lastname = lastName
}
// coming from protocol
var firstname: String
var lastname: String
}
// user 1
var user1 = MyAppUser()
user1.firstname = "Ashis"
user1.lastname = "laha"
print(user1)
// user 2
let user2 = MyAppUser(firstName: "Kunal", lastName: "Pradhan")
print(user2)
Output:
MyAppUser(firstname: "Ashis", lastname: "laha")
MyAppUser(firstname: "Kunal", lastname: "Pradhan")

Swift: Reducing the length of Init methods

I would like to reduce the length of init method.
struct Person {
var id: Int
var firstName: String
var lastName: String
var vehicle: String
var location: String
var timeZone: String
init (id: Int, firstName: String, lastName: String, vehicle: String, location: String, timeZone: String ) {
self.firstName = firstName
self.lastName = lastName
self.vehicle = vehicle
self.location = location
self.timeZone = timeZone
}
}
Below is an instance of Person I am creating. I have to pass in the value of every single variable inline.
let person = Person(id: 22, firstName: "John", lastName: "Doe", vehicle: "Chevy", location: "Dallas", timeZone: "CST")
Question: How can I shrink the length of init? In Obj-C I used to create a data model class. Populate it's variables and then pass the entire class, reducing the length of the init method.
i.e.
Person *person = [Person new];
person.id = 22;
person.firstName = "John";
person.lastName = "Doe";
person.vehicle = "Chevy";
person.location = "Dallas";
person.timeZone = "CST"
Person *person = [Person initWithPerson:person];
What's an equivalent way in Swift to reduce the length of init without having to initialize every single variable inline? I know tuples is one way, is there any other best practice?
Just remove the initializer!
struct Person {
let id: Int
let firstName: String
let lastName: String
let vehicle: String
let location: String
let timeZone: String
}
Now you can use the memberwise initializer
Person(
id: 87112,
firstName: "Walter",
lastName: "White",
vehicle: "2004 Pontiac Aztek",
location: "Albuquerque",
timeZone: "UTC-07:00"
)
Structure types automatically receive a memberwise initializer if they do not define any of their own custom initialisers.
The Swift Programming Language
DO NOT use var
As you can see I replaced var with let.
Unless you need to change some properties of a Person after the value has been created, I suggest you to use let. Otherwise you are free to use var. This way the compiler will prevent unwanted changes.
DO NOT use Optionals
I don't know the business logic of your app, however if a Person must have all that 6 properties always populated, don't make them optionals. Otherwise every time you need to use a Person value the compiler will force you to check if that optional has a value.
DO NOT use Implicitly Unwrapped Optionals
Seriously. There are a few cases where they are useful and a model value is not one of them
Using a struct you actually don't need an initializer
struct Person {
var id : Int?
var firstName: String?
var lastName: String?
var vehicle: String?
var location: String?
var timeZone: String?
}
var person = Person()
person.id = 22
person.firstName = "John"
person.lastName = "Doe"
person.vehicle = "Chevy"
person.location = "Dallas"
person.timeZone = "CST"
You can do the same with non-optionals
struct Person {
var id = 0
var firstName = ""
var lastName = ""
var vehicle = ""
var location = ""
var timeZone = ""
}
Consider also the benefit of an initializer to declare (read-only) constants
struct Person {
let id : Int
let firstName : String
let lastName : String
let vehicle : String
let location : String
let timeZone : String
}
In this case you have to use the implicit memberwise initializer.
let person = Person(id: 22, firstName: "John", lastName: "Doe", vehicle: "Chevy", location: "Dallas", timeZone: "CST")
Like it was mentioned in the comments, an initializer will be created for you, and it'll look like this:
Person(id: Int?, firstName: String?, lastName: String?, vehicle: String?, location: String?, timeZone: String?)
However, you can also do this:
var person = Person()
person.id = 100
person.firstName = "Name"
...
Like you used to do in ObjC. Note that person was declared as var, because if it was declared as let, you wouldn't be able to mutate it.

Want to create particular property value array from array

Having array of employee object, called employees and now I want to create another array called filteremployees which is having only id and date of birth value.
Using
let filteremployees = employee.map({ $0.id})
I can get array which contains only id value but i want to have id as well dateOfBirth
class Employee {
var id: Int
var firstName: String
var lastName: String
var dateOfBirth: NSDate?
init(id: Int, firstName: String, lastName: String) {
self.id = id
self.firstName = firstName
self.lastName = lastName
}
}
Try this:
let employees : [Employee] = ...
let list: [(Int, NSDate?)] = employees.map { ($0.id, $0.dateOfBirth) }
You must explicitly declare the type of list otherwise you get this error from the compiler
Type of expression is ambiguous without more context.
Tested with Xcode 7 Playground and Swift 2.0.
Hope this helps.
You could try using the same map method and returning a tuple of your expected values:
let filter employees: [(Int, NSDate?)] = employee.map({ ($0.id, $0.dateOfBirth) })
Alternatively, and I think this is a better solution, create a new value type and create that with only your required values
struct FilteredEmployee {
let id: String
let dateOfBirth: NSDate?
init(employee: Employee) {
id = employee.id
dateOfBirth = employee.dateOfBirth
}
}
And then you can map the initialiser over the array
let filteremployees = employee.map { FilteredEmployee($0) }