I'm trying to figure out the best pattern to work with objects in Swift.
i think i got it right with the initializers, both convenience and default... but what happen with the class factory methods?
I tried to create a simple class Person and a subclass Student, with few properties and methods. is it the most correct way to do it?
class Person{
var _name: String
var _surname: String
var _dateOfBirthday: String
var _phoneNumb: [String]
init(name:String, surname:String, dateOfBirthday:String, phone:[String]){
self._name = name
self._surname = surname
self._dateOfBirthday = dateOfBirthday
self._phoneNumb = phone
}
convenience init() {
self.init(name:"",surname:"",dateOfBirthday:"", phone:[])
}
convenience init(name:String){
self.init(name:name,surname:"",dateOfBirthday:"", phone:[])
}
}
class Student:Person{
var _studentId:Int
init(name: String, surname: String, dateOfBirthday: String, phone: [String], id:Int) {
self._studentId = id
super.init(name: "", surname: "", dateOfBirthday: "", phone: [])
}
convenience init(){
self.init(name: "", surname: "", dateOfBirthday: "", phone: [], id:0)
}
convenience init(name:String){
self.init(name:name,surname:"",dateOfBirthday:"", phone:[], id:0)
}
}
what if i want to add a class factory method? would it be something like this or i'm doing it wrong?
class func Person() -> Person {
var x = Person()
x._telephoneNumber = [String]() // is this needed? or i can initialize it later?
return x
}
class func PersonWithName(name:String) -> Person {
var x = Person(name:name, surname:"", dateOfBirthday:"", telephoneNumber:[])
return x
}
is this correct? why would it be better to use the init instead of the class factory?
is this correct? why would it be better to use the init instead of the class factory?
Why would you create a "class factory" if you can use init? init is idiomatic Swift way of creating new objects of a class.
Adding convenience initializers is the right choice in most cases when you want to add a shortcut to class's main (designated) initializer. However, in your case, they are completely unnecessary, because Swift supports default argument values.
Just define your initializer like so:
init(name:String = "", surname:String = "", dateOfBirthday:String = "", phone:[String] = []) { ... }
This way, you can invoke it as Person() or Person(name: "Andrew") or with any other combination of arguments.
EDIT:
As a side note, prefixing instance variables with an underscore generally doesn't seem to be idiomatic Swift. It's okay to omit the underscore and use self. to disambiguate between local and instance variables:
self.name = name
self.surname = surname
self.dateOfBirthday = dateOfBirthday
self.phoneNumb = phone
Prior to the recent Xcode 6.1 and Swift 1.1, it was necessary to use factory pattern if construction could fail because init() could not return optionals. This was also why many cocoa/objective-c libraries imported had factory methods.
With the release of Xcode 6.1 and Swift 1.1 and the support for init() that can return optionals, you should use the init() pattern with convenience initializers. With this release, Apple also changed their cocoa/objective-c imports to use the init() -> T? pattern over the factory method.
See https://developer.apple.com/library/ios/releasenotes/DeveloperTools/RN-Xcode/Chapters/xc6_release_notes.html for the release notes.
Related
I never used Swift4 before, and dont know how to use KVC in it.
I try to create model with Dictionary, here the code:
class Person : NSObject {
var name: String = ""
var age: Int = 0
init(dict: [String : Any]) {
super.init()
self.setValuesForKeys(dict)
}
}
let dict: [String : Any] = ["name" : "Leon", "age" : 18]
let p = Person(dict: dict)
print(p.name, p.age)
There I get two question:
1. Why dont I using AnyObject? "Leon"and18 was infer to String and Int, does it using in KVC?
2. #objc var name: String = "" , this form is worked, but I can not understand it.
Thanks for all helps.
To implement KVC support for a property in Swift 4, you need two things:
Since the current implementation of KVC is written in Objective-C, you need the #objc annotation on your property so that Objective-C can see it. This also means that the property's type needs to be compatible with Objective-C.
In addition to exposing the property to Objective-C, you will need to set up your notifications in order for observers to be notified when the property changes. There are three ways to do this:
For stored properties, the easiest thing to do is to add the dynamic keyword like so:
#objc dynamic var foo: String
This will allow Cocoa to use Objective-C magic to automagically generate the needed notifications for you, and is usually what you want. However, if you need finer control, you can also write the notification code manually:
#objc private static let automaticallyNotifiesObserversOfFoo = false
#objc var foo: String {
willSet { self.willChangeValue(for: \.foo) }
didSet { self.didChangeValue(for: \.foo) }
}
The automaticallyNotifiesObserversOf<property name> property is there to signify to the KVC/KVO system that we are handling the notifications ourselves and that Cocoa shouldn't try to generate them for us.
Finally, if your property is not stored, but rather depends on some other property or properties, you need to implement a keyPathsForValuesAffecting<your property name here> method like so:
#objc dynamic var foo: Int
#objc dynamic var bar: Int
#objc private static let keyPathsForValuesAffectingBaz: Set<String> = [
#keyPath(foo), #keyPath(bar)
]
#objc var baz: Int { return self.foo + self.bar }
In the example above, an observer of the baz property will be notified when the value for foo or the value for bar changes.
Everything was right for me but still I was getting this error, then I solved it by
Enabling the "Inherit module from Target" in the Identity Inspector.
or
Giving the module(usually folder name/group name) in which it is present from the Identity Inspector under Custom Class
your code
var name: String = ""
var age: Int = 0
you can write the same code like this, when you are sure about its type.
var name = ""
var age = 0
Any is generally used for all types(function types and optional types), while AnyObject is used for Class types.
#objc have different meaning, when you use #objc in your swift class, that code is available in objective-c. You should use #objc attribute to specify same as objective-c class, in result older archives can be replaced by new swift class.
I am creating a simple User object that inherits from RealmSwift's Object:
import Foundation
import RealmSwift
class User: Object {
#objc dynamic var userId: String = ""
#objc dynamic var email: String = ""
convenience init(userId: String, email: String) {
self.init()
self.userId = userId
self.email = email
}
When building, I receive the following error Missing argument for parameter 'userId' in call.
Am I missing something here?
Why use a convenience initializer? You can initialize Users with Realm's default initializer,
let user = User(value: ["userID": "1234", "email": "email#gmail.com"])
As it stands, you haven't written an initializer which accepts 0 arguments, but you are currently calling it...
You can remove the word "convenience", and change the line:
self.init()
to
super.init()
and the error will go away, and the object will be properly initialized.
This is because the superclass Object does have an initializer which takes no arguments.
I have a small problem in swift. Let's say I have a class called Pet.
Pet has a variable for name and noise, created like so:
class Pet
{
var name : String = ""
var canMakeNoise : Bool = true
}
Now, when I call initialise the class creating let's say a cat, I can easily do it like so:
var cat: Pet()
cat.name = "Garfield"
cat.canMakeNoise = false
This works smoothly, however when trying to pass it in directly using literal values like so:
let cat : Pet("Garfield",true)
or
let cat : Pet(name:"Garfield",canMakeNoise:true)
I get this error:
Swift Compile Error - Extra Argument in call
Why is that? How can I fix it? Thanks in advance.
If you want to add arguments to the initializer than you need to specify a new init function instead of relying on the default one. Here's how you'd do it in your case:
class Pet {
var name : String = ""
var canMakeNoise : Bool = true
init( name : String, canMakeNoise : Bool ) {
self.name = name
self.canMakeNoise = canMakeNoise
}
}
var kitty = Pet(name: "Cat", canMakeNoise: true)
Since you use default values for your variables, xCode does not find a need for initializer. So when you create a new instance of the class it will be a Pet with name = ”” and canMakeNoice = true.
If you want to change these default values you will need to supply a init method (and you can than remove these default values)
class Pet{
var name:String
var canMakeNoise:Bool
init(name:String, canMakeNoise:Bool){
self.name = name
self.canMakeNoise = canMakeNoise
}
convenience init (name:String){
self.name = name
self.init(name:name, canMakeNoise: true)
}
}
I’ve supplied two init methods here. The first one takes 2 arguments:
var cat: Pet = Pet(name: "Kitten", canMakeNoise: true)
This will create a Pet with the supplied name and the supplied canMakeNoise.
If you want to you can also supply a convenience init This is a shortened init. In this case used to be able to make another instance of the Pet class.
var dog: Pet = Pet(name: "Doggy")
As you see we don’t supply the canMakeNoise property here cause the convenience initializer does that for us (and uses true as the canMakeNoice)
I created this class for my object City
class City: NSObject {
var _name:String = ""
var name:String {
get {
return _name
}
set (newVal) {
_name = newVal
}
}
}
then when I create my object I do:
var city:City!
city.name = "London" //crash here
println("name city is\(city.name)");
it crash when I set the name with message "fatal error: unexpectedly found nil while unwrapping an Optional value"
This is not actually an answer (see other answers for a solution, such as #Greg's and #zelib's), but an attempt to fix some mistakes I see in your code
No need to create computed + stored property (unless you have a reason for that):
class City: NSObject {
var name: String = ""
}
If you inherit from NSObject, you automatically lose all swift features - avoid it (unless you have a reason for that)
class City {
var name: String = ""
}
You are using an empty string as absence of value - swift provides optionals for that
class City {
var name: String?
}
Alternative to 3., a city without a name wouldn't make much sense, so you probably want each instance to have a name. Use non optional property and an initializer:
class City {
var name: String
init(name: String) {
self.name = name
}
}
Avoid implicitly unwrapped optionals (unless you have a reason for that):
var city: City
Just like any other object oriented programming language, and object should be initialized before accessing it.
Like:
var city:City
This is just reference of the object. So, actual memory is not created here. You need to create actual object for City Class.
Fix it by adding following statement:
city = City()
You haven't initialised the city variable and when you trying to use it it crash.
initialise it first before you use it:
city = City()
city.name = "London"
You are getting error because you are not initializing your city variable instead you just implicitly unwrap it without initializing at any stage. To initialize it you must use the following code
var city:City = City()
You have to call the init method. So you would do it like this :
var city:City=City() //Calls init and creates an instance
city.name="foo"
If you don't define an init method(it's always a good practice that you do), the default init method is called.
I wanted to know if there was a way to call an initializer by only having the class name in Swift.
class Shape {
let name: String
var numberOfSides: Int?
init(name: String) {
self.name = name
}
}
Pseudo code:
let shapeClass = stringToClass("Shape")
let shape: Shape = shapeClass(name: "Something")
More than just trying to call a class function, you are trying to call an initializer dynamically. To be able to call an initializer dynamically on a class that you get at runtime, the compile-time type of that class value must be a metatype of a class that has that initializer; but since initializers are not always inherited in Swift, that initializer must be declared required.
Here we use NSClassFromString to get a class from a string. (I am declaring the classes as #objc with an explicit name, because otherwise Swift class names are mangled from the perspective of Objective-C and it's would be a pain to deal with.) However, it returns AnyClass (i.e. AnyObject.Type), which is a metatype whose values are any class. We want it to be restricted to classes that inherit from Shape, so we can use Shape's initializer, so we cast it to Shape.Type (the metatype whose values are classes that descend from Shape, inclusive).
I also demonstrate that it is polymorphic as it works with the name of a subclass by simply changing the name.
import Foundation
#objc(Shape)
class Shape {
let name: String
var type: String
required init(name: String) {
self.name = name
self.type = "Shape"
}
}
#objc(SubShape)
class SubShape : Shape {
required init(name: String) {
super.init(name: name)
self.type = "SubShape"
}
}
let shapeClass = NSClassFromString("SubShape") as Shape.Type
let shape: Shape = shapeClass(name: "Something")
println(shape.type) // prints "SubShape"