Swift - Написание универсального c HTTP-запроса - PullRequest
2 голосов
/ 16 января 2020

Поэтому я пытаюсь получить некоторые данные, используя обычные HTTP-запросы с новым протоколом Swift Decodable.

Проблема в том, что я обнаруживаю, что снова и снова пишу очень похожий код (создайте Сеанс URL, декодирование данных, обработчик завершения и т. Д. c).

Я подумываю упростить использование обобщений или наследования, но не уверен, как можно инициализировать «объекты * generic c».

Текущий код:

var response = TypeAResponse()

if let url = URL(string: urlName) {

    let session = URLSession.shared
    var request = URLRequest(url: url)

    let task = session.dataTask(with: request){ (data, response, error) in

        if let data = data {

            do {
                response = try JSONDecoder().decode(TypeAResponse.self, from: data)
                completion(true, TypeAResponse.items)
            } catch let parseError {
                print("parseError: \(parseError.localizedDescription)")
                completion(false, TypeAResponse.items)
            }

        } else {
            print("Error: Data not found")
            completion(false, TypeAResponse.items)
        }
    }

    task.resume()

} else {
    print("Invalid URL")
    completion(false, TypeAResponse.items)
}

Но TypeAResponse имеет другую структуру, чтобы сказать TypeBResponse - все, что они разделяют, это атрибут 'items'.

Может сделать что-то вроде

    func get<T>(generalType: T, completion: GeneralRequest){
        //but how would you initialise generalType?
        //cannot do something like
        var response = T()

    }

Для справки, TypeAResponse и TypeBResponse могут выглядеть примерно так:

struct TypeAResponse: Decodable {
    items: [Track]?
}

struct Track: Decodable {
    //for example
    var name: String?
}

struct TypeBResponse: Decodable {
    items: [Playlist]?
}

struct Playlist: Decodable {
    var name, id, description: String?
}

Ответы API:

См. Ссылку на веб-API Spotify - https://developer.spotify.com/documentation/web-api/reference/playlists/get-playlist/

(в частности, «Получить плейлисты пользователя» и «Получить треки плейлиста»)

Ответы [ 2 ]

2 голосов
/ 17 января 2020

Вы, конечно, на хорошем пути. Всегда хорошо начать с конкретного кода, а затем посмотреть, как сделать его более обобщенным c. В вашем конкретном случае c, когда вы спрашиваете "как бы вы инициализировали generalType?" Это именно то, что позволяет протокол. Поэтому, следуя вашему подходу, вы могли бы написать что-то вроде (я не проверял это; возможно, у него есть некоторые синтаксические ошибки):

