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

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?

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.

Related

Extend completionHandler of Document.close

I'd like to extend the Document.close function to first run some of my own code when the completionHandler triggers before calling the completionHandler that was passed to the function by the user. So something like this:
override func close(completionHandler: ((Bool) -> Void)? = nil) {
super.close(completionHandler: { (success) in
// TODO: do something AFTER writeContents has completed
// TODO: call completionHandler
})
}
So far so good, but I'm not able to call the completionHandler that was passed to the function. Using completionHandler() causes the compiler to complain about Cannot call value of non-function type '((Bool) -> Void)?'. A few StackOverflow searches later it sounds like we have to use the #escaping annotation:
override func close(completionHandler: #escaping ((Bool) -> Void)? = nil) {
super.close(completionHandler: { (success) in
// TODO: do something AFTER writeContents has completed
// TODO: call completionHandler
})
}
but the compiler complains again: #escaping attribute only applies to function types. This leads us to other questions like Swift 3 optional escaping closure parameter, but we are not able to change the function definition because it's overriding an (Objective-C) system library.
Am I misunderstanding something? If not, is there a way to workaround this limitation?
You can pass completion as below,
class Document: UIDocument {
override func close(completionHandler: ((Bool) -> Void)? = nil) {
let completion: ((Bool) -> Void)? = { success in
print("I got \(success)")
// TODO: do something AFTER writeContents has completed
//....
//....
//....
// TODO: call completionHandler
completionHandler?(success)
}
super.close(completionHandler: completion)
}
}

Optional #escaping Closure?

