In learning Swift, there seems to be two approaches to initializing a class instance:
// Approach A
class Person {
let first: String = "bob"
let last: String = "barker"
}
let worker = Person()
worker.first
worker.last
// Approach B
class Person2 {
let first2: String
let last2: String
init() {
self.first2 = "bill"
self.last2 = "williams"
}
}
let dealer = Person2()
dealer.first2
dealer.last2
Is there any reason why I would use one approach instead of the other?
“If a property always takes the same initial value, provide a default value rather than setting a value within an initializer. The end result is the same, but the default value ties the property’s initialization more closely to its declaration. It makes for shorter, clearer initializers and enables you to infer the type of the property from its default value. The default value also makes it easier for you to take advantage of default initializers and initializer inheritance, as described later in this chapter.”
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/us/jEUH0.l
I use the first version when I have a known default value that I am putting into the variable. I use the second for anything that could change based on what I pass into the init.
So my preferred version of the above would be:
// Approach A
class Person {
let first: String = "bob"
let last: String = "barker"
}
let worker = Person()
worker.first
worker.last
// Approach B
class Person2 {
let first2: String
let last2: String
init(first2: String, last2: String) {
self.first2 = first2
self.last2 = last2
}
}
let dealer = Person2(first2: "bill", last2: "williams")
dealer.first2
dealer.last2
Related
In the following example code, I create a struct and a class with similar members. With the struct I can initialize an instance by any number of the members into its constructor, and the rest will default. With a class, I have to specify every version of init I want to use. Seems like I must be missing some way to do it with a class though -- is there any way to do this? It looks like in 2016 there was not, but I know Swift has changed a ton since then. I'm hoping there is a way now.
import Foundation
struct FooStruct {
var id: UUID = UUID()
var title = ""
}
// these statements both work fine
let a = FooStruct(id: UUID())
let a2 = FooStruct(title: "bar")
class FooClass {
var id: UUID = UUID()
var title = ""
}
// these statements both give the same error:
// Argument passed to call that takes no arguments
let b = FooClass(id: UUID())
let b2 = FooClass(title: "bar")
What you are seeing with Structure types is what is called a memberwise initializer. Swift does not provide one of these to Class types because of the more complex way Classes are initialized, due to their inheritance model.
Swift provides a default initializer—different than a memberwise initializer—for any structure or class that provides default values for all of its properties and doesn’t provide at least one initializer itself. The default initializer simply creates a new instance with all of its properties set to their default values.
you could just use this:
class FooClass {
var id: UUID = UUID()
var title = ""
init(id: UUID = UUID(), title: String = ""){
self.id = id
self.title = title
}
}
and this will work:
let b = FooClass(id: UUID())
let b2 = FooClass(title: "bar")
I am trying to practice "class with generic". I encountered 2 errors:
Generic parameter 'T' could not be inferred
Reference to generic type 'GenericObject' requires arguments in <...>
The 2 errors in GenericManager class. Please reference the following code. How do I solve this issue?
class User {
var name: String
init(name: String) {
self.name = name
}
}
class Employee {
var name: String
var position: String
init(name: String, position: String) {
self.name = name
self.position = position
}
}
class GenericObject<T> {
var items = [T]()
init(forType: T.Type) {}
func addObject(_ obj: T) {
self.items.append(obj)
}
}
class GenericManager {
//issue: Generic parameter 'T' could not be inferred
var objects = [GenericObject]()
//issue: Reference to generic type 'GenericObject' requires arguments in <...>
func addObject(_ obj: GenericObject) {
self.objects.append(obj)
}
}
let u = User(name: "User")
let uo = GenericObject(forType: User.self)
uo.addObject(u)
let e = Employee(name: "Employee", position: "session manager")
let eo = GenericObject(forType: Employee.self)
eo.addObject(e)
let manager = GenericManager()
manager.addObject(uo)
manager.addObject(eo)
The compiler needs to know the type of T, and in this case you haven't supplied it.
You can do it like this:
var objects = [GenericObject<YourTypeHere>]()
For example, if GenericObject will hold an array of Int, it would look like this:
var objects = [GenericObject<Int>]()
I noticed you updated your question. It would be helpful to know what you're trying to achieve, but I'll try to help you anyway.
When you have a generic object, you need to tell the compiler the type of the generic at compile time, that's why it's complaining that the type can't be inferred, it needs to know.
Since you want to be able to add objects to the GenericManager array, you need the generic in those two cases to be the same, so you can modify your class like this:
class GenericManager<T> {
var objects = [GenericObject<T>]()
func addObject(_ obj: GenericObject<T>) {
self.objects.append(obj)
}
}
However, since the objects have to be of the same generic, you can't add a GenericObject<User> and GenericObject<Employee> to the same manager, what you can do is to implement those as GenericObject<Any>, and do the same with the GenericManager, then it will look like this:
let u = User(name: "User")
let uo = GenericObject(forType: Any.self)
uo.addObject(u)
let e = Employee(name: "Employee", position: "session manager")
let eo = GenericObject(forType: Any.self)
eo.addObject(e)
let manager = GenericManager<Any>()
manager.addObject(uo)
manager.addObject(eo)
Keep in mind that this will lose you any advantage that generics would do, what you could do is to create a protocol or common superclass and use that instead of Any, but that depends on what you're trying to achieve.
If you have any further questions, please add a comment instead of silently updating your question.
The problem you are having is that you are trying to use generics, but want to ignore that in GenericManager and store references to objects of different types.
Consider this - when you call manager.objects[0] what would you expect to be returned?
You can solve this by type-erasure using Any as EmilioPelaez suggested. However this is often a codesmell which leads to casting hacks throughout your code.
One alternative would be to use an enum to specify the different types of data you want to represent:
enum GenericObject {
case users([User])
case employees([Employee])
}
...
let uo = GenericObject.users([ u ])
...
let eo = GenericObject.employees([ e ])
Now when you access the properties inside GenericManager you would be required to switch over the different supported types, and when you add a new type you would be required to implement code whenever you use a GenericObject
In Swift 4 many on the Foundation team have discussed how much easier it is to use keyPaths as compared to Swift 3. This begs the question... What is a keyPath? Seriously, I can't find any clear resources.
Objective-C has the ability to reference a property dynamically rather than directly. These references, called keypaths. They are distinct from direct property accesses because they don't actually read or write the value, they just stash it away for use.
Let define a struct called Cavaliers and a struct called Player, then create one instance of each:
// an example struct
struct Player {
var name: String
var rank: String
}
// another example struct, this time with a method
struct Cavaliers {
var name: String
var maxPoint: Double
var captain: Player
func goTomaxPoint() {
print("\(name) is now travelling at warp \(maxPoint)")
}
}
// create instances of those two structs
let james = Player(name: "Lebron", rank: "Captain")
let irving = Cavaliers(name: "Kyrie", maxPoint: 9.975, captain: james)
// grab a reference to the `goTomaxPoint()` method
let score = irving.goTomaxPoint
// call that reference
score()
The last lines create a reference to the goTomaxPoint() method called score. The problem is, we can't create a reference to the captain's name property but keypath can do.
let nameKeyPath = \Cavaliers.name
let maxPointKeyPath = \Cavaliers.maxPoint
let captainName = \Cavaliers.captain.name
let cavaliersName = irving[keyPath: nameKeyPath]
let cavaliersMaxPoint = irving[keyPath: maxPointKeyPath]
let cavaliersNameCaptain = irving[keyPath: captainName]
Please test with Xcode 9 or capable snapshot.
Swift KVC
[Objective-C KVC]
KeyPath is a reference to a property of a type rather than value. It adds a dynamism into language
There are some of them:
KeyPath<Root, Value> - only read
WritableKeyPath<Root, Value> - read/write for var property
ReferenceWritableKeyPath<Root, Value> - read/write only for reference types[About]
PartialKeyPath<Root>
AnyKeyPath
You can find that KeyPath is used for shortcuts(e.g. sorting, iterating, filtering), KVO[Example], MemoryLayout[Example], SwiftUI and others more advanced features
Syntax
class SomeClass {
var v: String = "Hello World"
}
Pre Swift v4 - slow and not type safe
#objc //For example var v: String = "Hello world"
let keyPath = #keyPath(SomeClass.v) //keyPath is String Type
//read
someClass.value(forKeyPath: keyPath) //or forKey:
//write
someClass.setValue("Another string", forKeyPath: keyPath) //or forKey:
Starts from backwards slash \
let someKeyPath = \SomeClass.v //KeyPath<SomeClass, String>
If there is a known object you can use \.
someClass.observe(\.v, options: .new) //someClass1.v
//read
let res = someClass[keyPath: \SomeClass.v]
//write
someClass[keyPath: \.v] = "Another string"
I want to know how Literal Convertibles work in Swift. The little I know is that the fact that, in var myInteger = 5, myInteger magically becomes an Int is because Int adopts a protocol, ExpressibleByIntegerLiteral and we don't have to do var myInteger = Int(5). Similarly String, Array, Dictionary etc all conform to some Literal protocols.
My Question is
Am I right in my little understanding of Literal Convertibles?
How can we implement these in our own types. For example
class Employee {
var name: String
var salary: Int
// rest of class functionality ...
}
How can I implement Literal Protocols to do var employee :Employee = "John Doe" which will automatically assign "John Doe" to employee's name property.
You are partially correct in your understanding of the various ExpressibleBy...Literal protocols. When the Swift compiler parses your source code into an Abstract Syntax Tree, it already identified what literal represents what data type: 5 is a literal of type Int, ["name": "John"] is a literal of type Dictionary, etc. Apple makes the base type conform to these protocols for the sake of completeness.
You can adopt these protocols to give your class an opportunity to be initialized from a compile-time constant. But the use case is pretty narrow and I don't see how it applies to your particular situation.
For example, if you want to make your class conform to ExpressibleByStringLiteral, add an initializer to set all your properties from a String:
class Employee: ExpressibleByStringLiteral {
typealias StringLiteralType = String
var name: String
var salary: Int
required init(stringLiteral value: StringLiteralType) {
let components = value.components(separatedBy: "|")
self.name = components[0]
self.salary = Int(components[1])!
}
}
Then you can init your class like this:
let employee1: Employee = "John Smith|50000"
But if you dream about about writing something like this, it's not allowed:
let str = "Jane Doe|60000"
let employee2: Employee = str // error
And if you pass in the wrong data type for salary, it will be a run time error instead of a compile-time error:
let employee3: Employee = "Michael Davis|x" // you won't know this until you run the app
TL, DR: it is a very bad idea to abuse these ExpressibleBy...Literal types.
This can be a scenario to work with Convertibles in custom types.
struct Employee : ExpressibleByStringLiteral {
var name: String = ""
init() {}
init(stringLiteral name: String) {
self.name = name
}
}
func reportName(_ employee: Employee) {
print("Name of employee is \(employee.name)")
}
reportName("John Doe") //Name of employee is John Doe
Is there anyway to use conversion using a variable? I am using object stacking using type of "AnyObject" and I've been able to take the class type and populate a variable. Now I need to populate an array using conversion.
var myString = "Hello World"
var objectStack = [AnyObject]()
objectStack.append(myString)
let currentObject = String(describing: objectStack.last!)
var objectType = String()
let range: Range<String.Index> = currentObject.range(of: ":")!
objectType = currentObject.substring(to: range.lowerBound)
let range2: Range<String.Index> = objectType.range(of: ".")!
objectType = objectType.substring(from: range2.upperBound)
The code above will evaluate the class and set the value of "objectType" to "String". Now I'm trying to go the other way. Something like this:
for obj in objectStack{
obj = newObject as! objectType //this doesn't work
}
Is something like this possible?
There is a simpler, safer way to get the type:
let type = type(of: objectStack.last!) // String.Type
let typeString = String(describing: type) // "String"
The other way around is not possible because the type of the object is not known at compile time. Do you have a number of known types you want to try to cast to? In that case, use optional binding to check if the cast is successful:
let object = objectStack.last!
if let string = object as? String {
// do String stuff
}
else if let i = object as? Int {
// do Int stuff
}
// and so on
If you have a large number of possible types that share some common functionality: Use Protocols. See Swift Documentation for a nice introduction.
You define a protocol for some common functionality that different types can implement:
protocol Stackable {
func doStuff()
// (more methods or properties if necessary)
}
The protocol provides a contract that all types conforming to this protocol have to fulfill by providing implementations for all declared methods and properties. Let's create a struct that conforms to Stackable:
struct Foo: Stackable {
func doStuff() {
print("Foo is doing stuff.")
}
}
You can also extend existing types to make them conform to a protocol. Let's make String Stackable:
extension String: Stackable {
func doStuff() {
print("'\(self)' is pretending to do stuff.")
}
}
Let's try it out:
let stack: [Stackable] = [Foo(), "Cat"]
for item in stack {
item.doStuff()
}
/*
prints the following:
Foo is doing stuff.
'Cat' is pretending to do stuff.
*/
This worked for me:
var instance: AnyObject! = nil
let classInst = NSClassFromString(objectType) as! NSObject.Type
instance = classInst.init()