Felix Jassler
Felix Jassler

Reputation: 1531

Initialising member to class function causes 'self' used in method call error

I have a class attribute that points to one of the class functions. However when I try to initialize this variable with one of the functions, I get the following error:

'self' used in method call before all stored properties are initialized.

I'm able to initialize any other variable to those functions, but the error makes it sound like I'm calling the function even though I'm not.

import UIKit
import AudioToolbox

class BeatMaker {

    // iPhone 7 and up use beat function, iPhone 6s use beatFallback
    let hapticFunction: () -> ()
    let impactGenerator = UIImpactFeedbackGenerator.init(style: .heavy)

    init(supportsImpactGenerator: Bool) {
        // error 1: 'self' used in method call 'beat' before all stored properties are initialized
        // error 2: 'self' used in method call 'beatFallback' before all stored properties are initialized
        self.hapticFunction = (supportsImpactGenerator) ? beat : beatFallback
    }

    private func beat() {
        impactGenerator.impactOccurred()
    }

    private func beatFallback() {
        AudioServicesPlaySystemSound(1520)
    }

    func makeABeat() {
        hapticFunction()
    }
}

In this specific case I want to make use of the Taptic Engine and simulate a click through the UIImpactFeedbackGenerator. The iPhone 6s doesn't support this engine, so I want to call a fallback function that produces a similar effect.

I also tried initializing the variable on the spot:

// this works
var hapticFunction: (BeatMaker) -> () -> () = beat

init(supportsImpactGenerator: Bool) {
    if !supportsImpactGenerator {
        // error: Cannot assign value of type '() -> ()' to type '(BeatMaker) -> () -> ()'
        self.hapticFunction = beatFallback

        // produces same error
        self.hapticFunction = beatFallback.self
    }
}

I know that I could make everything static or put everything out of the class, but this feels like it should work yet it doesn't. Am I missing something?

EDIT

Setting the type of type of hapticFunction to an optional seems to work, but this doesn't make any sense to me. What's the difference?

// this works
var hapticFunction: (() -> ())?

init(supportsImpactGenerator: Bool) {
    self.hapticFunction = (supportsImpactGenerator) ? beat : beatFallback
}

Upvotes: 7

Views: 1272

Answers (2)

Sajjon
Sajjon

Reputation: 9897

It might be better to not use a Bool, but rather a nested Enum, which is also more extendible if you wanna add some other modes of haptic feedback later on.

I have a generalized solution for a generalized problem of your question. So either you do:


public class FunctionOwner {

    private let mode: Mode

    public init(`do` mode: Mode = .default) {
        self.mode = mode
    }
}


public extension FunctionOwner {

    enum Mode {
        case foo, bar
    }

    func fooOrBar() {
        switch mode {
        case .foo: foo()
        case .bar: bar()
        }
    }
}

private extension FunctionOwner {
    func foo() {
        print("doing foo")
    }

    func bar() {
        print("doing bar")
    }
}

public extension FunctionOwner.Mode {
    static var `default`: FunctionOwner.Mode {
        return .foo
    }
}

// USAGE
FunctionOwner(do: .bar).fooOrBar() // prints "doing foo"
FunctionOwner(do: .foo).fooOrBar() // prints "doing bar"

Or if you for some reason do want to keep the stored Mode, you can do this (might be relevant for your actual question on how you do a workaround of referencing self in the init.):

public class FunctionOwner {

    private let _function: (FunctionOwner) -> Void

    public init(`do` mode: Mode = .default) {
        _function = { functionOwner in
            switch mode {
            case .foo: functionOwner.foo()
            case .bar: functionOwner.bar()
            }
        }
    }
}

public extension FunctionOwner {

    enum Mode {
        case foo, bar
    }

    func fooOrBar() {
        _function(self)
    }
}

// The rest of the code is the same as the example above

Upvotes: 4

Zhang
Zhang

Reputation: 394

There are two method to fix this

You can:

Give hapticFunction a initial value like {}

or fix like:

class BeatMaker {

    let impactGenerator = UIImpactFeedbackGenerator.init(style: .heavy)
    let supportsImpactGenerator: Bool

    init(supportsImpactGenerator: Bool) {
        self.supportsImpactGenerator = supportsImpactGenerator
    }

    private func beat() {
        if supportsImpactGenerator {
            impactGenerator.impactOccurred()
        } else {
            AudioServicesPlaySystemSound(1520)
        }
    }

    func makeABeat() {
        beat()
    }
}

Upvotes: 0

Related Questions