swift - 尝试更改 NSTextField 的值时,我得到“在隐式展开可选值时意外发现 nil”
问题描述
我以前有 C 顺序编程的经验,但那是在 80 年代中期。一般来说,我是 OOP、Swift 和多线程编码的新手。我正在编写一个小程序以更好地理解所有 3 个。我能够构建一个功能程序,该程序启动两个线程,每个线程计数为 200,然后重置为 1 并在无限循环中重新开始计数。每个计数器的值都打印到控制台,每个线程都有一个开始和停止按钮,允许我单独控制它们。一切正常,尽管我承认我的代码远非完美(我不完全尊重封装,我有一些全局变量应该设为本地等......我的主要问题是试图将每个线程计数器值输出到标签而不是将它们打印到控制台。当我尝试更改任何标签的内容时,
当我在按钮函数中使用类似的代码行时,它可以完美运行。
这是我的 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
}
}
解决方案
您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)")
}
有关更多信息,请参阅主线程检查器。
推荐阅读
- meshroom - Meshroom 命令行界面 (CLI)
- c# - “@Price”附近的语法不正确
- javascript - 有人能解释一下这个简单的 javascript (ES6) 行对箭头函数有什么作用吗?
- rust - 如何实现安全的全局 Instant?
- asp.net - Entity Framework 或 Ado.Net 的替代方案?
- reactjs - d3 动画 React Native 代码在 IOS 中很快而在 Android 中很慢?
- python - 从熊猫数据框中填充矩阵
- java - 将 long 移动超过 31 位(java)
- c# - DOTween 在 Unity C# 中不可用
- javascript - TypeError:无法将属性“className”设置为 null