我一遍又一遍地使用相同的代码块。我如何巩固它?

I keep using this same chunk of code over and over. How do I consolidate it?

作为 Swift 菜鸟,我发现自己在复制和粘贴代码。我知道我应该使用 DRY 方法而不是这样做,但是这段特殊的代码让我感到难过。我尝试创建一个结构来保存它,但该结构抛出了各种错误。我不太了解 类 以及我将如何对其进行子类化,所以也许这就是解决方案。我只是不知道该怎么做?或者可能是延期?

无论如何,这是我在每个新视图控制器中不断复制和粘贴的代码:

import UIKit
import AVKit

class Step3JobSummaryVC: UIViewController, UITableViewDataSource, UITableViewDelegate {

...

var sourceVCIdentity = "setup"

var initialLaunch = true
let playerVC = AVPlayerViewController()
let video = Video.step3JobsSummary

...


// ------------------------
// Autoplay Video Functions
// ------------------------

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if initialLaunch == true {
        showUnplayedVideo()
        initialLaunch = false
    }

    Video.updatePlaybackTime(playerVC: playerVC, videoURL: video.url, firebaseVideoID: video.firebaseID)
}

func showUnplayedVideo() {

    // 1. get current video data
    Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in

        if !watched {

            // 2. show setup video popup on first load
            guard let videoURL = URL(string: self.video.url) else { print("url error"); return }
            let player = AVPlayer(url: videoURL)

            self.playerVC.player = player

            // 3. fast forward to where user left off (if applicable)
            player.seek(to: CMTimeMakeWithSeconds(playbackTime, 1))

            // 4. dismiss the player once the video is over and update Firebase
            NotificationCenter.default.addObserver(self,
                                                   selector: #selector(self.playerDidFinishPlaying),
                                                   name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
                                                   object: self.playerVC.player?.currentItem)

            self.present(self.playerVC, animated: true) {
                self.playerVC.player?.play()
            }
        }
    }
}

@objc func playerDidFinishPlaying(note: NSNotification) {
    self.playerVC.dismiss(animated: true)
    Video.updateFirebase(firebaseVideoID: video.firebaseID)
}

任何帮助都会很棒。我只是想学习:-)

编辑 #1

这是我对扩展的尝试。我简化并重构了我的代码,但和以前一样,它给了我一个错误。这次错误是 'extensions must not contain stored properties'。那么如何访问 AVPlayerController?!?

extension UIViewController {

let playerVC = AVPlayerViewController()

func showUnplayedVideo(video: Video) {

    // 1. get current video data
    Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in

        if !watched {
            // 2. show setup video popup on first load
            guard let videoURL = URL(string: video.url) else { print("url error"); return }
            let player = AVPlayer(url: videoURL)

            self.playerVC.player = player

            // 3. fast forward to where user left off (if applicable)
            player.seek(to: CMTimeMakeWithSeconds(playbackTime, 1))

            // 4. dismiss the player once the video is over and update Firebase
            NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
                                                   object: playerVC.player?.currentItem,
                                                   queue: .main) { (notification) in

                                                    self.playerDidFinishPlaying(note: notification as NSNotification)

            self.present(self.playerVC, animated: true) {
                self.playerVC.player?.play()
            }
        }
    }
}

    func playerDidFinishPlaying(note: NSNotification, video: Video) {
        self.playerVC.dismiss(animated: true)
        Video.updateFirebase(firebaseVideoID: video.firebaseID)
    }
}

编辑#2

所以我编译的代码没有任何错误,但现在它没有触发。啊啊

extension UIViewController {

func showUnplayedVideo(playerVC: AVPlayerViewController, video: Video) {

    print("does this code even fire?")

    // 1. get current video data
    Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in

        if !watched {
            // 2. show setup video popup on first load
            guard let videoURL = URL(string: video.url) else { print("url error"); return }
            let player = AVPlayer(url: videoURL)

            playerVC.player = player

            // 3. fast forward to where user left off (if applicable)
            player.seek(to: CMTimeMakeWithSeconds(playbackTime, 1))

            // 4. dismiss the player once the video is over and update Firebase
            NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
                                                   object: playerVC.player?.currentItem,
                                                   queue: .main) { (notification) in

                                                    self.playerDidFinishPlaying(playerVC: playerVC, note: notification as NSNotification, video: video)

                                                    self.present(playerVC, animated: true) {
                                                        playerVC.player?.play()
                                                    }
            }
        }
    }
}

func playerDidFinishPlaying(playerVC: AVPlayerViewController, note: NSNotification, video: Video) {
    playerVC.dismiss(animated: true)
    Video.updateFirebase(firebaseVideoID: video.firebaseID)
}
}

为什么这行不通?

您是否考虑过使用更传统的继承模型?

class VideoPlayingBaseController: : UIViewController, UITableViewDataSource, UITableViewDelegate {

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if initialLaunch == true {
        showUnplayedVideo()
        initialLaunch = false
    }

    Video.updatePlaybackTime(playerVC: playerVC, videoURL: video.url, firebaseVideoID: video.firebaseID)
}

