LoginSignup
3
1

More than 3 years have passed since last update.

Swiftの無限ループをforEachで実現する

Last updated at Posted at 2020-02-09

はじめに

なんで余計なことを考えるのか。while( true ) {}を使いましょう。
(Swift触り始めて数日の初心者なので、間違いなどがある可能性があります)

きっかけ

ネットのどこかでみかけた以下のやりとり。

cソースからswift5に移行してるのですが、ループ処理の代替で詰まりました。
for (;;) {}
上記for文をswift5で書くとするとどう書くでしょうか?
while (true) {}
が無難ですかね...

それ以外ないような

やはりwhile文使うしか無さそうですね。ありがとうございました

foreachは?

無限ループforeachサンプルお願いします

ごめん勘違いしてたわ

作ってみたもの

cancelメソッドを実行するまで無限にnextできるイテレーター。

無限forEach
class Loop:Sequence,IteratorProtocol{
    typealias Element = Loop
    var _loopVal :Loop?

    init() { _loopVal = self }    
    func next() -> Loop? {return _loopVal }
    func cancel() { self._loopVal = nil }
}
forEachでの使い方
var cnt = 0
Loop().forEach { loop in 
    cnt += 1
    print(cnt)

    if cnt > 5 { loop.cancel() }
}
for..inでの使い方(breakで抜けるパターン)
var cnt = 0
for _ in Loop(){
    cnt += 1
    print(cnt)

    if cnt > 5 { break }
}

(使用例の条件が簡易すぎて用途が不明瞭すぎる…)

以下雑感(作ってる間に考えてたこととか)

  • 最初は単なる無限のジェネレーター(イテレーター、シーケンス)を作ればそれで完了だと思っていた。
    • for..inはいいけど、forEach{}(クロージャー)で使うと無限ループ終了できないね。うん。
    • Cancelの仕組みつくろか。 cancel:boolを条件値に持てばええな。
    • ……。ループするたびに判定処理とか実行コストアカンでしょ……。
    • selfを変数にいれて管理すれば判定いらんくなるな。(これでやっと↑のコードに)
ループ毎に判定してたやつ
class Loop:Sequence,IteratorProtocol{
    typealias Element = Loop
    var _cancel = false

    func next() -> Loop? {return self._cancel ? nil : self }
    func cancel() { self._cancel = true }
}


  • Cancelの仕組みはスレッドセーフの観点からみたらどうなる??
    • breakで抜ける分には、breakの条件になる値をスレッドセーフにして読み書きしてれば大丈夫だろうけど、Cancelでは次ループに入る可能性があるのでは?
    • ……残念なことに問題がでそうなコードを書いてみたけれど、おかしな値を出すことができなかった。
      (並列処理の書き方がよくない??)
スレッドセーフ検証
class Counter{
    var count: Int = 0;
    init(){}
}

func doAsyncs() {
    let group = DispatchGroup()
    let queue = DispatchQueue(label: "queue", attributes: .concurrent)

    let loop = Loop();
    let cnt = Counter();

    // 並列処理させるタスク
    // 共用のカウンタをインクリメントする
    // (微妙な動きさせたいので、一旦共用カウンタは排他制御なし)
    // 共用のカウンタが規定値超えたところでループを抜ける。
    let task = { (taskNo: Int) in
        var i = 0
        loop.forEach { l in
            if cnt.count > 100000 {
                l.cancel()
                // ループ抜けた時のタスクNoとタスク内カウンタを表示
                print("taskNo:\(taskNo)   i:\(i)")
                return
            }
            cnt.count += 1
            i += 1
        }
    }

    // タスク10個作って実行
    for i in 1 ... 10{
        let work =  DispatchWorkItem {
                task(i)
                group.leave()
            }
        group.enter()
        queue.async(execute: work)
    }

    // 全タスク終了で共用カウンタの値出力
    group.notify(queue: .main) { print("result=\(cnt.count)") }
}

doAsyncs()
出力結果
taskNo:7   i:10589
taskNo:2   i:10333
taskNo:10   i:10380
taskNo:4   i:10383
taskNo:1   i:10516
taskNo:3   i:10508
taskNo:6   i:10419
taskNo:8   i:10400
taskNo:9   i:10479
taskNo:5   i:10557
result=100001
  • iの合計値>ループ終了条件値なのは共用カウンタのインクリメントをアトミック操作してないため……。
  • Loopのインスタンスはタスクごとに別にしても、共用にしても結果変わらず。
  • そもそも終了条件をみたしているときはループを抜るので、問題にならないのか。
  • そうなると_loopVal = nilの操作がアトミックかを考えればいいだけかな?
  • Swiftがそのへんどうなのかは、また今度調べよう。
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1