I want to use Optional #escaping closures to execute something when the closure is called in the function, but if i make it optional like that:
func checkForFavorites(_ completion: (#escaping (Bool) -> Void)?) {
}
i get this error
#escaping attribute may only be used in function parameter position
This may be an easy question so please have understanding

How do I cast an __NSMallocBlock__ to its underlying type in Swift 3?

I had a trick to help test UIAlertController that worked in Swift 2.x:
extension UIAlertController {
typealias AlertHandler = #convention(block) (UIAlertAction) -> Void
func tapButtonAtIndex(index: Int) {
let block = actions[index].valueForKey("handler")
let handler = unsafeBitCast(block, AlertHandler.self)
handler(actions[index])
}
}
This fails under Swift 3.x with fatal error: can't unsafeBitCast between types of different sizes, which tempts me to believe there might be a way to make the cast work. Can anyone figure it out?
Found a solution that works in Swift 3.0.1
extension UIAlertController {
typealias AlertHandler = #convention(block) (UIAlertAction) -> Void
func tapButton(atIndex index: Int) {
if let block = actions[index].value(forKey: "handler") {
let blockPtr = UnsafeRawPointer(Unmanaged<AnyObject>.passUnretained(block as AnyObject).toOpaque())
let handler = unsafeBitCast(blockPtr, to: AlertHandler.self)
handler(actions[index])
}
}
}
(Originally, the block value was the actual block, not a pointer to the block—which you obviously can't cast to a pointer to AlertHandler)
My answer is based on #Robert Atkins's, but shorter.
The problem here is that, valueForKey returns a Any typed object, and because in Swift,
MemoryLayout<Any>.size == 32
MemoryLayout<AnyObjcBlockType>.size == 8
an assertion will be triggered in unsafeBitCast when casting between types of different sizes.
One work-around is to create an intermediate wrapper and transform back to raw pointer, which satisfies MemoryLayout<UnsafeRawPointer>.size == 8.
A much simpler way is to create an indirect reference directly using protocol AnyObject, relying on the fact that MemoryLayout<AnyObject >.size == 8, we can write following valid code:
typealias AlertHandler = #convention(block) (UIAlertAction) -> Void
func tapButton(atIndex index: Int) {
if let block = actions[index].value(forKey: "handler") {
let handler = unsafeBitCast(block as AnyObject, to: AlertHandler.self)
handler(actions[index])
}
}
If your UIAlertController is an action sheet you can modify Robert's answer to dismiss the UIAlertController before you executed the handler.
dismiss(animated: true, completion: {() in handler(self.actions[index])})
I was using this extension for testing and without this modification my assertions for presented view controller were failing.

Unable to compile Siri extension in Swift 2.3

I want to integrate the Siri/Intent extension in my App, currently based on Swift 2.3. I am using the Xcode 8 beta 6 build.
The app fails to even compile, giving out errors as
Type 'IntentHandler' does not conform to protocol 'INSendMessageIntentHandling'
Protocol requires function 'handle(sendMessage:completion:)' with type '(sendMessage: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) -> Void'
Objective-C method 'handleWithSendMessage:completion:' provided by method 'handle(sendMessage:completion:)' does not match the requirement's selector ('handleSendMessage:completion:')
Even the sample app from Apple, UnicornChat fails to compile with the same errors. I did change the SWIFT_VERSION flag to Yes, to support Swift 2.3
Sample Code:
func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
let response = INSendMessageIntentResponse(code: .Success, userActivity: userActivity)
completion(response)
}
This fails with the error above.
Anyone has an idea on how to make Siri work in Swift 2.3?
So, this helped
https://forums.developer.apple.com/message/174455#174455
We need to explicitly define the #objc method signatures with the Swift ones. As in the following works.
#objc (handleSendMessage:completion:)
func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {
//..
}
Apparently Xcode 8 beta 6 has been updated to Swift 3 and the call back needs #escaping added to the method:
func handle(sendMessage intent: INSendMessageIntent, completion: #escaping (INSendMessageIntentResponse) -> Swift.Void) {
//..
}
If the Siri delegate methods have been updated to Swift 3 (as appears to be the case) then I am afraid you will have to use Swift 3 as well, or an older version of the SDK.
While compiling your code with Swift 2.3, you need to set Use Legacy Swift Language Version to YES for Siri and SiriUI targets.
Also, when IntentHandler and IntentViewController are created, methods might have been created in Swift 3 (as happened to me). So convert the classes to Swift 2.3, and use #objc method signatures along with the delegates.
For IntentHandler,
#objc (handleSendMessage:completion:)
func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {
}
#objc (handleSearchForMessages:completion:)
func handle(searchForMessages intent: INSearchForMessagesIntent, completion: (INSearchForMessagesIntentResponse) -> Void) {
}
#objc (handleSetMessageAttribute:completion:)
func handle(setMessageAttribute intent: INSetMessageAttributeIntent, completion: (INSetMessageAttributeIntentResponse) -> Void) {
}
For IntentViewController,
#objc (configureWithInteraction:context:completion:)
func configureWithInteraction(interaction: INInteraction!, context: INUIHostedViewContext, completion: ((CGSize) -> Void)!) {
}
Hope this helped.

"Does not conform to protocol" error when extending different class

I'm attempting to test my own class by injecting objects that adapt the URLSession and URLSessionDataTask protocols. I'm extending NSURLSession and NSURLSessionDataTask to adopt those protocols so that I can use the existing objects normally, but use test objects in unit tests.
I have the following code, with the error commented:
typealias SessionHandler = (NSData?, NSURLResponse?, NSError?) -> Void
protocol URLSession {
func dataTaskWithURL(url: NSURL, completionHandler: SessionHandler) -> URLSessionDataTask
}
protocol URLSessionDataTask {
}
// Type 'NSURLSession' does not conform to protocol 'URLSession'
extension NSURLSession : URLSession {}
extension NSURLSessionDataTask : URLSessionDataTask {}
I understand the error, my protocol doesn't exactly match the method as implimented by NSURLSession. How do I fix this?
What I ended up doing was creating a protocol extension that creates the necessary method that NSURLSession requires.
extension NSURLSession : URLSession {
func dataTaskWithURL(url: NSURL, completionHandler: SessionHandler) -> URLSessionDataTask {
return dataTaskWithURL(url, completionHandler: completionHandler) as NSURLSessionDataTask
}
}