I'm working through Swift apprentice 3rd Edition.
The book quickly skips through required initialisers.
If I have a super class :
class Animal {
let animalType: String
required init(animalType: String) {
self.animalType = animalType
}
convenience init(animal: Animal) {
self.init(animalType: animal.animalType)
}
}
and then subclass like:
class WildAnimal: Animal {
let name: String
required init(animalType: String) {
name = ""
super.init(animalType: animalType)
}
}
The only way I can initialise the subclass is to assign placeholder values in the required init. i.e. I had to put name = "".
Is there no way to initialise a subclasses parameters during initialisation if the parent class has a required init?
You can have more than one initializer. You just also have to "override" the required parent initializers.
class WildAnimal: Animal {
var name: String
required init(animalType: String) {
name = ""
super.init(animalType: animalType)
}
init(animalType: String, name: String) {
self.name = name
super.init(animalType: animalType)
}
}
Forcing subclasses to define de required initializers is the point of required.
Write the required modifier before the definition of a class
initializer to indicate that every subclass of the class must
implement that initializer
Source
Related
I have a protocol and a class which I want to extend. The protocol requires field of some type and the class has a field with the same name and the type as Implicitly Unwrapped Optional of this type.
Can this class be extended by this protocol? If yes, then how?
If I try to write an extension, Xcode give an error of not conforming. But if I add the field into the extension, it gives an error of redeclaration.
protocol Named {
var name: String { get }
}
class Person {
var name: String!
}
extension Person: Named {
// Type 'Finances.Account' does not conform to protocol 'Named'
}
Property names and types declared in a protocol must exactly be matched by the conforming classes.
So you cannot resolve the error without changing the property type in either the protocol or the conforming type. You could also rename one of the properties and add the matching property to the conforming type as a new field.
So either do:
protocol Named {
var name: String { get }
}
class Person {
var name: String
init(_ name:String) {
self.name = name
}
}
extension Person: Named {
}
Or
protocol Named {
var name: String { get }
}
class Person {
var _name: String!
}
extension Person: Named {
var name: String {
return _name
}
}
As #user28434 pointed out, there's a(n ugly) workaround. You can create a wrapper protocol that matches the optionality of the Person class, make that protocol inherit from the original protocol, declare the non-optional variable in an extension on the new protocol and make Person conform to the new protocol instead of the original Named.
protocol Named {
var name: String { get }
}
class Person {
var name: String!
}
protocol Namedd: Named {
var name: String! { get }
}
extension Namedd {
var name: String {
return name!
}
}
extension Person: Namedd {
}
I'm trying to access a default method implementation defined in a protocol extension that's constrained in implementation to a class. A 'regular' declaration works fine, however when I try to cast to the protocol I can not access the default defined method on the protocol, though the type satisfies the where clause.
Please consider this example:
class Person {
var name: String
init(name: String) {
self.name = name
}
}
class Hero: Person {
var ability: String
init(name: String, ability: String) {
self.ability = ability
super.init(name: name)
}
}
class Pilot: Person {
var callSign: String
init(name: String, callSign: String) {
self.callSign = callSign
super.init(name: name)
}
}
class Programmer: Person {
var favoriteLanguage: String
init(name: String, favoriteLanguage: String) {
self.favoriteLanguage = favoriteLanguage
super.init(name: name)
}
}
// define a protocol
protocol PersonPresenter: class { }
// extend it where the conformer is a type of Person
extension PersonPresenter where Self: Person {
func displayName() {
print(name.uppercased())
}
}
// conform subclasses of Person to PersonPresenter
extension Hero: PersonPresenter { }
extension Pilot: PersonPresenter { }
extension Programmer: PersonPresenter { }
let myHero = Hero(name: "Hiro", ability: "Bend time & space")
myHero.displayName() // prints 'HIRO'
let myPilot = Pilot(name: "Pete", callSign: "Maverick")
myPilot.displayName() // prints 'PETE'
let myProgrammer = Programmer(name: "Chris", favoriteLanguage: "Swift")
myProgrammer.displayName() // prints 'CHRS'
let people: [Person] = [myHero,myPilot,myProgrammer]
if let presenter = people[0] as? PersonPresenter {
presenter.displayName() // Errror, PerseonPresenter is not a subtype of 'Person'
}
I would like to find a way to cast to PersonPresenter while satisfying the where constraint so as not to be forced to try and cast to each specific sub-class to access the default implementation of the protocol extension. Or to not have to conform the super class (which may be used in many other places) to the protocol.
The real problem is that you are extending an empty protocol.
By casting to Hero, the compiler knows it is a subclass of Person and then it satisfies the extension constraint.
But when casting to the protocol PersonPresenter itself, the compiler doesn't know if the constraint (being a Person) can be satisfied.
If you declare the requirement in the protocol, it will work:
protocol PersonPresenter: class {
func displayName()
}
There are two parts of code, there result seems same in the *.playground, So that make me a little confused, so what the exactly difference between designated and convenience init in this code below, and how could I know when should I use a convenience init rather than a designated init?
Part 1
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
let food = Food()
let food2 = Food(name: "Rice")
Part 2
class Food {
var name: String
init(name: String) {
self.name = name
}
init() {
self.name = "[Unnamed]"
}
}
let food = Food()
let food2 = Food(name: "Rice")
In part 1 and part 2 the different part are
//part 1
convenience init() {
self.init(name: "[Unnamed]")
}
//part 2
init() {
self.name = "[Unnamed]"
}
The difference arises in subclasses; in what you've shown there is no usage difference.
“Convenience initializers must always delegate across” meaning in class A a convenience initializers calls a designated initializers in class A. “Designated initializers must always delegate up” meaning class B, a subclass of A, calls designated initializers in A.
Thus, in your 'part 2' both of the initializers in Food are available to subclasses of Food. But, in your 'part 1', only one initializer can be used in subclasses.
For example (Food in the following is your 'part 1'):
21> class Fat : Food {
22. init () {
23. super.init()
24. }
25. }
repl.swift:23:9: error: must call a designated initializer of the superclass 'Food'
super.init()
^
Quotes are: Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/us/jEUH0.l
I am trying to consume an API where every object names its ID field differently. Example: Group.groupid, Team.teamid, etc.
I have a BaseAPIObject that has a required initializer that accepts a parsed JSON dictionary and a convenience initializer that takes just the ID field (the only required property of my class).
I've dealt with the changing id field names by adding a static aka "class" method that returns the ID field name and subclasses override that function to return their own field name.
The problem I have is that in my base class' convenience initializer I can't call self.dynamicType before I've called self.init() but I need the results of that static class function before I can properly construct my object.
public class BaseAPIObject {
var apiID: String!
var name: String?
var createdBy: String?
var updatedBy: String?
//Construct from JSONSerialization Dictionary
required public init(_ data: [String: AnyObject]) {
name = data["name"] as String?
createdBy = data["created_by"] as String?
updatedBy = data["updated_by"] as String?
super.init()
let idName = self.dynamicType.idFieldName()
apiID = data[idName] as String?
}
/// Creates an empty shell object with only the apiID set.
/// Useful for when you have to chase down a nested object structure
public convenience init(id: String) {
// THIS is what breaks! I can't call self.dynamicType here
// How else can I call the STATIC CLASS method?
// in ObjC this would be as simple as self.class.getIdFieldName()
let idFieldName = self.dynamicType.getIdFieldName()
let data = [idFieldName: id]
self.init(data)
}
//This gets overridden by subclasses to return "groupid" or whatever
class func idFieldName() -> String {
return "id"
}
}
Question: How can I solve the problem of calling a subclass' class function before I run init on the instance itself?
Instead of creating a class function for figuring out the id, create init functions instead. Since you already have to create one of these functions per subclass, you're not really losing anything. The subclasses init function then calls the super's init with the id name.
Here's an example, I changed some of the properties of your group just for the sake of making the example simple to illustrate the concept.
public class BaseAPIObject {
var objData: [String:String]
required public init(_ data: [String: String]) {
println("Data: \(data)")
self.objData = data
}
public convenience init(id: String, idFieldName: String) {
let data = [idFieldName: id]
self.init(data)
}
}
And then in your subclass, just conceptually something like this:
public class GroupObject: BaseAPIObject {
public convenience init (id: String) {
self.init(id: id, idFieldName: "group")
}
}
let go = GroupObject(id: "foo")
println(go.objData["group"]!) //prints "foo"
I have a class called Letter
class Letter
{
init() {}
}
And I have an extension for this class:
extension Letter
{
convenience init(file_path:String) {
self = Letter.loadFromFile(file_path)
}
class func loadFromFile(file_path:String)->Letter {...}
}
I need to create and init with path to file and when i call Letter(file_path) I need a new object that returned by a func loadFromFile. How to assign in an init method or to return a new object?
It gives the error:
Cannot assign to value: 'self' is immutable
Class functions that return instances of that class seems to be an anti-pattern in Swift. You'll notice that the "with" Objective-C class methods like [NSString stringWithString:#"some other string"] made the transition as "with"-less convenience initializers: NSString(string: "some other string").
Furthermore, you'll need to delegate to a designated initializer from within a convenience initializer.
Also, since you're 1) defining the original class and 2) don't need the convenience initializer scoped differently than the designated initializer, I don't see any reason to place it in an extension.
Putting those together:
class Letter {
init() { … }
convenience init(filePath: String) {
self.init()
loadFromFile(filePath)
}
func loadFromFile(filePath: String) { … }
}
let letter1 = Letter()
letter1.loadFromFile("path1")
let letter2 = Letter(filePath: "path2")
In summary, the analogy for assigning to self in Swift is calling an initializer.
Let me know if this works for you!
Convenience initializer must delegate up to designated initializer
It says that convenience init(file_path:String) should call other initialiser
convenience init(file_path:String) {
self.init()
//here can set other properties
}
Convenience initialiser usually provide some default parameters
Convenience initialiser are designed to make creation of class instance less complicated. It means that you don't need to pass all arguments to constructor. In your example the class should look like this
Designated initializer takess all possible arguments.
Convenience provide default value
Code example
// Create instance of a Letter
Letter()
Letter(file_path: "path.txt")
Letter(file_path: "path.txt", option: 0, other: 0)
//Class Implementation
class Letter
{
init(file_path: String , option: Int, other: Int) {
// Instansiate class
}
}
extension Letter {
convenience init() {
self.init(file_path:"a")
}
convenience init(file_path:String) {
self.init(file_path: file_path , option: 0, other: 0)
}
class func loadFromFile(file_path:String) -> Letter {
return Letter()
}
}
Now you can create instance of Letter this way -
You can't assign to self. What about something like this:
class Letter {
}
extension Letter {
convenience init(filePath: String) {
self.init()
// code to load a Letter from a file goes here.
}
class func loadFromFile(filePath: String) -> Letter {
return Letter(filePath: filePath)
}
}