Klaudyna Marciniak
Klaudyna Marciniak

Reputation: 45

Error: Generic parameter 'T' could not be inferred.

I have a problem with calling this method:

func setUpFeedbackForm<T:UIViewController>(viewController: T,
                                        viewForScreenshot: UIView, 
                                        completionHandler: @escaping () -> ()) 
                                                  where T:FeedbackFormDelegate { ... }

inside this wrapper function:

public class func setUpFeedbackFormWrapper(viewController: UIViewController, 
                                        viewForScreenshot: UIView, 
                                        completionHandler: @escaping () -> ()) {

    setUpFeedbackForm(viewController: viewController,
                   viewForScreenshot: viewForScreenshot,
                   completionHandler: completionHandler)
}

I am getting error: Generic parameter 'T' could not be inferred. I do understand what the error means, but I don't know how to implement this call correctly. Also the reason why I have this wrapper is that I want to expose func setUpFeedbackForm to obj-c code and I can't import it directly to obj-c because of swift's generics.

Could you please show me the correct way of calling it?

Upvotes: 3

Views: 1000

Answers (1)

Cristik
Cristik

Reputation: 32783

You have two constraints on the viewController parameter that need to be satisfied when calling setUpFeedbackForm:

  • inheritance from UIViewController
  • conformance to FeedbackFormDelegate

, and setUpFeedbackFormWrapper satisfies only one, thus the compiler doesn't know what to do with the other one.

The problem is caused by a limitation of Swift which can't directly express variables/parameters that satisfy both class inheritance and protocol conformance, unless using generics, which breaks the Objective-C compatibility.

Thus a valid UIViewController<FeedbackFormDelegate> construct in Objective-C doesn't have a direct equivalent in Swift.

A workaround to this limitation is to declare a 3rd method that exposes the class inheritance and protocol conformance arguments as two distinct parameters, and call that method from both the Objective-C compatible and the Swift-only versions.

func setUpFeedbackForm<T:UIViewController>(viewController: T,
                       viewForScreenshot: UIView,
                       completionHandler: @escaping () -> ())
    where T:FeedbackFormDelegate {
        setupFeedbackFormImpl(viewController: viewController,
                              feedbackFormDelegate: viewController,
                              viewForScreenshot: viewForScreenshot, completionHandler: completionHandler)
}

func setupFeedbackFormImpl(viewController: UIViewController,
                           feedbackFormDelegate: FeedbackFormDelegate,
                           viewForScreenshot: UIView,
                           completionHandler: @escaping () -> ()) {
    // actual code here
}

public func setUpFeedbackFormWrapper(viewController: UIViewController,
                                     viewForScreenshot: UIView,
                                     completionHandler: @escaping () -> ()) {

    guard let feedbackFormDelegate = viewController as? FeedbackFormDelegate else { 
        // you can also report errors here, if you want to
        // forbid runtime calls with controllers that are not FeedbackFormDelegate
        return 
    }
    setupFeedbackFormImpl(viewController: viewController,
                          feedbackFormDelegate: feedbackFormDelegate,
                          viewForScreenshot: viewForScreenshot,
                          completionHandler: completionHandler)
}

If we think in terms of SOLID programming, then this workaround follows the Interface Segregation Principle, as we receive one argument for the view controller stuff, and another one for the delegate stuff, even if they point to the same object behind.

Upvotes: 2

Related Questions