75216


_****やりたいこととできないこと****_  
現在、「Operationで定義したタスク(FirstOperationClass)」から、「タスクをキューに入れて実行しているクラス(ViewControllerClass)」のラベルの値を変更したいですが、そのラベルの値がnilになってしまいできていない状況です。

_****実施した手順とその結果****_

### 試したこと１

FirstOperationからラベルを指定して値を変更する。

まずタスクをキューに詰め、実行します。その際FirstOperation.main()の中身を確認するとサブスレッドになっており、サブスレッドではUIの更新ができないので、メインスレッドで実行するためにGCDのDispatch.mainを使用しています。printで確認したところメインスレッドになっていました。OperationではGCDが使うのはスタンダードではないのかもしれません。  
SecondOperationは不要だとは思いますが、まだOperationの理解度が足りておらず思わぬご指摘があるかもしれないので、一応載せています。またflagを定義しているのは後ほどキャンセル機能を実装したいからです。今回はおそらく関係ありません。

下記が実行コードになります。

[code]

    import UIKit
    
    class ThirdViewController: UIViewController {
        // Lableの定義、タスク実行後ここに値を入れたい。
        @IBOutlet weak var returnLabel: UILabel!
        override func viewDidLoad() {
            super.viewDidLoad()
            // キューの作成
            let queue = OperationQueue()
            queue.name = "com.example.my_operation_queue"
            queue.maxConcurrentOperationCount = 1
            queue.qualityOfService = .userInitiated
            // 作成したキューにタスクを入れる
            var operations: [Operation] = []
    
            operations.append(FirstOperation())
            operations.append(SecendOperation())
            operations[1].addDependency(operations[0])
            queue.addOperations(operations, waitUntilFinished: false)
            print("OperationQueueにタスクを追加しました。")
        }
    }
    
    var flag = true
    class FirstOperation: Operation {
        override func main() {
            if flag {
                print("This is FirstOperation step")
                print("１つめ\(Thread.isMainThread)")
                DispatchQueue.main.async {
                    print("２つめ\(Thread.isMainThread)")
                    ThirdViewController().returnLabel.text = "オペレーションから値の操作"
                }
            } else {
                print("FirstOperation cancel")
            }
        }
    }
    
    class SecendOperation: Operation {
        override func main() {
            if flag {
                print("This is SecondOperation")
            } else {
                print("cancel")
            }
      }
    }
    
[/code]

出たエラーとしてFirstOperation.mainの 「ThirdViewController().returnLabel.text =
"オペレーションから値の操作"」の部分でThread 1: Fatal error: Unexpectedly found nil while
implicitly unwrapping an Optional
valueというエラーが出ており、returnLabelに値が入ったいないことが分かりました。

### 試したこと１を経て、再度試したこと

returnLabelに値を入れることができなかったので外から操作をせずに、ViewControllerで操作しようと考えました。  
なので、main()の値を返り値としてその返り値の値をViewControllerのLabelに入れようと考えました。  
がmain()でエラーが生じました。

下記がコードです。変更点のみ記載します。

[code]

    var flag = true
    class FirstOperation: Operation {
        override func main() -> String {
            if flag {
                print("This is FirstOperation step")
                print("１つめ\(Thread.isMainThread)")
                 let returnValue = "オペレーションから値の操作"
               return returnValue
            } else {
                print("FirstOperation cancel")
            }
        }
    }
    
[/code]

