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