Swift 我得到;在隐式展开可选值“时意外发现nil”;尝试更改NSTextField的值时

Swift 我得到;在隐式展开可选值“时意外发现nil”;尝试更改NSTextField的值时,swift,xcode,nsviewcontroller,Swift,Xcode,Nsviewcontroller,我以前有C语言顺序编程的经验,但那是在80年代中期。我不熟悉OOP、Swift和多线程编码。我正在写一个小程序,以便更好地理解所有3个。我能够构建一个功能性程序,启动两个线程,每个线程计数为200,然后重置为1,并在一个无限循环中重新开始计数。每个计数器的值都会打印到控制台上,每个线程都有一个开始和停止按钮,可以分别控制它们。尽管我承认我的代码还远远不够完美,但一切都很好(我不完全尊重封装,我有一些全局变量应该是本地的等等。我的主要问题是试图将每个线程计数器值输出到标签,而不是将它们打印到控制台

我以前有C语言顺序编程的经验,但那是在80年代中期。我不熟悉OOP、Swift和多线程编码。我正在写一个小程序,以便更好地理解所有3个。我能够构建一个功能性程序,启动两个线程,每个线程计数为200,然后重置为1,并在一个无限循环中重新开始计数。每个计数器的值都会打印到控制台上,每个线程都有一个开始和停止按钮,可以分别控制它们。尽管我承认我的代码还远远不够完美,但一切都很好(我不完全尊重封装,我有一些全局变量应该是本地的等等。我的主要问题是试图将每个线程计数器值输出到标签,而不是将它们打印到控制台。当我尝试更改任何标签的内容时,我会得到“在隐式展开可选值时意外发现零”

当我在按钮函数中使用类似的代码行时,它工作得非常好

self.threadBValueLabel.stringValue = "Stop"
这是my ViewController.swift文件的内容:

class ViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        //Call Async Task
        startProgram()
    }

    override var representedObject: Any? {
        didSet {
           // Update the view, if already loaded.
        }
    }

    @IBOutlet var threadAValueLabel: NSTextField!
    @IBOutlet var threadBValueLabel: NSTextField!



    @IBAction func threadAStartButton(_ sender: NSButtonCell) {
    threadAGoNoGo = 1
        self.threadAValueLabel.stringValue = "Start"
    }

    @IBAction func threadAStopButton(_ sender: NSButton) {
        threadAGoNoGo = 0
        self.threadAValueLabel.stringValue = "Stop"
    }

    @IBAction func threadBStartButton(_ sender: NSButton) {
        threadBGoNoGo = 1
        self.threadBValueLabel.stringValue = "Start"
    }

    @IBAction func threadBStopButton(_ sender: NSButton) {
        threadBGoNoGo = 0
        self.threadBValueLabel.stringValue = "Stop"
    }

    func changethreadALabel(_ message: String) {
        self.threadAValueLabel.stringValue = message
    }

    func changethreadBLabel(_ message: String) {
        self.threadBValueLabel.stringValue = message
    }
创建错误的代码位于最后两种方法中:

        self.threadAValueLabel.stringValue = message

下面的代码在按钮函数中运行良好

self.threadBValueLabel.stringValue = "Stop"
创建这两个线程的代码如下所示:

import Foundation
func startProgram(){
    let myViewController: ViewController = ViewController (nibName:nil, bundle:nil)

    // Start counting through 200 when Thread A start button is pressed and stop when Thread A Stop button is pressed. When reaching 200, go back to 0 and loop forever

    DispatchQueue(label: "Start Thread A").async {
        while true {                                    // Loop Forever
            var stepA:Int = 1
            while stepA < 200{
                for _ in 1...10000000{}                 // Delay loop
                if threadAGoNoGo == 1{
                print("Thread A \(stepA)")
                myViewController.changethreadALabel("South \(stepA)") // Update Thread A value display label
                stepA += 1
                }
            }
        stepA = 1
        }
    }

    // Start counting through 200 when Thread B start button is pressed and stop when Thread B Stop button is pressed. When reaching 200, go back to 0 and loop forever
    DispatchQueue(label: "Start Thread B").async {
        while true {                                    // Loop Forever
            var stepB:Int = 1
            while stepB < 200{
                for _ in 1...10000000{}                 // Delay loop
                if threadBGoNoGo == 1{
                    print("Tread B \(stepB)")
                    myViewController.changethreadBLabel("South \(stepB)") // Update Thread B value display label
                    stepB += 1
                }
            }
        stepB = 1
        }
    }
}
虽然我理解错误消息的含义,但我已经尝试找到解决此问题的方法几个小时了,但似乎没有任何效果

这是完整的代码

import Foundation
import Cocoa
public var threadAGoNoGo:Int = 0
public var threadBGoNoGo:Int = 0



class ViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        //Call Async Task

        // Start counting through 200 when Thread A start button is pressed and stop when Thread A Stop button is pressed. When reaching 200, go back to 0 and loop forever
        DispatchQueue(label: "Start Thread A").async {
            while true {                                    // Loop Forever
                var stepA:Int = 1
                while stepA < 200{
                    for _ in 1...10000000{}                 // Delay loop
                    if threadAGoNoGo == 1{
                        print("Thread A \(stepA)")
                        self.changethreadALabel("Thread A \(stepA)")
                        stepA += 1
                    }
                }
                stepA = 1
            }
        }

        // Start counting through 200 when Thread B start button is pressed and stop when Thread B Stop button is pressed. When reaching 200, go back to 0 and loop forever
        DispatchQueue(label: "Start Thread B").async {
            while true {                                    // Loop Forever
                var stepB:Int = 1
                while stepB < 200{
                    for _ in 1...10000000{}                 // Delay loop
                    if threadBGoNoGo == 1{
                        print("Tread B \(stepB)")
                        self.changethreadBLabel("Thread B \(stepB)")
                        stepB += 1
                    }
                }
                stepB = 1
            }
        }
    }

    override var representedObject: Any? {
        didSet {
           // Update the view, if already loaded.
        }
    }

    @IBOutlet var threadAValueLabel: NSTextField!
    @IBOutlet var threadBValueLabel: NSTextField!

    @IBAction func threadAStartButton(_ sender: NSButtonCell) {
    threadAGoNoGo = 1
        self.threadAValueLabel.stringValue = "Start"
    }

    @IBAction func threadAStopButton(_ sender: NSButton) {
        threadAGoNoGo = 0
        self.threadAValueLabel.stringValue = "Stop"
    }

    @IBAction func threadBStartButton(_ sender: NSButton) {
        threadBGoNoGo = 1
        self.threadBValueLabel.stringValue = "Start"
    }

    @IBAction func threadBStopButton(_ sender: NSButton) {
        threadBGoNoGo = 0
        self.threadBValueLabel.stringValue = "Stop"
    }

    func changethreadALabel(_ message:String) {
        self.threadAValueLabel.stringValue = message
    }

    func changethreadBLabel(_ message: String) {
        self.threadBValueLabel.stringValue = message
    }

}