func get<T: Decodable>(generalType: T, completion: @escaping (Result<T, Error>) -> Void {

    let task = session.dataTask(with: request){ (data, response, error) in

        guard let data = data else {
            let err = error ?? ... some default error ...
            completion(.failure(err))
            return
        }

        let result = Result {
            // You know you can call `decode` with it because it's Decodable
            try JSONDecoder().decode(T.self, from: data)
        }
        completion(result)                     
    }
    task.resume()
}

Серия Начать с протокола может быть помощи (спасибо тем, кто упомянул об этом). Это углубляется в эту проблему. Но для простых приложений эта функция может быть всем, что вам нужно, и вам следует избегать добавления сложности, пока она вам не пригодится.

0 голосов
/ 17 января 2020

Имена ваших полей должны точно соответствовать именам полей в строке JSON, которую вы декодируете. Надеюсь, поможет следующий короткий пример. В зависимости от того, сколько вы на самом деле хотите декодировать, следующий объект сможет декодировать объект ответа из Spotify:

Предполагается, что ваш объект ответа является следующим (взято из пример Spotify ):

{
  "collaborative" : false,
  "description" : "Having friends over for dinner? Here´s the perfect playlist.",
  "external_urls" : {
    "spotify" : "http://open.spotify.com/user/spotify/playlist/59ZbFPES4DQwEjBpWHzrtC"
  },
  "followers" : {
    "href" : null,
    "total" : 143350
  },
  "href" : "https://api.spotify.com/v1/users/spotify/playlists/59ZbFPES4DQwEjBpWHzrtC",
  "id" : "59ZbFPES4DQwEjBpWHzrtC",
  "images" : [ {
    "url" : "https://i.scdn.co/image/68b6a65573a55095e9c0c0c33a274b18e0422736"
  } ],
  "name" : "Dinner with Friends",
  "owner" : {
    "external_urls" : {
      "spotify" : "http://open.spotify.com/user/spotify"
    },
    "href" : "https://api.spotify.com/v1/users/spotify",
    "id" : "spotify",
    "type" : "user",
    "uri" : "spotify:user:spotify"
  },
  "public" : null,
  "snapshot_id" : "bNLWdmhh+HDsbHzhckXeDC0uyKyg4FjPI/KEsKjAE526usnz2LxwgyBoMShVL+z+",
  "tracks" : {
    "href" : "https://api.spotify.com/v1/users/spotify/playlists/59ZbFPES4DQwEjBpWHzrtC/tracks",
    "items" : [ {
      "added_at" : "2014-09-01T04:21:28Z",
      "added_by" : {
        "external_urls" : {
          "spotify" : "http://open.spotify.com/user/spotify"
        },
        "href" : "https://api.spotify.com/v1/users/spotify",
        "id" : "spotify",
        "type" : "user",
        "uri" : "spotify:user:spotify"
      },
      "is_local" : false,
      "track" : {
        "album" : {
          "album_type" : "single",
          "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CH", "CL", "CO", "CR", "CY", "CZ", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ],
          "external_urls" : {
            "spotify" : "https://open.spotify.com/album/5GWoXPsTQylMuaZ84PC563"
          },
          "href" : "https://api.spotify.com/v1/albums/5GWoXPsTQylMuaZ84PC563",
          "id" : "5GWoXPsTQylMuaZ84PC563",
          "images" : [ {
            "height" : 640,
            "url" : "https://i.scdn.co/image/47421900e7534789603de84c03a40a826c058e45",
            "width" : 640
          }, {
            "height" : 300,
            "url" : "https://i.scdn.co/image/0d447b6faae870f890dc5780cc58d9afdbc36a1d",
            "width" : 300
          }, {
            "height" : 64,
            "url" : "https://i.scdn.co/image/d926b3e5f435ef3ac0874b1ff1571cf675b3ef3b",
            "width" : 64
          } ],
          "name" : "I'm Not The Only One",
          "type" : "album",
          "uri" : "spotify:album:5GWoXPsTQylMuaZ84PC563"
        },
        "artists" : [ {
          "external_urls" : {
            "spotify" : "https://open.spotify.com/artist/2wY79sveU1sp5g7SokKOiI"
          },
          "href" : "https://api.spotify.com/v1/artists/2wY79sveU1sp5g7SokKOiI",
          "id" : "2wY79sveU1sp5g7SokKOiI",
          "name" : "Sam Smith",
          "type" : "artist",
          "uri" : "spotify:artist:2wY79sveU1sp5g7SokKOiI"
        } ],
        "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CH", "CL", "CO", "CR", "CY", "CZ", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ],
        "disc_number" : 1,
        "duration_ms" : 204732,
        "explicit" : false,
        "external_ids" : {
          "isrc" : "GBUM71403920"
        },
        "external_urls" : {
          "spotify" : "https://open.spotify.com/track/4i9sYtSIlR80bxje5B3rUb"
        },
        "href" : "https://api.spotify.com/v1/tracks/4i9sYtSIlR80bxje5B3rUb",
        "id" : "4i9sYtSIlR80bxje5B3rUb",
        "name" : "I'm Not The Only One - Radio Edit",
        "popularity" : 45,
        "preview_url" : "https://p.scdn.co/mp3-preview/dd64cca26c69e93ea78f1fff2cc4889396bb6d2f",
        "track_number" : 1,
        "type" : "track",
        "uri" : "spotify:track:4i9sYtSIlR80bxje5B3rUb"
      }
    }],
    "limit" : 100,
    "next" : "https://api.spotify.com/v1/users/spotify/playlists/59ZbFPES4DQwEjBpWHzrtC/tracks?offset=100&limit=100",
    "offset" : 0,
    "previous" : null,
    "total" : 105
  },
  "type" : "playlist",
  "uri" : "spotify:user:spotify:playlist:59ZbFPES4DQwEjBpWHzrtC"
}
struct Playlist: Decodable {
    var collaborative: Bool?
    var description: String?
    var external_urls: [String: String]?
    var followers: FollowerData?
    var href, id: String?
    var images: [Image]?
    var name: String?
    var owner: User?
    var `public`: String?
    var snapshot_id: String?
    var tracks: Track?
    var type: String?
    var uri: String?
}

