TruMan1
TruMan1

Reputation: 36098

One-step object creation and property initialization in Swift?

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?

Upvotes: 16

Views: 4982

Answers (5)

Alain T.
Alain T.

Reputation: 42133

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() }
   ]

Upvotes: 3

Dan Beaulieu
Dan Beaulieu

Reputation: 19954

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")

Upvotes: 7

Aaron Rasmussen
Aaron Rasmussen

Reputation: 13316

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.

Upvotes: 0

Jonah
Jonah

Reputation: 17958

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"})

Upvotes: 13

Leo Dabus
Leo Dabus

Reputation: 236380

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

Upvotes: 8

Related Questions