问题
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