struct FollowerData: Decodable {
    var href: String?
    var total: Int?
}

struct User: Decodable {
    var external_urls: [String: String]?
    var href, id, name, type, uri: String?
}

struct Track: Decodable {
    var href: String?
    var items: [TrackItem]?
    var limit: Int?
    var next: String?
    var offset: Int?
    var previous: String?
    var total: Int?
}

struct TrackItem: Decodable {
    var added_at: String?
    var added_by: User?
    var is_local: Bool?
    var track: TrackDetails?
}

struct TrackDetails: Decodable {
    var album: Album?
    var artists: [User]?
    var available_markets: [String]?
    var disc_number: Int?
    var duration_ms: Int?
    var explicit: Bool?
    var external_ids: [String: String]?
    var external_urls: [String: String]?
    var href, id, name: String?
    var popularity: Int?
    var preview_url: String?
    var track_number: Int?
    var type: String?
    var uri: String?
}

struct Album: Decodable {
    var album_type: String?
    var available_markets: [String]?
    var external_urls: [String: String]?
    var href, id: String?
    var images: [Image]?
}

struct Image: Decodable {
    var width, height: Int?
    var url: String?
}

let jsonString = "{ \"collaborative\" : false, \"description\" : \"Having friends over for dinner? Here´s the perfect playlist.\", \"external_urls\" : { \"spotify\" : \"http://open.spotify.com/user/spotify/playlist/59ZbFPES4DQwEjBpWHzrtC\" }, \"followers\" : { \"href\" : null, \"total\" : 143350 }, \"href\" : \"https://api.spotify.com/v1/users/spotify/playlists/59ZbFPES4DQwEjBpWHzrtC\", \"id\" : \"59ZbFPES4DQwEjBpWHzrtC\", \"images\" : [ { \"url\" : \"https://i.scdn.co/image/68b6a65573a55095e9c0c0c33a274b18e0422736\" } ], \"name\" : \"Dinner with Friends\", \"owner\" : { \"external_urls\" : { \"spotify\" : \"http://open.spotify.com/user/spotify\" }, \"href\" : \"https://api.spotify.com/v1/users/spotify\", \"id\" : \"spotify\", \"type\" : \"user\", \"uri\" : \"spotify:user:spotify\" }, \"public\" : null, \"snapshot_id\" : \"bNLWdmhh+HDsbHzhckXeDC0uyKyg4FjPI/KEsKjAE526usnz2LxwgyBoMShVL+z+\", \"tracks\" : { \"href\" : \"https://api.spotify.com/v1/users/spotify/playlists/59ZbFPES4DQwEjBpWHzrtC/tracks\", \"items\" : [ { \"added_at\" : \"2014-09-01T04:21:28Z\", \"added_by\" : { \"external_urls\" : { \"spotify\" : \"http://open.spotify.com/user/spotify\" }, \"href\" : \"https://api.spotify.com/v1/users/spotify\", \"id\" : \"spotify\", \"type\" : \"user\", \"uri\" : \"spotify:user:spotify\" }, \"is_local\" : false, \"track\" : { \"album\" : { \"album_type\" : \"single\", \"available_markets\" : [ \"AD\", \"AR\", \"AT\", \"AU\", \"BE\", \"BG\", \"BO\", \"BR\", \"CH\", \"CL\", \"CO\", \"CR\", \"CY\", \"CZ\", \"DK\", \"DO\", \"EC\", \"EE\", \"ES\", \"FI\", \"FR\", \"GB\", \"GR\", \"GT\", \"HK\", \"HN\", \"HU\", \"IE\", \"IS\", \"IT\", \"LI\", \"LT\", \"LU\", \"LV\", \"MC\", \"MT\", \"MY\", \"NI\", \"NL\", \"NO\", \"NZ\", \"PA\", \"PE\", \"PH\", \"PL\", \"PT\", \"PY\", \"RO\", \"SE\", \"SG\", \"SI\", \"SK\", \"SV\", \"TR\", \"TW\", \"UY\" ], \"external_urls\" : { \"spotify\" : \"https://open.spotify.com/album/5GWoXPsTQylMuaZ84PC563\" }, \"href\" : \"https://api.spotify.com/v1/albums/5GWoXPsTQylMuaZ84PC563\", \"id\" : \"5GWoXPsTQylMuaZ84PC563\", \"images\" : [ { \"height\" : 640, \"url\" : \"https://i.scdn.co/image/47421900e7534789603de84c03a40a826c058e45\", \"width\" : 640 }, { \"height\" : 300, \"url\" : \"https://i.scdn.co/image/0d447b6faae870f890dc5780cc58d9afdbc36a1d\", \"width\" : 300 }, { \"height\" : 64, \"url\" : \"https://i.scdn.co/image/d926b3e5f435ef3ac0874b1ff1571cf675b3ef3b\", \"width\" : 64 } ], \"name\" : \"I'm Not The Only One\", \"type\" : \"album\", \"uri\" : \"spotify:album:5GWoXPsTQylMuaZ84PC563\" }, \"artists\" : [ { \"external_urls\" : { \"spotify\" : \"https://open.spotify.com/artist/2wY79sveU1sp5g7SokKOiI\" }, \"href\" : \"https://api.spotify.com/v1/artists/2wY79sveU1sp5g7SokKOiI\", \"id\" : \"2wY79sveU1sp5g7SokKOiI\", \"name\" : \"Sam Smith\", \"type\" : \"artist\", \"uri\" : \"spotify:artist:2wY79sveU1sp5g7SokKOiI\" } ], \"available_markets\" : [ \"AD\", \"AR\", \"AT\", \"AU\", \"BE\", \"BG\", \"BO\", \"BR\", \"CH\", \"CL\", \"CO\", \"CR\", \"CY\", \"CZ\", \"DK\", \"DO\", \"EC\", \"EE\", \"ES\", \"FI\", \"FR\", \"GB\", \"GR\", \"GT\", \"HK\", \"HN\", \"HU\", \"IE\", \"IS\", \"IT\", \"LI\", \"LT\", \"LU\", \"LV\", \"MC\", \"MT\", \"MY\", \"NI\", \"NL\", \"NO\", \"NZ\", \"PA\", \"PE\", \"PH\", \"PL\", \"PT\", \"PY\", \"RO\", \"SE\", \"SG\", \"SI\", \"SK\", \"SV\", \"TR\", \"TW\", \"UY\" ], \"disc_number\" : 1, \"duration_ms\" : 204732, \"explicit\" : false, \"external_ids\" : { \"isrc\" : \"GBUM71403920\" }, \"external_urls\" : { \"spotify\" : \"https://open.spotify.com/track/4i9sYtSIlR80bxje5B3rUb\" }, \"href\" : \"https://api.spotify.com/v1/tracks/4i9sYtSIlR80bxje5B3rUb\", \"id\" : \"4i9sYtSIlR80bxje5B3rUb\", \"name\" : \"I'm Not The Only One - Radio Edit\", \"popularity\" : 45, \"preview_url\" : \"https://p.scdn.co/mp3-preview/dd64cca26c69e93ea78f1fff2cc4889396bb6d2f\", \"track_number\" : 1, \"type\" : \"track\", \"uri\" : \"spotify:track:4i9sYtSIlR80bxje5B3rUb\" } }], \"limit\" : 100, \"next\" : \"https://api.spotify.com/v1/users/spotify/playlists/59ZbFPES4DQwEjBpWHzrtC/tracks?offset=100&limit=100\", \"offset\" : 0, \"previous\" : null, \"total\" : 105 }, \"type\" : \"playlist\", \"uri\" : \"spotify:user:spotify:playlist:59ZbFPES4DQwEjBpWHzrtC\"}"
if let data = jsonString.data(using: .utf8) {
    let playlist = try? JSONDecoder().decode(Playlist.self, from: data)
    print(playlist ?? "fail")
}
...