68301


The
MovieDBという映画データベースのAPIを使って映画一覧をJSONから抜き出したいのですが、moviesにJSONから抜き出したresultをinitで設定しているのですが、いざMovieListを使ってMovieList.moviesを表示しようとすると空の配列になってしまいます。  
なぜ空に戻ってしまうのでしょうか？途中のprint(weakSelf.movies[0]["title"]!)ではデータを表示できています。  
API_KEYを公開できないため実行はできないと思います。

[code]

    import Foundation
    import UIKit
    
    class MovieList{
        
        let apiKey: String = "API_KEY"
        var pageURL: String
        var movies : [[String: Any]] = []
    
        
        init(page: Int){
            pageURL = "https://api.themoviedb.org/3/movie/now_playing?api_key=\(apiKey)&language=ja&page=\(page.description))"
            
            guard let url: URL = URL(string: pageURL) else {
                print("URL not constructed")
                return
            }
    
            let task: URLSessionTask = URLSession.shared.dataTask(with: url) { [weak self] (data, res, err) in
                do {
                    if let error = err {
                        throw error
                    }
                    guard let jsonData = data else {
                        print("data is nil")
                        return
                    }
                    guard let obj = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
                         print("JSON Serialization failed")
                         return
                    }
                    
                    guard let results = obj["results"] as? [[String: Any]] else {
                        print("items is not much type")
                        return
                    }
                    
                    DispatchQueue.main.async { [weak self] in
                        guard let weakSelf = self else {
                            print("self is already deallocated")
                            return
                        }
                        weakSelf.movies = results
                        print(weakSelf.movies[0]["title"]!)
                    }
                }catch {
                    print(error)
                }
            }
            task.resume()
        }
    }
    
    var movieList: MovieList = MovieList(page: 1)
    
    print(movieList.movies)
    
[/code]

[code]

    {
        "results": [
            {
                "popularity": 138.944,
                "vote_count": 255,
                "video": false,
                "poster_path": "\/9zrbgYyFvwH8sy5mv9eT25xsAzL.jpg",
                "id": 531454,
                "adult": false,
                "backdrop_path": "\/jMO1icztaUUEUApdAQx0cZOt7b8.jpg",
                "original_language": "en",
                "original_title": "Eurovision Song Contest: The Story of Fire Saga",
                "genre_ids": [
                    35,
                    10402
                ],
                "title": "ユーロビジョン歌合戦 〜ファイア・サーガ物語〜",
                "vote_average": 6.6,
                "overview": "世界最大級の歌合戦に出場するチャンスを手にした小さな町のデュオ。だが有力なライバルや邪魔者、舞台での思わぬ出来事が、夢をかなえたい2人の行く手を阻む。",
                "release_date": "2020-06-26"
            },
            {
                "popularity": 107.457,
                "id": 475430,
                "video": false,
                "vote_count": 548,
                "vote_average": 5.9,
                "title": "アルテミスと妖精の身代金",
                "release_date": "2020-06-12",
                "original_language": "en",
                "original_title": "Artemis Fowl",
                "genre_ids": [
                    12,
                    14,
                    878,
                    10751
                ],
                "backdrop_path": "\/o0F8xAt8YuEm5mEZviX5pEFC12y.jpg",
                "adult": false,
                "overview": "",
                "poster_path": "\/mhDdx7o7hhrxrikq8aqPLLnS9w8.jpg"
            },
            {
                "popularity": 84.03,
                "id": 619592,
                "video": false,
                "vote_count": 46,
                "vote_average": 5.3,
                "title": "Force of Nature",
                "release_date": "2020-07-02",
                "original_language": "en",
                "original_title": "Force of Nature",
                "genre_ids": [
                    28,
                    18
                ],
                "backdrop_path": "\/jAtO4ci8Tr5jDmg33XF3OZ8VPah.jpg",
                "adult": false,
                "overview": "",
                "poster_path": "\/ucktgbaMSaETUDLUBp1ubGD6aNj.jpg"
            }
        ]
    }
    