しかし「override func main() -> String {」の行で、Method does not override any method
from its superclassが出てしまいます。  
これはmain()メソッドはオーバーライドしており、返り値を定義するとオバーライド元のメソッドと異なるメソッドだと判断されてしまい、エラーになったと仮定します。またオーバーライドするのではなく、別の自作メソッドで返り値を定義してViewController  
で呼び出しても、OperationQueueに入れたタスクとしては実行されず、根源的な解決にはならないので実施しませんでした。

_****何を・どのように調べたのか****_  
上記を解決するために、以下のキーワードで検索しました。

  * swift Operation タスク UIの変更

参考にした記事のURLは以下です。

【Swift】非同期処理「Operation」について  
<https://qiita.com/Kai_82/items/61051a558acffdbcdc1d>

1,2のエラー、問題点に対して何か解決案があればアドバイスよろしくお願いします。  
またOperationを使用したこの実装例であればこの2案と異なっていても構いません。

またそもそも 全てをGCDで実装するのではなくOperation,OperationQueueを使っているのは、最終的な目標として

  1. 実行ボタンを押すと3つA,B,CのAPIを順に実行しどれか一つでも失敗したらその処理を終了するという依存関係のある処理を行い、結果を画面に表示すること

  2. 別のボタンを処理の途中で押すとキャンセルできるようにしたい、

  3. そしてこれらの実行コードがネストにならないようにしたい。

これらを実装したいからです。


CC BY-SA 4.0
2021-04-11T16:49:44.130
75213
2021-04-12T00:05:52.940
2021-04-12T00:05:52.940
3060
36424
question
swift swift4 xcode12
Operation,OperationQueueを使用してそこで取得した値をUIViewControllerにあるラベルの値に入れたい


あなたの(「試したこと１」で)最も問題があるのは、この行です。

[code]

                    ThirdViewController().returnLabel.text = "オペレーションから値の操作"
    
[/code]

`ThirdViewController()`というイニシャライザ呼び出しで、 **実際に画面表示を担当しているインスタンスとは別の新しいインスタンスを**
、 **storyboardの設定を無視して**
作成してしまっています。Storyboardを無視したインスタンスの作成ですから、iOSはIBOutletの接続なんてやってくれていないため、当然`returnLabel`はnilのままですし、たとえstoryboard経由でインスタンスを作成したとしても、画面表示に関係ない別インスタンスの`UILabel`を操作しても画面には何も反映されません。

一般に

# `何かのViewController()`というイニシャライザ呼び出しは意味のないインスタンスを作成するので使っちゃダメ

と覚えておいた方が良いでしょう。

* * *

あなたが書かれているようなことを実現したいのであれば、
**実際に表示を担当しているViewControllerのインスタンスをOperationに渡してやる** 必要があるでしょう。

[code]

    class ThirdViewController: UIViewController {
        // Lableの定義、タスク実行後ここに値を入れたい。
        @IBOutlet weak var returnLabel: UILabel!
        override func viewDidLoad() {
            super.viewDidLoad()
            // キューの作成
            let queue = OperationQueue()
            queue.name = "com.example.my_operation_queue"
            queue.maxConcurrentOperationCount = 1
            queue.qualityOfService = .userInitiated
            // 作成したキューにタスクを入れる
            var operations: [Operation] = []
    
            let first = FirstOperation()
            first.thirdVC = self //<-実際に表示を担当しているVCを渡す
            operations.append(first)
            operations.append(SecendOperation())
            operations[1].addDependency(operations[0])
            queue.addOperations(operations, waitUntilFinished: false)
            print("OperationQueueにタスクを追加しました。")
        }
    }
    
    var flag = true //<- `flag`なんて意味不明の変数名の使用はやめましょう
    class FirstOperation: Operation {
        var thirdVC: ThirdViewController? //<-実際に表示を担当しているVCを受け取る
        override func main() {
            if flag {
                print("This is FirstOperation step")
                print("１つめ\(Thread.isMainThread)")
                DispatchQueue.main.async {
                    print("２つめ\(Thread.isMainThread)")
                    self.thirdVC?.returnLabel.text = "オペレーションから値の操作" //<-実際に表示を担当しているVCを操作する
                }
            } else {
                print("FirstOperation cancel")
            }
        }
    }
    
[/code]

* * *

### 「試したこと１を経て、再度試したこと」について

Operationで実行される処理から「返り値」を返すことはできません。処理結果から何らかの値を渡したいのであれば、他の方法を考えてください。


CC BY-SA 4.0
2021-04-11T23:37:21.070
75216
2021-04-11T23:37:21.070
13972
75213
answer