protocol extension inheriting from Obj-C optional protocol

I have a couple of view controllers that can send email and was hoping to cleanup the code using a protocol extension. Something like this:


protocol MailSender: class, MFMailComposeViewControllerDelegate {
    func composeMail(config: MFMailComposeViewController -> ())

    func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?)
    func dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?)
}

extension MailSender {
    func composeMail(config: MFMailComposeViewController -> ()) {
        let mailer = MFMailComposeViewController()
        mailer.mailComposeDelegate = self
        config(mailer)
        presentViewController(mailer, animated: true, completion: .None)
    }

// This method will never be called :(
    func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
        /* handle result */
        dismissViewControllerAnimated(true, completion: .None)
    }
}


In use it looks like:


class MyViewController: UIViewController, MailSender {
…
// when needed, compose mail…
composeMail() { m in
    m.setSubject("title")
    m.setMessageBody("message", isHTML: false)
}


It works (to an extent), the major issue is that the `MFMailComposeViewControllerDelegate` method never gets called. Only thing I can think of which might cause the issue is that the delegate method is optional, but why that would prevent things working, I'm not sure? (If I put the delegate method inside the view controllers, then everything works, but the point of the exercise is to reduce code duplication, so this isn't particularly helpful).


Any suggestions would be most welcome. Cheers!

You've got to use (self as MailSender), the angry cat head emoji, or something that functions the same; I've logged the bug.


http://stackoverflow.com/questions/31663560/how-can-you-provide-default-implementations-for-uipageviewcontrollerdatasource

If you explicitly mark the mailComposeController(didFinishWithResult:) method in the protocol extension with @objc, you'll get an error that tells you the real problem:

error: method in a protocol extension cannot be represented in Objective-C



Since the MFMailComposeViewControllerDelegate protocol only has the one method, another workaround would be to create a wrapper/proxy class to provide as the delegate, which just relays the delegate method.


protocol MailSender: class, MFMailComposeViewControllerDelegate
{
    func composeMail(config: MFMailComposeViewController -> ())
   
    func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?)
    func dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?)
}


@objc class MailDelegateProxy: NSObject, MFMailComposeViewControllerDelegate
{
    let sender: MailSender
   
    init(_ target: MailSender) {sender = target}
   
    func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?)
    {
        sender.mailComposeController(controller, didFinishWithResult:result, error: error)
    }
}


extension MailSender
{
    func composeMail(config: MFMailComposeViewController -> ())
    {
        let mailer = MFMailComposeViewController()
        mailer.mailComposeDelegate = MailDelegateProxy(self)
        config(mailer)
        presentViewController(mailer, animated: true, completion: .None)
    }
   
    func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?)
    {
        / handle result */
        dismissViewControllerAnimated(true, completion: .None)
    }
}

Thanks for the suggestion. This looks like a rather promising technique, though at the minute I'm getting an EXEC_BAD_ACCESS when the delegate method is due to be called. I think the problem is that I need some way of holding a reference to MailDelegateProxy (mailComposeDelegate is unowned).

As a thoroughly horrible experimental hack, I've added the following requirement to the protocol


protocol MailSender: class, MFMailComposeViewControllerDelegate {
    var mailer: MailDelegateProxy? { get set }
}


View controllers need to implement `var mailer`, the extension can then set the var in `composeMail` and clean it up at the end of `mailComposeController(didFinishWithResult:)`. In those circumstances, everything works; but the price is dreadful code!


Next mission: try and find another way of holding a reference to `MailDelegateProxy` which isn't so awful. Suggestions welcome : )

Would just encapsulating the mail code in a class instead of a protocol work for what you're looking for?


(untested)

@objc class MailAgent: NSObject, MFMailComposeViewControllerDelegate
{
    let source: UIViewController
  
    init(controller: UIViewController) {source = controller}
  
    func composeMail(config: MFMailComposeViewController -> ())
    {
        let mailer = MFMailComposeViewController()
        mailer.mailComposeDelegate = self
        config(mailer)
        source.presentViewController(mailer, animated: true, completion: .None)
    }
  
    func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?)
    {
        /* handle result */
        source.dismissViewControllerAnimated(true, completion: .None)
    }
}


class MyViewController: UIViewController
{
    var mailAgent: MailAgent!
  
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?)
    {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        mailAgent = MailAgent(controller: self)
    }
  
    required init?(coder aDecoder: NSCoder) {super.init(coder: aDecoder); mailAgent = MailAgent(controller: self)}
  
    // when needed, compose mail…
    func test()
    {
        mailAgent.composeMail()
        { m in
            m.setSubject("title")
            m.setMessageBody("message", isHTML: false)
        }
    }
}


That way the reference to the mail agent is automatically handled by the lifetime of your view controller.

Thanks for the suggestion. I've made one amend as it looks like a retain cycle would result from holding a reference to the view controller.


    weak var source: UIViewController!


It's not as clean as a protocol extension would have been (but it's better than my previous hacky solution – holding a var to MailDelegateProxy).

I'll continue to ponder ideas today, but I have a feeling that this is as good as it gets for now.

protocol extension inheriting from Obj-C optional protocol
 
 
Q