16

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?

TruMan1
  • 33,665
  • 59
  • 184
  • 335

5 Answers5

13

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"})
Jonah
  • 17,918
  • 1
  • 43
  • 70
8

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
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
6

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")
Dan Beaulieu
  • 19,406
  • 19
  • 101
  • 135
  • Ah hah that's great!! Didn't realize structs had this but not other types. Good to know!! Only thing is the Student class is out of my control buried in another library so I cannot modify its source. – TruMan1 Jan 23 '16 at 00:30
2

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() }
   ]
Alain T.
  • 40,517
  • 4
  • 31
  • 51
0

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.

Aaron Rasmussen
  • 13,082
  • 3
  • 42
  • 43
  • 1
    The Student class is out of my control and I cannot edit it. It resides in another framework. So I can only inherit or extend it. – TruMan1 Jan 23 '16 at 00:29