尝试更改NSTextField的值时

时间:2019-07-12 03:22:19

标签: swift xcode nsviewcontroller

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

当我在按钮功能中使用类似的代码行时,它会完美工作。

这是我的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 = 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
        }
    }
}

对你们大多数人来说,这可能很简单,但是我花了四个晚上试图自己弄清楚这个论坛,但没有成功。

新修改:

多亏了Rob的回答,我得以进步,但我遇到了另一个障碍。如果我将代码移至ViewController类,则似乎可以访问我的stringValue变量self.threadAValueLabel.stringValue和self.threadBValueLabel.stringValue,但以下几行会生成“必须仅从主线程使用NSControl.stringValue”错误消息:

self.threadAValueLabel.stringValue = message
self.threadBValueLabel.stringValue = message

尽管我了解错误消息的含义,但已经尝试了几个小时才能找到解决该问题的方法,但似乎无济于事。

这是完整的代码

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
    }

}

1 个答案:

答案 0 :(得分:0)

您的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)")
}

有关更多信息,请参见Main Thread Checker