变量需要全局范围,但要避免使用Implicitly Unwrapped Optionals

时间:2018-02-08 13:16:26

标签: ios swift optional

我需要使用全局范围定义一些变量,因此通过我的ViewController类的各种函数都可以访问它们。

我发现并遵循了一些解释相同内容的在线教程/文章/参考资料--Swift,iOS,CoreML和用于图像处理/分类的摄像头。其中不止一个说的是"我们知道变量将存在并具有此数据类型,因此可以添加'!' &#34 ;.但是,只是学习Swift,我理解并且愿意遵守不使用'!'的设计原则。

所以我开始用var varName : ClassType!声明变量,然后在稍后的执行点设置值。它总是在被访问之前设置(其中一个"规则"我在其他文章中看到的隐式解包的Optionals)。

如果我在需要它们的函数中声明它们,那么只有该函数才能访问它们。但我需要通过ViewController访问它们。

我的骨架结构:

import UIKit
import AVFoundation

class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {

    // MARK: Global Variables
    var layer: CALayer {
        return viewCamera.layer
    }
    var isCameraRunning = false

    // INIT Camera Variables. Used by multiple functions, so global scope
    var cameraSession : AVCaptureSession!
    var device : AVCaptureDevice!
    var cameraLayer : AVCaptureVideoPreviewLayer!
    var cameraOutput : AVCaptureVideoDataOutput!
    var cameraInput : AVCaptureDeviceInput!

    // MARK: IB Outlets
    @IBOutlet weak var labelInfo: UILabel! // provides user with status info
    @IBOutlet weak var viewCamera: UIView! // a simple UIView on the storyboard

    override func viewDidLoad() {
        super.viewDidLoad()

        let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.viewCameraTap))
        viewCamera.addGestureRecognizer(tap)

    }

    @objc func viewCameraTap() {

        if isCameraRunning {

            // STOP CAMERA (AND PROCESSING)
            cameraRelease()

            // RESET UI
            labelInfo.text = "Default text"

        } else {

            // INIT CAMERA
            // START CAMERA, contained within cameraInit()
            // PROCESS IMAGE CLASSIFICATION, contained within captureOutput()
            cameraInit()


            // UPDATE UI
            labelInfo.text = "Processing"


        }

        isCameraRunning = !isCameraRunning // Toggles the value after processing the function
    }

    // MARK: Camera Init & Release

    func cameraInit() {

        cameraSession = AVCaptureSession()
        cameraSession.sessionPreset = AVCaptureSession.Preset.photo

        device = AVCaptureDevice.default(for: AVMediaType.video)

        do {
            cameraInput = try AVCaptureDeviceInput(device: device!)

            if cameraSession.canAddInput(cameraInput) {
                cameraSession.addInput(cameraInput)
            }
        } catch {
            print(error.localizedDescription)
        }

        cameraOutput = AVCaptureVideoDataOutput()
        if cameraSession.canAddOutput(cameraOutput) {
            cameraSession.addOutput(cameraOutput)
        }

        cameraLayer = AVCaptureVideoPreviewLayer(session: cameraSession)
        cameraLayer?.videoGravity = .resizeAspectFill

        cameraLayer.frame = layer.frame
        cameraLayer.frame.origin = CGPoint(x: 0, y: 0)
        layer.addSublayer(cameraLayer)

        cameraOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))

        cameraSession.startRunning()

    }

    func cameraRelease() {
        if cameraSession != nil {
            if cameraSession.isRunning {

                cameraSession.stopRunning()
                cameraSession.removeInput(cameraInput)
                cameraSession.removeOutput(cameraOutput)
                cameraOutput = nil
                cameraLayer.removeFromSuperlayer()
                cameraSession = nil
            }
        }
    }

    // MARK: Camera Capture

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

        processCameraBuffer(sampleBuffer: sampleBuffer) { result in

            print("result: \(result)")
        }
    }

    func processCameraBuffer(sampleBuffer: CMSampleBuffer, completion: @escaping (Float) -> Void) {

        var result : Float = 0.0
        // some processing, etc...

        completion(result)
    }

}

