Zapato33
Zapato33

Reputation: 53

Inheritance and initialization in swift

I have been trying to create two different classes: for the superclass I create my properties and init() function with the appropriate parameters. However, when I create my subclass with its own properties and init() function I get into trouble.

What I'm trying to achieve here is that at some point in my app, the user will enter a bunch of fields and when he/she is ready and clicks the 'ready' button, the content of the fields will be used as arguments for my init() function of the subclass. The superclass's init() has less parameters than the init() of the subclass.

I've been reading about designated and convenience initializer but I'll a bit confused. Maybe I don't really need them, I'm not sure.

What i'm trying to avoid is having to change the value of any of the properties of the subclass manually after the call to init().

Any help is much appreciated.

/// NEW CODE:

class Habit {

enum HabitType {
    case health
    case wealth
    case social
    case career
    case noType
}

var habitID: Int
var title: String
var type: HabitType
var duration: Int
var toAccomplish = [String]()
var currentFeelings = [String]()
var howToFix = [String]()

init?(habitID: Int, title: String, type: String, duration: Int, toAccomplish: [String]?, currentFeelings: [String]?, howToFix: [String]?) {

    self.habitID = habitID
    self.title = title
    switch type {
    case "health":
        self.type = HabitType.health
    case "wealth":
        self.type = HabitType.wealth
    case "social":
        self.type = HabitType.social
    case "career":
        self.type = HabitType.career
    default:
        self.type = HabitType.noType
    }
    self.duration = duration

    if let accomplish = toAccomplish {
        self.toAccomplish = accomplish
    }
    if let feelings = currentFeelings {
        self.currentFeelings = feelings
    }
    if let fix = howToFix {
        self.howToFix = fix
    } else {
        return nil
    }
}

}

class MiniHabit: Habit {

var trigger: String
var action: String

init(habitID: Int, title: String, type: String, duration: Int, toAccomplish: [String]?, currentFeelings: [String]?, howToFix: [String]?, trigger: String, action: String) {

    ////init() Error: A non-failable initializer cannot chain to failable initializer 'init(habitID:title:type:duration:toAccomplish:currentFeelings:howToFix:)' written with 'init?' 
    super.init(habitID: habitID, title: title, type: type, duration: duration, toAccomplish: toAccomplish, currentFeelings: currentFeelings, howToFix: howToFix)

    self.trigger = trigger
    self.action = action

}

}

Upvotes: 3

Views: 1060

Answers (3)

ABakerSmith
ABakerSmith

Reputation: 22959

New answer in response to updated code

It's looking better than last time, but there are still a few changes you could make:

Firstly, you don't need to use a switch statement to check which enum type type should be. You can use pass the enum type as the argument to the initialiser. For example, say this was your Habit class:

class Habit {
    enum HabitType {
        case Health
        case Wealth
        case Social
        case Career
    }

    let type: HabitType

    // Don't use a String here, use the HabitType enum.
    init(type: HabitType) {
        self.type = type
    }
}

You could then create an instance of Habit like so:

let aHabit = Habit(type: .Health)

See enumeration in The Swift Programming Language for more information.

Secondly, in regard to your HabitType enum, I see you've got a case NoType. This would be a perfect place to use optionals instead of using NoType. For example, you could declare the variable type to be an optional, then when it equals nil you know there was no type. For example, this would be your HabitType enum:

enum HabitType {
    case Health
    case Wealth
    case Social
    case Career
}

// And this is how could could define type to be optional.
let type: HabitType?

Thirdly, the error you're getting in MiniHabit's initialiser is because you've declared Habit's initialiser to be failable (denoted by the ? after init). This is not necessary in this situation and instead your initialiser could look like this:

// Not failable anymore. 
init(habitID: Int, title: String, type: String, duration: Int, toAccomplish: [String]?, currentFeelings: [String]?, howToFix: [String]?) {

    // Setup other variables

    if let accomplish = toAccomplish {
        self.toAccomplish = accomplish
    }
    if let feelings = currentFeelings {
        self.currentFeelings = feelings
    }
    if let fix = howToFix {
        self.howToFix = fix
    } 

    // No need to return nil if the user didn't supply a value for howToFix.
}

For more information on failable initialiser see here: https://developer.apple.com/swift/blog/?id=17

