【问题标题】:Variables need global scope, but avoid Implicitly Unwrapped Optionals变量需要全局作用域,但要避免隐式展开选项
【发布时间】:2018-02-08 22:05:41
【问题描述】:

我需要在全局范围内定义一些变量,以便通过我的 ViewController 类的各种函数可以访问它们。

我发现并关注了一些在线教程/文章/参考资料,它们解释了同一件事 - Swift、iOS、CoreML 和用于图像处理/分类的相机。其中不止一个说“我们知道变量将存在并具有这种数据类型,因此可以添加'!' ”。但是,刚学 Swift,我理解并愿意坚持不使用 '!' 的设计原则。

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

如果我在需要它们的函数中声明它们,则只有该函数可以访问它们。但我需要通过 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?')

或者,在这种情况下,是否可以使用隐式解包选项?我知道在设置之前不会访问此变量,并且我知道它将具有的数据类型。

【问题讨论】:

  • 这些不是全局变量,它们包含在类的实例中。范围就是那个实例。

标签: ios swift optional


【解决方案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
    
    ...
    

然后在cameraInit() 中,我不能使用guard let newVarNameif let newVarName,因为此newVarName 对象的范围将受到限制,并且还会使用不同的名称。

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

更新

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
}

Guard让解决方案:

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.:您应该将您创建的所有变量更改为可选项(您可以将 @IBOutlets 保留为隐式展开)。

【讨论】:

  • 感谢@Milan 的解释、背景和解释。我使用了 guard let 方法,除了 cameraRelease() 函数外,它似乎按预期工作。尝试设置 cameraSession = nil 时,新错误是“无法将 Nil 值分配给类型 'AVCaptureSession'” - 我所做的只是在函数顶部放置 guard let cameraSession = cameraSession else { return }
  • 感谢@Milan 的帮助和解释。目的是遍历每个 var,我现在已经完成了。唯一导致另一个问题的是cameraInput,它在设置时不喜欢guard 语句,因此必须在do 语句中使用if let。另外device 仅用于 1 行,所以我想我可以使用附加的“?”,但也必须编写它自己的 guard 语句。
  • @jingo_man 我会在任何可以使用的地方使用?,如果只是一行,则与if let相同
猜你喜欢
  • 1970-01-01
  • 2012-12-19
  • 2013-12-31
  • 1970-01-01
  • 2011-05-02
  • 2023-03-19
  • 1970-01-01
  • 2012-05-16
  • 1970-01-01
相关资源
最近更新 更多