func showUnplayedVideo() {

    // 1. get current video data
    Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in

        if !watched {

            // 2. show setup video popup on first load
            guard let videoURL = URL(string: self.video.url) else { print("url error"); return }
            let player = AVPlayer(url: videoURL)

            self.playerVC.player = player

            // 3. fast forward to where user left off (if applicable)
            player.seek(to: CMTimeMakeWithSeconds(playbackTime, 1))

            // 4. dismiss the player once the video is over and update Firebase
            NotificationCenter.default.addObserver(self,
                                                   selector: #selector(self.playerDidFinishPlaying),
                                                   name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
                                                   object: self.playerVC.player?.currentItem)

            self.present(self.playerVC, animated: true) {
                self.playerVC.player?.play()
            }
        }
    }
}

@objc func playerDidFinishPlaying(note: NSNotification) {
    self.playerVC.dismiss(animated: true)
    Video.updateFirebase(firebaseVideoID: video.firebaseID)
}


}

然后让你的 类 使用它:

class Step3JobSummaryVC: VideoPlayingBaseController {
 //more code here
}

我将从为您的功能定义协议开始,如下所示:

protocol VideoPlayable {
    func showUnplayedVideo(playerVC: AVPlayerViewController, video: Video)
}

然后给它添加一个默认实现

extension VideoPlayable where Self: UIViewController {

    func showUnplayedVideo(playerVC: AVPlayerViewController, video: Video) {

       print("does this code even fire?")

       // 1. get current video data
       Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in

            if !watched {
            // 2. show setup video popup on first load
                guard let videoURL = URL(string: video.url) else { print("url error"); return }
                let player = AVPlayer(url: videoURL)

                playerVC.player = player

                // 3. fast forward to where user left off (if applicable)
                player.seek(to: CMTimeMakeWithSeconds(playbackTime, 1))

                // 4. dismiss the player once the video is over and update Firebase
                NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
                                                   object: playerVC.player?.currentItem,
                                                   queue: .main) { (notification) in

                    self.playerDidFinishPlaying(playerVC: playerVC, note: notification as NSNotification, video: video)

                }

                self.present(playerVC, animated: true) {
                    playerVC.player?.play()
                }

            }
        }
    }

    private func playerDidFinishPlaying(playerVC: AVPlayerViewController, note: NSNotification, video: Video) {
        playerVC.dismiss(animated: true)
        Video.updateFirebase(firebaseVideoID: video.firebaseID)
    }
}

因此,当您将 VideoPlayable 协议添加到控制器时,您将可以使用您的自定义功能,而其他不应具有该功能的控制器将无法访问此方法。 另外,如果您真的想访问该方法

func playerDidFinishPlaying(playerVC: AVPlayerViewController, note: NSNotification, video: Video)

将其添加到协议中并从实现中删除私有语句。

并且您的视频播放器没有显示,因为您将播放器的显示添加到通知块中。

此外,请考虑为您的块添加适当的自我处理。现在我觉得有可能是自己被卡住了。

只是为了让你知道声明 其中自我:UIViewController 将实现的访问限制为 UIViewControllers,因此如果将协议添加到 UIView 子类,您将无法访问默认实现。然后您将需要添加一个新的 :) 这可以防止在您不希望使用它的地方丢失协议。

您可以将所有重复使用的代码移动到一个单独的 class:

class Step3JobSummaryVC: UIViewController {

    let videoPlayer = VideoPlayer(video: Video.step3JobsSummary)

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        videoPlayer.start(on: self)
    }
}

final
class VideoPlayer {
    private var initialLaunch: Bool = true
    private let playerVC = AVPlayerViewController()
    private let video: Video

    init(video: Video) {
        self.video = video
    }

    func start(on viewController: UIViewController) {
        if initialLaunch == true {
            showUnplayedVideo(on: viewController)
            initialLaunch = false
        }

        Video.updatePlaybackTime(playerVC: playerVC, videoURL: video.url, firebaseVideoID: video.firebaseID)
    }

    func showUnplayedVideo(on viewController: UIViewController) {

        // 1. get current video data
        Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in

            if !watched {

                // 2. show setup video popup on first load
                guard let videoURL = URL(string: self.video.url) else { print("url error"); return }
                let player = AVPlayer(url: videoURL)

                self.playerVC.player = player

                // 3. fast forward to where user left off (if applicable)
                player.seek(to: CMTimeMakeWithSeconds(playbackTime, preferredTimescale: 1))

                // 4. dismiss the player once the video is over and update Firebase
                NotificationCenter.default.addObserver(self,
                                                       selector: #selector(self.playerDidFinishPlaying),
                                                       name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
                                                       object: self.playerVC.player?.currentItem)

                viewController.present(self.playerVC, animated: true) {
                    self.playerVC.player?.play()
                }
            }
        }
    }

    @objc func playerDidFinishPlaying(note: NSNotification) {
        self.playerVC.dismiss(animated: true)
        Video.updateFirebase(firebaseVideoID: video.firebaseID)
    }
}