Swift - Refactoring code. Closure and inout?

大兔子大兔子 提交于 2021-02-11 14:44:10

问题


I have a class thats used to help manage the process of users sending emails with MFMailComposeViewControllerDelegate.

The code is rather long, and I'm using it inside of almost all of my ViewControllers. And I figure I should be able to add it as an extension to UIVIewController. And so I'm currently trying to do that, but at a loss of how to do it correctly.

Working Code:

Struct:

struct Feedback {
    let recipients = [R.App.Contact.email] // [String]
    let subject: String
    let body: String
    let footer: String
}

Class:

final class FeedbackManager: NSObject, MFMailComposeViewControllerDelegate {
    
    private var feedback: Feedback
    private var completion: ((Result<MFMailComposeResult,Error>)->Void)?
    
    override init() {
        fatalError("Use FeedbackManager(feedback:)")
    }
    
    init?(feedback: Feedback) {
        print("init()")
        guard MFMailComposeViewController.canSendMail() else {
            return nil
        }
        self.feedback = feedback
    }
    
    func send(on viewController: UIViewController, completion:(@escaping(Result<MFMailComposeResult,Error>)->Void)) {
        print("send()")
        
        var appVersion = ""
        if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
            appVersion = version
        }
        
        let mailVC = MFMailComposeViewController()
        self.completion = completion
        
        mailVC.mailComposeDelegate = self
        mailVC.setToRecipients(feedback.recipients)
        mailVC.setSubject(feedback.subject)
        mailVC.setMessageBody("<p>\(feedback.body)<br><br><br><br><br>\(feedback.footer)<br>Potfolio: (\(appVersion))<br>\(UIDevice.modelName) (\(UIDevice.current.systemVersion))</p>", isHTML: true)
        
        DispatchQueue.main.async {
            viewController.present(mailVC, animated:true)
        }
        
    }
    
    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
        print("mailComposeController()")
        if let error = error {
            completion?(.failure(error))
            controller.dismiss(animated: true)
        } else {
            completion?(.success(result))
            controller.dismiss(animated: true)
        }
    }
}

Implementation:

var feedbackManager: FeedbackManager?

func sendEmail() {
        
        let feedback = Feedback(subject: "subject", body: "body", footer: "footer")
        
        if let manager = FeedbackManager(feedback: feedback) {
            self.feedbackManager = manager
            self.feedbackManager?.send(on: self) { [weak self] result in
                switch result {
                case .failure(let error):
                    print("error: ", error.localizedDescription)
                case .success(_):
                    print("Success")
                }
                self?.feedbackManager = nil
            }
        } else { // Cant Send Email:
            let failedMenu = UIAlertController(title: "Please email " + R.App.Contact.email, message: nil, preferredStyle: .alert)
            let okAlert = UIAlertAction(title: "Ok!", style: .default)
            failedMenu.addAction(okAlert)
            DispatchQueue.main.async {
                self.present(failedMenu, animated: true)
            }
        }
    }

My attempt to refractor:

import UIKit

extension UIViewController {
    
    func refactoredEmail(on viewController: UIViewController, with feedback: Feedback, manager: inout FeedbackManager?) -> FeedbackManager? {
        
        guard let letManager = FeedbackManager(feedback: feedback) else {
            let failedMenu = UIAlertController(title: "Please email " + R.App.Contact.email, message: nil, preferredStyle: .alert)
            let okAlert = UIAlertAction(title: "Ok!", style: .default)
            failedMenu.addAction(okAlert)
            DispatchQueue.main.async {
                viewController.present(failedMenu, animated: true)
            }
            return nil
        }
        
        manager = letManager
        manager?.send(on: self) { [weak manager] result in
            switch result {
            case .failure(let error):
                print("error: ", error.localizedDescription)
            case .success(_):
                print("Success")
            }
            manager = nil
        }
        return letManager
    }
}

Implementation:

var feedbackManager: FeedbackManager?
    
    func sendEmail() {
        let feedback = Feedback(subject: "subject", body: "body", footer: "footer")
        refactoredEmail(on: self, with: feedback, manager: &feedbackManager)
    }

FeedbackManager Class is the same as above. --


As is, this is functional, but, refactoredEmail uses feedbackManager as an inout parameter. And feedbackManager is not returned to nil after completion.

I'm not entirely sure how bad (or not) it is to use inout. And I don't fully understand closures. However, my understanding is that the FeedbackManager Class must be a class because it subclasses MFMailComposeViewControllerDelegate. And because of this, each ViewController needs it own reference to the class. Which is where var feedbackManager: FeedbackManager? comes in.

feedbackManager can then run the code inside the FeedbackManager Class. Which presents the MFMailComposeViewController on top of the ViewController that feedbackManager is in. But since now refactoredEmail() is an extension of UIViewController, my guess is the closure is capturing incorrectly?

In conclusion: My goal is to refractor the code so that implementing it in each view controller can hopefully be cut down in comparison with the first implementation code block.

Also, how bad is it to use inout?


回答1:


I'm not sure why manager is not nil after completion, but I can show you other way of implementation.

You could use associated objects:

protocol FeedbackShowable: class {
    func giveFeedback(with feedback: Feedback)
}

extension UIViewController: FeedbackShowable {
    private enum AssociatedObjectKeys {
        static var feedbackManager: String = "feedbackManager"
    }

    private var feedbackManager: FeedbackManager? {
        get {
            return objc_getAssociatedObject(self, &AssociatedObjectKeys.feedbackManager) as? FeedbackManager
        }
        set {
            objc_setAssociatedObject(self,
                                     &AssociatedObjectKeys.feedbackManager,
                                     newValue as FeedbackManager?,
                                     .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    func giveFeedback(with feedback: Feedback) {
        let feedbackManager = FeedbackManager(feedback: feedback)
        self.feedbackManager = feedbackManager
        feedbackManager.send(on: self) { [weak self] result in
            self?.feedbackManager = nil
        }
    }
}


来源:https://stackoverflow.com/questions/63783481/swift-refactoring-code-closure-and-inout

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!