Is there a way to assign property values to a class instance even if it is not a parameter in the init constructor? For example, in C# I can do this:
public class Student
{
public string firstName;
public string lastName;
}
var student1 = new Student();
var student2 = new Student { firstName = "John", lastName = "Doe" };
Notice for student2 I can still assign values during initialization even though there's no constructor in the class.
I could not find in the documentation if you can do something like this for Swift. If not, is there a way to use extensions to extend the Student class to assign property values during initialization?
The reason I'm looking for this is so I can add a bunch of instances to an array without explicitly creating variables for each student instance, like this:
var list = new[] {
new Student { firstName = "John", lastName = "Doe" },
new Student { firstName = "Jane", lastName = "Jones" },
new Student { firstName = "Jason", lastName = "Smith" }
}
Any native or elegant way to achieve this in Swift?
You have a couple of options depending on how you want to configure this type and what syntax is most convenient for you.
You could define a convenient initializer which accepts the properties you want to set. Useful if you're setting the same properties all the time, less useful if you're setting an inconsistent set of optional properties.
public class Student
{
public var firstName:String?;
public var lastName:String?;
}
extension Student {
convenience init(firstName: String, lastName: String) {
self.init()
self.firstName = firstName
self.lastName = lastName
}
}
Student(firstName: "Any", lastName: "Body")
You could define a convenience initializer which accepts a block to configure the new instance.
extension Student {
convenience init(_ configure: (Student) -> Void ) {
self.init()
configure(self)
}
}
Student( { $0.firstName = "Any"; $0.lastName = "Body" } )
You could imitate Ruby's tap method as an extension so you can operate on an object in the middle of a method chain.
extension Student {
func tap(block: (Student) -> Void) -> Self {
block(self)
return self
}
}
Student().tap({ $0.firstName = "Any"; $0.lastName = "body"})
If that last one is useful you might want to be able to adopt tap on any object. I don't think you can do that automatically but you can define a default implementation to make it easier:
protocol Tap: AnyObject {}
extension Tap {
func tap(block: (Self) -> Void) -> Self {
block(self)
return self
}
}
extension Student: Tap {}
Student().tap({ $0.firstName = "Any"; $0.lastName = "body"})
If your class has no required initialiser, you can use a closure method to set the Student properties before returning the new Student object as follow:
public class Student {
var firstName = String()
var lastName = String()
}
let student1 = Student()
let student2: Student = {
let student = Student()
student.firstName = "John"
student.lastName = "Doe"
return student
}()
print(student2.firstName) // John
print(student2.lastName) // Doe
I just wanted to point out that if your structure can be immutable, you can just use a struct rather than a class and you'll get an implicit initializer for free:
Just paste this into a playground and change struct to class and you'll see what I mean.
struct Student
{
var firstName : String?
var lastName : String?
}
var student = Student(firstName: "Dan", lastName: "Beaulieu")
This is just syntactic candy but I use a custom operator to do this kind of thing:
infix operator <- { associativity right precedence 90 }
func <-<T:AnyObject>(var left:T, right:(T)->()) -> T
{
right(left)
return left
}
let rgbButtons:[UIButton] =
[
UIButton() <- { $0.backgroundColor = UIColor.redColor() },
UIButton() <- { $0.backgroundColor = UIColor.greenColor() },
UIButton() <- { $0.backgroundColor = UIColor.blueColor() }
]
The reason I'm looking for this is so I can add a bunch of instances to an array without explicitly creating variables for each student instance[.]
I'm not familiar with C#, but in Swift, you can do that just by initializing the objects inside the array declaration:
var list: [Student] = [
Student(firstName: "John", lastName: "Doe"),
Student(firstName: "Jane", lastName: "Jones"),
Student(firstName: "Jason", lastName: "Smith")
]
The other approaches suggested are all equally valid, but if you are simply trying to populate an array without declaring any variables, Swift makes that easy.
Related
There is this job in Swift 5.0:
The class is presented below. In the body of this class, create a function that will print the parameters of this class for a specific object. Create such an object of class Student, call it this function and display the result on the screen:
Job class
class Student {
var name: String
var surname: String
var yearOfBorn: Int
var mark: Double
init(name: String, surname: String, yearOfBorn: Int, mark: Double) {
self.name = name
self.surname = surname
self.yearOfBorn = yearOfBorn
self.mark = mark
}
}
How i can make it?
I trying:
func printStudent() {
if name == name {
print(name)
} else if surname == surname {
print(surname)
} else if yearOfBorn == yearOfBorn {
print(yearOfBorn)
} else if mark == mark {
print(mark)
}
}
I’m not sure what your intent was with these if statements. Perhaps you are thinking of:
if let foo = foo { ... }
But that technique is only used if foo was an optional. But your properties are not optionals, so if let syntax is unnecessary.
Needless to say, you could just do:
func printStudent() {
print(name)
print(surname)
print(yearOfBorn)
print(mark)
}
FWIW, if your intent is just to print this out for your own purposes, you might want to make your class conform to CustomStringConvertible:
extension Student: CustomStringConvertible {
var description: String { return "<Student name=\(name); surname=\(surname); yearOfBorn=\(yearOfBorn); mark=\(mark)>" }
}
Then you don’t need to write your own printStudent method at all, but can use print directly:
let student = Student(name: "Rob", surname: "Ryan", yearOfBorn: 2000, mark: 4)
print(student)
And that will produce:
<Student name=Rob; surname=Ryan; yearOfBorn=2000; mark=4.0>
Alternatively, if you’re OK with struct value type instead, you don’t need the init method or the CustomStringConvertible protocol, at all. Then you can define Student as simply:
struct Student {
var name: String
var surname: String
var yearOfBorn: Int
var mark: Double
}
And then
let student = Student(name: "Rob", surname: "Ryan", yearOfBorn: 2000, mark: 4)
print(student)
Will produce:
Student(name: "Rob", surname: "Ryan", yearOfBorn: 2000, mark: 4.0)
If you want to print all the attributes of the object you don’t need this if statements; as a matter of fact if you pass name == name as the parameter the first if statement will be always entered and thus the other ones skipped.
You just need to create a function like this where you print each attribute:
func printStudent() {
print(self.name)
print(self.surname)
print(self.yearOfBorn)
...
}
You just need to print the variables:
func printStudent() {
print("Name: \(self.name), Surname: \(self.surname), Year Of Born: \(self.yearOfBorn)")
}
try this code:
func printStudent () {
print("name: \(self.name), surname: \(self.surname), yearOfBorn: \ .
(self.yearOfBorn), mark: \(self.mark)")
}
I'm working on a project and I have created a class to handle the json response to convert it to modal class and change it back to json request with updated data if needed.
Here in the class I'm getting and saving values from and into dictionary. I need to create an enum for the dictionary keys so that there should be less chance for error for complex key formats.
I even tried using like
enum Fields {
case Name
case Email
}
but Fields.Email return Fields object
if I use a protocol of a variable like
protocol someProtocol {
var name: String { get }
}
extension someProtocol {
var name:String {
return String(describing: self)
}
}
and then extend the enum Fields:someProtocol
then I can use it like Fields.name.name or Fields.email.name
But My client will not approve this I want to create an enum so that I can access the string directly like for name I want key "Name" and I should get it liek "Fields.name" or ".name"
So here I have two objectives
first it that I need to create something that can be accessed through class function
second it should be common so that I can use it with multiple classes
third I can access it with less operators
—
class PersonService {
class Update {
var name = ""
var email = ""
var personId = 0
func createDataFrom(dic:[AnyHashable : Any]) -> Update {
let update = Update()
update.name = dictionary["Name"]
update.email = dictionary["Email"]
update.personId = dictionary["Id"]
return update
}
func createDataTo() -> [AnyHashable:Any] {
var ret = [AnyHashable : Any]()
ret["Name"] = name
ret["Email"] = email
ret["Id"] = personId
return ret
}
}
}
Something like that?
enum Fields: String {
case Name = "Name"
case Email = "Email"
}
Print(Fields.Name.rawValue)
result: "Name"
Or
struct Constants {
static let name = "Name"
static let email = "Email"
}
print(Constants.name)
result: "Name"
I have two classes (to simplify I drop other filed the object is more complicated then the Person):
class Person
{
var name: String = "Default name"
init(object: PersonEntity)
{
name = object.daysMask
}
}
class Employer: Person
{
}
I have function that configure for me a person
func getConiguratedPerson(name: String) -> Person
{
let person = Person()
person.name = name
}
In case if I want to get Person I simple do this:
let person = getConiguratedPerson("Alex")
but what if I need Employer instead and I want to use this function as well
let employer = getConiguratedPerson("Alex") // returns Person as expected but need to have employer instead.
In Objective-C we can simple do this:
Employer *employer = Employer([self getConiguratedPerson:"Alex"]) if I remember.
The mistake is the getConfiguredPerson function. You want an initializer. Initializers return your own class, which is exactly what you want.
class Person {
var name: String = "Default name"
init(name: String) {
self.name = name
}
init(object: PersonEntity) {
self.init(object.daysMask)
}
}
class Employer: Person {}
Now to create a person, you just use Person(name: "Alex") and to get an employer you use Employer(name: "Alex").
Note that this is true in ObjC as well. You shouldn't have getConfiguredPerson there either. You'd should have [Person initWithName:].
You can't use upcasting here, because configured person is not of Employee type, but of Person type. To achieve the desired result I would suggest something like this:
class Person {
var name = "name"
required init() { }
}
class Employee: Person { }
func getPerson<T: Person>(name: String) -> T {
let person = T()
person.name = name
return person
}
let employee: Employee = getPerson("Alex")
or this if type of object returning by factory method depends on input:
func getPerson(name: String) -> Person {
if name != "Alex" {
return Person()
}
return Employee()
}
let employee = getPerson("Alex") as! Employee
There are a total of 3 classes in all.
In the first class, it will do all init to manage the strings.
In the second class, it creates an array of class objects for first class.
In the third class, it will access the data stored in the second class. (This is where I need to know.)
FirstClass.swift
class Person{
var name : String
var description : String
init(name: String, description: String){
self.name = name
self.description = description
}
SecondClass.swift
class StoreData: UIViewController {
var personList = [Person]()
override func viewDidLoad() {
super.viewDidLoad()
self.personList.append(Person(name: "John", description: "123"))
self.personList.append(Person(name: "Mary", description: "456"))
}
ThirdClass.swift
So in this class, I need to access the data that is stored in SecondClass.swift. Say I wat to store in a string array of names. I want to extract where var names = personList[0].name How do I do so?
The easiest way in your case to make it work is like this:
class StoreData: UIViewController {
static var sharedData = StoreData()
var personList = [Person]()
override func viewDidLoad() {
super.viewDidLoad()
self.personList.append(Person(name: "John", description: "123"))
self.personList.append(Person(name: "Mary", description: "456"))
}
}
class ThirdClass {
func someFunc() {
let person1 = StoreData.sharedData.personList[0]
print(person1)
}
}
But it's not the best way, but you can't explain what you need. Hopes you understand, that this code makes singleton from StoreData. And it will work for you task.
I have a custom class in Swift and I'd like to use subscripting to access its properties, is this possible?
What I want is something like this:
class User {
var name: String
var title: String
subscript(key: String) -> String {
// Something here
return // Return the property that matches the key…
}
init(name: String, title: String) {
self.name = name
self.title = title
}
}
myUser = User(name: "Bob", title: "Superboss")
myUser["name"] // "Bob"
Update: The reason why I'm looking for this is that I'm using GRMustache to render from HTML templates. I'd like to be able to just pass my model object to the GRMustache renderer…
GRMustache fetches values with the keyed subscripting objectForKeyedSubscript: method and the Key-Value Coding valueForKey: method. Any compliant object can provide values to templates.
https://github.com/groue/GRMustache/blob/master/Guides/view_model.md#viewmodel-objects
This is a bit of a hack using reflection. Something along the lines of the following could be used.
protocol PropertyReflectable { }
extension PropertyReflectable {
subscript(key: String) -> Any? {
let m = Mirror(reflecting: self)
for child in m.children {
if child.label == key { return child.value }
}
return nil
}
}
struct Person {
let name: String
let age: Int
}
extension Person : PropertyReflectable {}
Then create a Person and access it's keyed properties.
let p = Person(name: "John Doe", age: 18)
p["name"] // gives "John Doe"
p["age"] // gives 18
You could modify the subscript to always return an interpolated string of the property value.
Adding some syntax sugar to Benzi's answer:
protocol PropertyReflectable { }
extension PropertyReflectable {
subscript(key: String) -> Any? {
let m = Mirror(reflecting: self)
return m.children.first { $0.label == key }?.value
}
}
struct Person: PropertyReflectable {
let name: String
let age: Int
}
Then create a Person and access it's keyed properties.
let p = Person(name: "John Doe", age: 18)
p["name"] // gives "John Doe"
p["age"] // gives 18
Using valueForKey should enable you to access properties using their names. Be sure that you're working with a object that inherit NSObject
class people: NSObject {
var age: NSString = "44"
var height: NSString = "153"
}
let person:people = people()
let stringVariable = "age"
person.valueForKey("age")
// Print "44"
person.valueForKey("\(stringVariable)")
// Print "44"
(GRMustache author here)
Until a swift-oriented Mustache library is out, I suggest having your classes inherit from NSObject (so that they have the valueForKey: method). GRMustache will then fetch values with this method.
In case this would still not work (blank values in the rendering), you may try to disable GRMustache security features (see https://github.com/groue/GRMustache/blob/master/Guides/security.md#disabling-safe-key-access)
Should you experience any other trouble, please open an issue right into the repository: https://github.com/groue/GRMustache/issues
EDIT February 2, 2015: GRMustache.swift is out: http://github.com/groue/GRMustache.swift
Shim's answer above doesn't work anymore in Swift 4. There are two things you should be aware of.
First of all, if you want to use value(forKey:) function, your class must inherit NSObject.
Secondly, since Objective-C doesn't know anything about value type, you have to put the #objc keyword in front of your value type properties and Swift will do the heavy-lifting for you.
Here is the example:
import Foundation
class Person: NSObject {
#objc var name: String = "John Dow"
#objc var age: Int = 25
#objc var height: Int = 180
subscript(key: String) -> Any? {
return self.value(forKey: key)
}
}
let person: Person = Person()
person["name"] // "John Dow"
person["age"] // 25
person["height"] // 180
I suppose you could do:
class User {
let properties = Dictionary<String,String>()
subscript(key: String) -> String? {
return properties[key]
}
init(name: String, title: String) {
properties["name"] = name
properties["title"] = title
}
}
Without knowing your use case I would strongly advise against doing this.
Another approach:
class User {
var name : String
var title : String
subscript(key: String) -> String? {
switch key {
case "name" : return name
case "title" : return title
default : return nil
}
}
init(name: String, title: String) {
self.name = name
self.title = title
}
}
It might be worth noting that Swift doesn't appear to currently support reflection by names. The reflect function returns a Mirror whose subscript is Int based, not String based.