<代码>导入基础 进口可可 公共变量threadAGoNoGo:Int=0 公共变量threadBGoNoGo:Int=0 类ViewController:NSViewController{ 重写func viewDidLoad(){ super.viewDidLoad() //加载视图后执行任何其他设置。 //调用异步任务 //当按下开始按钮时开始计数到200,当按下停止按钮时停止。当达到200时,返回到0并永远循环 DispatchQueue(标签:“启动线程A”).async{ 而真正的{//永远循环 变量stepA:Int=1 而stepA<200{ 对于1…10000000{}//延迟循环中的uu 如果threadAGoNoGo==1{ 打印(“线程A\(步骤A)”) self.changeThrealabel(“线程A\(步骤A)”) stepA+=1 } } 步骤a=1 } } //当按下线程B开始按钮时开始计数到200,当按下线程B停止按钮时停止。当达到200时,返回到0并永远循环 DispatchQueue(标签:“启动线程B”).async{ 而真正的{//永远循环 变量stepB:Int=1 而步骤b<200{ 对于1…10000000{}//延迟循环中的uu 如果threadBGoNoGo==1{ 打印(“踏板B\(踏板B)”) self.changethreadBLabel(“线程B\(步骤B)”) 步骤b+=1 } } 步骤b=1 } } } 覆盖var representedObject:有吗{ 迪塞特{ //更新视图(如果已加载)。 } } @IBOutlet var threadAValueLabel:NSTextField! @IBOutlet var threadBValueLabel:NSTextField! @iAction func threadAStartButton(\发送方:NSButtonCell){ threadAGoNoGo=1 self.threadAValueLabel.stringValue=“开始” } @iAction func threadAStopButton(uu发送方:NSButton){ threadAGoNoGo=0 self.threadAValueLabel.stringValue=“停止” } @iAction func threadBStartButton(\发送方:NSButton){ threadBGoNoGo=1 self.threadBValueLabel.stringValue=“开始” } @iAction func threadBStopButton(u发件人:NSButton){ threadBGoNoGo=0 self.threadBValueLabel.stringValue=“停止” } func changethrealabel(umessage:String){ self.threadAValueLabel.stringValue=消息 } func changethreadBLabel(u消息:字符串){ self.threadBValueLabel.stringValue=消息 } }
您的
startProgram
正在实例化视图控制器的一个新的、重复的实例,该实例没有连接任何插座。因此,插座将为
nil
,尝试引用它们将生成您描述的错误

如果您真的想使其成为全局函数,请传递视图控制器引用,例如:

func startProgram(on viewController: ViewController) {
    // var myViewController = ...

    // now use the `viewController` parameter rather than the `myViewController` local var

    ...
}
然后视图控制器可以将引用传递给自身,以便全局
startProgram
知道要更新的视图控制器实例:

startProgram(on: self)
或者,更好的是,(a)你根本不应该有全局方法;和(b)即使您这样做了,视图控制器之外的任何东西都不应该更新视图控制器的出口。这里简单的解决方案是,将
startProgram
作为
ViewController
类的方法,然后您可以直接引用出口


如上文所述,您已编辑问题以将
startProgram
放入视图控制器类中。然后,您遇到了第二个不同的问题,调试器报告:

NSControl.stringValue只能从主线程使用

是的,如果您是从后台线程启动UI更新,则必须将该更新发送回主线程,例如:

DispatchQueue.main.async {
    self.changethreadALabel("Thread A \(stepA)")
}

有关详细信息,请参阅。

您的
启动程序正在实例化视图控制器的一个新的复制实例,该实例没有任何连接的出口。因此,出口将为
nil
,尝试引用它们将生成您描述的错误b
startProgram(on: self)
DispatchQueue.main.async {
    self.changethreadALabel("Thread A \(stepA)")
}