[/code]


CC BY-SA 4.0
2020-07-05T08:52:20.510
68297
2020-07-05T10:47:10.953
2020-07-05T10:14:41.993
40856
40856
question
swift json
JSONからデータを抜き出したい


非同期処理について、理解が不十分なようです。

あなたのコードで`{ [weak self] (data, res, err)
in`から、`task.resume()`の前の行の`}`までは、`URLSession`の[`dataTask`メソッド](https://developer.apple.com/documentation/foundation/urlsession/1407613-datatask)の`completionHandler:`引数にクロージャーとして渡されたものです。(以下、「完了ハンドラー」と書きます。)

この完了ハンドラーは、`task.resume()`で開始された通信処理が、

**完了するまで実行されません。**

[code]

    var movieList: MovieList = MovieList(page: 1) //<-`MovieList.init`の中で通信処理が開始される
    
    print(movieList.movies) //<-この時点で通信は完了していないので`movies`は初期値の`[]`のまんま
    
    //この後のどこかのタイミングで完了ハンドラーが実行され、`movieList.movies`に値が設定される
    
[/code]

つまり、「空に戻ってしまう」のではなく、 **「まだ空の間に使おうとしている」** のです。

* * *

通信が完了した後でないと意味を持たない処理は、全て完了ハンドラーの中に記述しないといけません。

どんな処理を`MovieList`の中に書けば良いのか、事前には決定できないのであれば、自前完了ハンドラーを引数に加え、それを呼んでやるようにします。

[code]

    import UIKit
    
    class MovieList {
        
        let apiKey: String = "API_KEY"
        var pageURL: String
        var movies : [[String: Any]] = []
        
        init(page: Int, completion: @escaping ([[String: Any]])->Void) { //<-自前の完了ハンドラーを宣言
            pageURL = "https://api.themoviedb.org/3/movie/now_playing?api_key=\(apiKey)&language=ja&page=\(page.description))"
            
            guard let url: URL = URL(string: pageURL) else {
                print("URL not constructed")
                return
            }
    
            let task = URLSession.shared.dataTask(with: url) { (data, res, err) in
                do {
                    if let error = err {
                        throw error
                    }
                    guard let jsonData = data else {
                        print("data is nil")
                        return
                    }
                    guard let obj = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
                         print("Not JSON object")
                         return
                    }
                    
                    guard let results = obj["results"] as? [[String: Any]] else {
                        print("items does not match type")
                        return
                    }
                    
                    DispatchQueue.main.async {
                        self.movies = results
                        print(self.movies[0]["title"]!)
                        completion(self.movies) //<-通信が完了したら自前の完了ハンドラーを呼ぶ
                    }
                } catch {
                    print(error)
                }
            }
            task.resume()
        }
    }
    
    let movieList: MovieList = MovieList(page: 1) {movies in //<-自前の完了ハンドラーにクロージャーを渡す
        print(movies) //<-この`print`は通信が完了してから実行される
    }
    
[/code]

以前別質問の回答へのコメントとして書いた事柄については、修正してあります。また、もし、Playgroundで試しているのであれば、次のようなコードがないとPlaygroundが通信処理の完了を待ってくれないことがあります。

[code]

    import PlaygroundSupport
    PlaygroundPage.current.needsIndefiniteExecution = true
    
[/code]

なお、現在のコードには、「エラーが起きたときには完了ハンドラーが呼ばれない」と言う大きな欠点があるのですが、あなたが`MovieList`クラスをどのように実アプリの中で使うのかが不明なので、ここではそのままにしておきました。

その辺も含めて、`MovieList.init`の中で通信処理を起動してしまう、と言うやり方自体が破綻する可能性も考えられますが、その辺はまた別質問としていただいた方が良いでしょう。


CC BY-SA 4.0
2020-07-05T10:47:10.953
68301
2020-07-05T10:47:10.953
13972
68297
answer