При попытке изменить значение NSTextField я получаю «Неожиданно найденный ноль при неявном развертывании необязательного значения» - PullRequest
1 голос
/ 12 июля 2019

У меня есть предыдущий опыт последовательного программирования на С, но это было в середине 80-х. Я новичок в ООП, 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
    }

Код, создающий ошибку, находится в последних 2 методах:

        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
        }
    }
}

Вероятно, это очень просто для большинства из вас, но я провел четыре вечера, пытаясь выяснить сам и поискать на этом форуме, но безуспешно.

Новое редактирование:

Благодаря ответу Роба я смог прогрессировать, но я попал в другую ловушку. если я переместлю свой код в класс 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 голосов
/ 12 июля 2019

Ваш 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 должен использоваться только из основного потока

Да, если вы инициировалиПри обновлении пользовательского интерфейса из фонового потока вы должны отправить это обновление обратно в основной поток, например:

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

См. Проверка основного потока для получения дополнительной информации.

...