Hope that helps! Let me know if there's anything else that needs clarifying.

Upvotes: 1

ABakerSmith
ABakerSmith

Reputation: 22959

I made a few changes to your code, this is how it looks now:

// 1.
class Habit {
    var habitID: Int
    var title: String
    var type: String
    var duration: Int
    var toAccomplish = [String]()
    var currentFeelings = [String]()
    var howToFix = [String]()

    init(habitID: Int, title: String, type: String, duration: Int, toAccomplish: [String]?, currentFeelings: [String]?, howToFix: [String]?) {

        self.habitID = habitID
        self.title = title
        self.type = type
        self.duration = duration

        // 2.
        if let acomplish = toAccomplish {
            self.toAccomplish = acomplish
        }

        if let feelings = currentFeelings {
            self.currentFeelings = feelings
        }

        if let fix = howToFix {
            self.howToFix = fix
        }
    }

    class MiniHabit: Habit {
        // 3.
        var trigger: String
        var action : String

        init(habitID: Int, title: String, type: String, duration: Int, toAccomplish: [String]?, currentFeelings: [String]?, howToFix: [String]?, trigger: String, action: String) {
            // 4.
            self.trigger = trigger
            self.action = action

            super.init(habitID: habitID, title: title, type: type, duration: duration, toAccomplish: toAccomplish, currentFeelings: currentFeelings, howToFix: howToFix)
        }
    }
}

1. You probably don't need to declare your class as public. From the documentation:

Public access enables entities to be used within any source file from their defining module, and also in a source file from another module that imports the defining module. You typically use public access when specifying the public interface to a framework.

By default your classes will be have internal access, which should be fine if you're not intending to create a framework from these classes.

For more information see Access Control in The Swift Programming Language, link here: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html

2. Using optional binding, using if let, is preferred to checking if an optional is nil and then force unwrapping it. See The Swift Programming Language for more information on optional binding.

3. trigger and action don't have to be given initial values of empty strings as long as you set them before you call super init. Swift enforces that all properties defined in your current class must be initialised before super.init is called. Because you're setting them before your super.init call, could also use let instead of var for trigger and action if you wanted them to be immutable.

4. You're now only initialising the variables that MiniHabit defines, all the other variables are left to be setup by the designated initialiser on Habitat.

Also, I notice you have a type variable in Habit. If type should only be a select few values I would recommend using an enum, for example:

class Habit {
    enum HabitType {
        // Give these more appropriate names.
        case HabitType1
        case HabitType2
        case HabitType3
    }

    var type: HabitType

    // Other variables...
}

Hope that helps.

Upvotes: 4

Marcos Crispino
Marcos Crispino

Reputation: 8218

There are to things wrong with your code:

  1. You don't need the required keyword in the initializer. As stated in the documentation:

Write the required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer

  1. The properties you assign in the superclass' initializer should not be assigned in the subclass

This code works in a Playground (Xcode 6.3):

public class Habit {
    var habitID: Int
    var title: String
    var type: String
    var duration: Int
    var toAccomplish = [String]()
    var currentFeelings = [String]()
    var howToFix = [String]()

    public init(habitID: Int, title: String, type: String, duration: Int, toAccomplish: [String]?, currentFeelings: [String]?, howToFix: [String]?) {

        self.habitID = habitID
        self.title = title
        self.type = type
        self.duration = duration

        if toAccomplish != nil {
            self.toAccomplish = toAccomplish!
        }
        if currentFeelings != nil {
            self.currentFeelings = currentFeelings!
        }
        if howToFix != nil {
            self.howToFix = howToFix!
        }
    }
}

public class MiniHabit: Habit {
    var trigger: String = ""
    var action: String = ""

    public init(habitID: Int, title: String, type: String, duration: Int, toAccomplish: [String]?, currentFeelings: [String]?, howToFix: [String]?, trigger: String, action: String) {
        super.init(habitID: habitID, title: title, type: type, duration: duration, toAccomplish: toAccomplish, currentFeelings: currentFeelings, howToFix: howToFix)

        self.trigger = trigger
        self.action = action
    }
}

var h = MiniHabit(habitID: 0, title: "", type: "", duration: 1, toAccomplish: nil, currentFeelings: nil, howToFix: nil, trigger: "", action: "")

Upvotes: 1

Related Questions