在范围内使变量全局化的最佳/首选/推荐方法是什么,而不是隐式解包的选项?

如果我设置一个变量,如: var cameraSession : AVCaptureSession?

然后在cameraInit()中,我无法使用guard let newVarNameif let newVarName,因为此newVarName对象的范围有限,并且使用的名称也不同。

cameraInit()的顶部,我可以使用cameraSession = AVCaptureSession()初始化它。但是将来对cameraSession的所有引用都必须是可选择链接的(即尾随' cameraSession?')

或者,在这种情况下,使用Implicitly Unwrapped Optionals是否可以接受?我知道这个变量在设置之前是不被访问的,我知道它将具有的数据类型。

2 个答案:

答案 0 :(得分:1)

您应该更喜欢在隐式展开的可选项之前使用可选项。

因此请使用以下内容:

var cameraSession : AVCaptureSession?

然后,无论何时需要使用它,您都可以使用以下方法之一:

  1. 如果只出现一次,您可以使用可选链接:

    cameraSession?.sessionPreset = AVCaptureSession.Preset.photo
    
  2. 如果有多次出现,和/或右侧使用cameraSession(因此需要一个值,而不仅仅是可选的):

    guard let cameraSession = cameraSession else { return }
    // from now till the end of scope (in this case the method) you
    // can use cameraSession as a non optional type
    cameraSession.sessionPreset = AVCaptureSession.Preset.photo
    
    ...
    
  3.   

    然后在cameraInit()中,我无法使用guard let newVarNameif let newVarName,因为此newVarName对象的范围有限,并且使用的名称也不同。

    那又怎样?范围仅限于给定变量,而不是它指向的实例。在该范围内,您可以访问它,就像它不是可选的一样。是的,您需要在需要使用if let cameraSession = cameraSession的任何地方使用guard let cameraSession = cameraSessioncameraSession,但这是您必须为类型安全付出的代价(它是考虑到你得到的东西,这是一个非常小的价格。)

    <强>更新

    cameraRelease有点棘手。

    如果让解决方案:

    func cameraRelease() {
        if let cameraSession = cameraSession {
            if cameraSession.isRunning {
    
                cameraSession.stopRunning()
                cameraSession.removeInput(cameraInput)
                cameraSession.removeOutput(cameraOutput)
                cameraOutput = nil
                cameraLayer.removeFromSuperlayer()
            }
        }
        // this you have to put outside, because here you are trying to assign 
        // something to the cameraSession instance property, not to the local
        // variable created by if let
        cameraSession = nil
    }
    

    警卫解决方案:

    func cameraRelease() {
        guard let cameraSession = cameraSession else { return }
        if cameraSession.isRunning {
            cameraSession.stopRunning()
            cameraSession.removeInput(cameraInput)
            cameraSession.removeOutput(cameraOutput)
            cameraOutput = nil
            cameraLayer.removeFromSuperlayer()
        }
        // use self.cameraSession instead of cameraSession to refer to
        // instance property and not to the local variable created by guard let
        self.cameraSession = nil
    }
    

    P.S。:您应该将所有已创建的变量更改为选项(您可以将@IBOutlet保留为隐式展开)。

答案 1 :(得分:0)

您不应该cameraSession隐式解包Optional,因为您在nil中将其设置为cameraRelease()。相反,它应该是一个常规的Optional,你必须在使用前进行测试和解包。

您的其他财产也是如此。如果它们在可能被使用之前被设置并且它们的生命周期是整个范围的(在这种情况下,类的实例的范围)那么它们可以被隐式地展开。否则他们应该是常规Optional。这是设置它们最安全的方法,否则您可能会遇到使用未设置Optional并导致运行时崩溃的问题。