68120


以下のSwiftコードのネストを浅くしたいのですが、いい案はないでしょうか？

[code]

    task = URLSession.shared.dataTask(with: URL(string: url)!) { (data, res, err) in
        if let obj = try! JSONSerialization.jsonObject(with: data!) as? [String: Any] {
            if let items = obj["items"] as? [[String: Any]] {
                self.repo = items
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            }
        }
    }
    
[/code]


CC BY-SA 4.0
2020-06-29T05:02:21.613
68098
2020-06-30T08:21:50.267
2020-06-29T08:17:35.087
3060
40856
question
swift
Swiftのネストを浅くしたい


一つネストを減らせました。

参考：[Swift 1.2のif
let複数宣言と例外処理](https://qiita.com/boohbah/items/84e9d76a8ceaf9f56077)

[code]

    task = URLSession.shared.dataTask(with: URL(string: url)!) { (data, res, err) in
        if let obj = try! JSONSerialization.jsonObject(with: data!) as? [String: Any],
           let items = obj["items"] as? [[String: Any]] {
               self.repo = items
               DispatchQueue.main.async {
                   self.tableView.reloadData()
               }
        }
    }
    
[/code]


CC BY-SA 4.0
2020-06-29T05:12:11.327
68099
2020-06-29T05:12:11.327
40856
68098
answer


ネストを減らすと言う意味では、guard文の採用も検討された方が良いでしょう。

[code]

    task = URLSession.shared.dataTask(with: URL(string: url)!) { (data, res, err) in
        guard let obj = try! JSONSerialization.jsonObject(with: data!) as? [String: Any],
            let items = obj["items"] as? [[String: Any]]
            else {
                print("bad data")
                return
        }
        self.repo = items
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }
    
[/code]

* * *

なお、コード例の中で`try!`を使用していますが、`JSONSerialization.jsonObject(with:)`は、入力となるデータに1バイトでもおかしな点があれば簡単に失敗するので、`try!`を使用するとそこでアプリがクラッシュしてしまいます。「強制tryは確実に安全と分かっている場合以外は使ってはいけない」と覚えておいた方が良いと思います。

別質問とも関わりますが、その他の危険な強制アンラップも全部避け、エラーチェックもきちんとやるとこんな感じになります。「ネストを減らす」と言うご質問の趣旨からは外れることになりますが、`URLSession`は様々な理由でエラー終了しますので、(今は練習用のコードなのかもしれませんが、例えそうだとしても)必要なエラー処理を省略すべきではありません。

[code]

    guard let theUrl = URL(string: url) else {
        print("bad URL string")
        return
    }
    task = URLSession.shared.dataTask(with: theUrl) { (data, res, err) in
        do {
            if let error = err {
                throw error
            }
            guard let data = data else {
                print("data is nil")
                throw MyError.dataNil
            }
            guard let obj = try JSONSerialization.jsonObject(with: data) as? [String: Any],
                let items = obj["items"] as? [[String: Any]]
                else {
                    throw MyError.badData
            }
            self.repo = items
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        } catch {
            print(error)
            //必要であればエラー時の処理を追加
        }
    }
    
[/code]

(どこかにこんなものを定義してある前提のコードです。)

[code]

    enum MyError: Error {
        case dataNil
        case badData
    }
    
[/code]


CC BY-SA 4.0
2020-06-29T07:40:55.010
68107
2020-06-29T16:50:08.917
2020-06-29T16:50:08.917
3060
13972
68098
answer


はじめまして、`URLSession`で非同期なので、タイミングによっては`self`が解放されている可能性まで考えてのコードです。  
`guard`文の多用になってあまりすっきりはしていないかも知れませんが、あえて `guard let a = b, let c =
d`の様な一つのguard文で複数のチェックを行う事は今回は避けています。  
エラー処理の構文まで含めると1ネスト減らすことが出来ていて、  
ロジック自体は2ネスト減らすことができそうです。

[code]

    guard let url: URL = URL(string: url) else {
        print("URL not constructed")
        return
    }
    task = URLSession.shared.dataTask(with: url) { [weak self] (data, res, err) in
        guard let weakSelf = self else {
            print("self already deallocated")
            return
        }
        guard let jsonData: Data = data else {
            print("received data not exist"
            return
        }
        guard let obj = try? JSONSerialization.jsonObject(with: data!) as? [String: Any] {
            print("JSON Serialization failed")
            return
        }
        guard let items = obj["items"] as? [[String: Any]] else {
            print("items is not much type")
            return
        }
        weakSelf.repo = items
        DispatchQueue.main.async { [weak self] in
            guard let weakSelf = self else {
                print("self is already deallocated")
                return
            }
            weakSelf.tableView.reloadData()
        }
    }
    
[/code]

`try!`は続くメソッドや関数が例外を発生させた時にエラーでアプリケーションそのものが終了してしまうため、`try?`というエラーが起きた時は`nil`を返す`try`に書き替えてあります。  
これにより、`obj`というローカル変数はそれ以降必ずあることが保証されるので、`guard`文によるアンラップチェックを多用してデーターが十全であることをアンラップが必要な度にしつこいほど確認・保証する作りになっています。これによりなにをアンラップした時にエラーになったか？、が続く処理ですぐにかけるので、エラーの発生箇所と対処箇所がすぐ近くに書けるのもメリットの一つだと思います

また、`URLSession`にデーターをリクエストしているので、指定したURLからデーターが帰ってきた時には、呼び出したクラスが解放しようとした後かも知れない場合も考慮し、クロージャーの先頭に  
`[weak self] (引数があればここに書く) in`という「このブロックの中では`self`を弱参照してメモリーリークを防ぐ」と共に、  
`guard let weakSelf = self else { return }`  
という一文で弱参照している`self`が解放されていない（nilでない）ことも確認しています。

コード全体のネストは一段しか減らせていないですが、オプショナル型のアンラップ時の`nil`によるアプリのクラッシュを可能な限り防ぐ方向に振っています。


CC BY-SA 4.0
2020-06-29T17:37:02.593
68120
2020-06-29T17:37:02.593
14745
68098
answer


回答してくださったのを統合した感じでコーディングしてみました。

そしてErrorに関するMyError.swiftのファイルを別に作りました。

## わかったこと

  * try?文は基本「guard else」とセットで使うこと
  * Error処理をちゃんとすること
  * 

[code]

    guard let url: URL = URL(string: url) else {
        print("URL not constructed")
        return
    }
                
    task = URLSession.shared.dataTask(with: url) { [weak self] (data, res, err) in
        do {
            if let error = err {
                throw error
            }
                    
            guard let weakSelf = self else {
                print("self already deallocated")
                return
            }
    
            guard let jsonData = data else {
                print("data is nil")
                throw MyError.dataNil
            }
            guard let obj = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
                print("JSON Serialization failed")
                throw MyError.badData
            }
    
            guard let items = obj["items"] as? [[String: Any]] else {
                print("items is not much type")
    　　　　　　　throw MyError.badData
            }
            weakSelf.repo = items
            DispatchQueue.main.async { [weak self] in
                guard let weakSelf = self else {
                    print("self is already deallocated")
                    return
                }
                weakSelf.tableView.reloadData()
            }
        }catch {
                print(error)
        }
    }
    
    
[/code]


CC BY-SA 4.0
2020-06-30T08:13:49.103
68139
2020-06-30T08:21:50.267
2020-06-30T08:21:50.267
40856
40856
68098
answer