API call funktioniert nicht aus Xcode 16.2

  • API call funktioniert nicht aus Xcode 16.2

    Hallo,

    ich habe seit 10 Jahren nichts mehr in Xcode programmiert und wollte jetzt wieder damit anfangen. Ich wollte Wetterdaten von OpenWeatherMap empfangen, was aber nicht funktioniert hat. Deshalb habe ich nach einfachen Beispielen gesucht, um wieder in das Thema reinzukommen und bin auf dieses Video gestoßen:
    youtube.com/watch?v=ERr0GXqILgc&t=514s

    Das habe ich dann nachgebaut und bekomme aber immer den Fehler "Invalid data"

    Hier der Code:

    Quellcode

    1. import SwiftUI
    2. // Standard View model
    3. struct ContentView: View {
    4. @State private var user: GithubUser?
    5. var body: some View {
    6. VStack(spacing: 20) {
    7. AsyncImage(url: URL(string: user?.avatarUrl ?? "")) { image in
    8. image
    9. .resizable()
    10. .aspectRatio(contentMode: .fit)
    11. .clipShape(Circle())
    12. }
    13. placeholder: {
    14. Circle()
    15. .foregroundColor(.secondary)
    16. }
    17. .frame(width: 120, height: 120)
    18. Text(user?.login ?? "Login Placeholder")
    19. .bold()
    20. .font(.title)
    21. Text(user?.bio ?? "Bio Placeholder")
    22. .padding()
    23. Spacer()
    24. }
    25. .padding()
    26. .task {
    27. do {
    28. user = try await getUser()
    29. }
    30. catch GHError.invalidURL {
    31. print("Invalid URL!")
    32. }
    33. catch GHError.invalidResponse {
    34. print("Invalid response!")
    35. }
    36. catch GHError.invalidData {
    37. print("Invalid data!")
    38. }
    39. catch {
    40. print("Unexpected error")
    41. }
    42. }
    43. }
    44. // Normally in another View file
    45. func getUser() async throws -> GithubUser {
    46. let endpoint = "https://api.github.com/users/USERNAME"
    47. guard let url = URL(string: endpoint) else {
    48. throw GHError.invalidURL
    49. }
    50. let (data, response) = try await URLSession.shared.data(from: url)
    51. guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
    52. throw GHError .invalidResponse
    53. }
    54. do {
    55. let decoder = JSONDecoder()
    56. decoder.keyDecodingStrategy = .convertFromSnakeCase
    57. return try decoder.decode(GithubUser.self, from: data)
    58. }
    59. catch {
    60. throw GHError.invalidData
    61. }
    62. }
    63. }
    64. // Enable the phone preview
    65. struct ContentView_Previews: PreviewProvider {
    66. static var previews: some View {
    67. ContentView()
    68. }
    69. }
    70. // Model object (normally in a separate Model file)
    71. struct GithubUser: Codable {
    72. let login: String
    73. let avatarUrl: String
    74. let bio: String
    75. }
    76. // Create the error objects
    77. enum GHError: Error {
    78. case invalidURL
    79. case invalidResponse
    80. case invalidData
    81. }
    Alles anzeigen
  • Da die empfangenen Daten einen JSON String enthalten, kannst Du die Daten mal in einen String umwandeln und ausgeben. Evtl. kannst Du dann erkennen, warum der JSONDecoder diese nicht verarbeiten kann.

    Alternativ auch mal die genaue Beschreibung des JSONDecoder Fehlers im catch Block ausgeben.
  • Genau, wirf einfach den Debugger an oder gib den String aus, dass sollte dir schon Hinweise zu dem Problem geben.

    Oder ruf die Adresse einfach im Browser auf oder (besser) im Terminal mittels curl:

    Quellcode

    1. $ curl https://api.github.com/users/apple (base)
    2. {
    3. "login": "apple",
    4. "id": 10639145,
    5. "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwNjM5MTQ1",
    6. "avatar_url": "https://avatars.githubusercontent.com/u/10639145?v=4",
    7. "gravatar_id": "",
    8. "url": "https://api.github.com/users/apple",
    9. "html_url": "https://github.com/apple",
    10. "followers_url": "https://api.github.com/users/apple/followers",
    11. "following_url": "https://api.github.com/users/apple/following{/other_user}",
    12. "gists_url": "https://api.github.com/users/apple/gists{/gist_id}",
    13. "starred_url": "https://api.github.com/users/apple/starred{/owner}{/repo}",
    14. "subscriptions_url": "https://api.github.com/users/apple/subscriptions",
    15. "organizations_url": "https://api.github.com/users/apple/orgs",
    16. "repos_url": "https://api.github.com/users/apple/repos",
    17. "events_url": "https://api.github.com/users/apple/events{/privacy}",
    18. "received_events_url": "https://api.github.com/users/apple/received_events",
    19. "type": "Organization",
    20. "user_view_type": "public",
    21. "site_admin": false,
    22. "name": "Apple",
    23. "company": null,
    24. "blog": "https://apple.com",
    25. "location": "Cupertino, CA",
    26. "email": null,
    27. "hireable": null,
    28. "bio": null,
    29. "twitter_username": null,
    30. "public_repos": 312,
    31. "public_gists": 0,
    32. "followers": 27554,
    33. "following": 0,
    34. "created_at": "2015-01-21T20:19:28Z",
    35. "updated_at": "2024-11-13T16:53:13Z"
    36. }
    Alles anzeigen
    Wenn das Problem dir dann klar ist, dann kannst Du es gleich fixen. Wenn nicht, dann öffne in Xcode einen neuen Playground und probiere den JSONDecoder-Abschnitt getrennt vom restlich Code aus. Mit etwas rumprobieren, solltest Du dann schnell auf das Problem stoßen.
  • Hallo,

    also im browser und im terminal funktioniert alles, das hatte ich auch schon getestet.
    Da das Ganze ja in dem YouTube Video auch problemlos funktioniert, verstehe ich das Problem leider nicht.
    Wenn ihr den Fehler erkannt habt, wäre ich für einen Hinweis dankbar. Ich muss mich erst wieder in die Materie einarbeiten und meine Kenntnisse reichen momentan nicht, die Lösung zu erkennen.
    HTTP response ist 200 und es kommen auch Daten an.

    print(data) liefert 1208 bytes und print(response) dieses:

    Quellcode

    1. <NSHTTPURLResponse: 0x60000027f4a0> { URL: https://api.github.com/users/USERNAME } { Status Code: 200, Headers {
    2. "Accept-Ranges" = (
    3. bytes
    4. );
    5. "Access-Control-Allow-Origin" = (
    6. "*"
    7. );
    8. "Cache-Control" = (
    9. "public, max-age=60, s-maxage=60"
    10. );
    11. "Content-Encoding" = (
    12. gzip
    13. );
    14. "Content-Length" = (
    15. 463
    16. );
    17. "Content-Type" = (
    18. "application/json; charset=utf-8"
    19. );
    20. Date = (
    21. "Sat, 15 Mar 2025 14:10:22 GMT"
    22. );
    23. Etag = (
    24. "W/\"5009dbf00324fb870c6e6a6a9799f232b56933380538693ce386d73ca81fcbed\""
    25. );
    26. "Last-Modified" = (
    27. "Fri, 14 Mar 2025 12:50:14 GMT"
    28. );
    29. Server = (
    30. "github.com"
    31. );
    32. "Strict-Transport-Security" = (
    33. "max-age=31536000; includeSubdomains; preload"
    34. );
    35. Vary = (
    36. "Accept,Accept-Encoding, Accept, X-Requested-With"
    37. );
    38. "access-control-expose-headers" = (
    39. "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset"
    40. );
    41. "content-security-policy" = (
    42. "default-src 'none'"
    43. );
    44. "referrer-policy" = (
    45. "origin-when-cross-origin, strict-origin-when-cross-origin"
    46. );
    47. "x-content-type-options" = (
    48. nosniff
    49. );
    50. "x-frame-options" = (
    51. deny
    52. );
    53. "x-github-api-version-selected" = (
    54. "2022-11-28"
    55. );
    56. "x-github-media-type" = (
    57. "github.v3; format=json"
    58. );
    59. "x-github-request-id" = (
    60. "5EED:2972A8:205A40:21C0E3:67D58A4E"
    61. );
    62. "x-ratelimit-limit" = (
    63. 60
    64. );
    65. "x-ratelimit-remaining" = (
    66. 57
    67. );
    68. "x-ratelimit-reset" = (
    69. 1742051261
    70. );
    71. "x-ratelimit-resource" = (
    72. core
    73. );
    74. "x-ratelimit-used" = (
    75. 3
    76. );
    77. "x-xss-protection" = (
    78. 0
    79. );
    80. } }
    Alles anzeigen

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von Vetinari108 ()

  • Nur aus der Hüfte geschossen - ich kenne die API nicht: Kann es sein, dass Dein Problem hier liegt:

    Vetinari108 schrieb:

    Quellcode

    1. "Content-Encoding" = (
    2. gzip
    3. );
    Ich würde mir data mal genauer anschauen, ob das JSON eventuell gepackt ist (oder könnte der Decoder das)?

    Sorry, falls ich Dich damit auf eine falsche Fährte führen sollte, fiel mir nur gerade so am iPad auf…

    Mattes

    Edit: Ups, das Unzippen macht wohl schon URLSession für Dich. Also doch ein roter Hering, entschuldige. Trotzdem data ansehen… ;)
    Diese Seite bleibt aus technischen Gründen unbedruckt.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von MyMattes ()

  • Der Hinweis war doch, nutze den Debugger bzw. Xcode Playground.

    Playground Beispiel:

    Quellcode

    1. import Foundation
    2. let response = """
    3. {
    4. "login": "apple",
    5. "id": 10639145,
    6. "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwNjM5MTQ1",
    7. "avatar_url": "https://avatars.githubusercontent.com/u/10639145?v=4",
    8. "gravatar_id": "",
    9. "url": "https://api.github.com/users/apple",
    10. "html_url": "https://github.com/apple",
    11. "followers_url": "https://api.github.com/users/apple/followers",
    12. "following_url": "https://api.github.com/users/apple/following{/other_user}",
    13. "gists_url": "https://api.github.com/users/apple/gists{/gist_id}",
    14. "starred_url": "https://api.github.com/users/apple/starred{/owner}{/repo}",
    15. "subscriptions_url": "https://api.github.com/users/apple/subscriptions",
    16. "organizations_url": "https://api.github.com/users/apple/orgs",
    17. "repos_url": "https://api.github.com/users/apple/repos",
    18. "events_url": "https://api.github.com/users/apple/events{/privacy}",
    19. "received_events_url": "https://api.github.com/users/apple/received_events",
    20. "type": "Organization",
    21. "user_view_type": "public",
    22. "site_admin": false,
    23. "name": "Apple",
    24. "company": null,
    25. "blog": "https://apple.com",
    26. "location": "Cupertino, CA",
    27. "email": null,
    28. "hireable": null,
    29. "bio": null,
    30. "twitter_username": null,
    31. "public_repos": 312,
    32. "public_gists": 0,
    33. "followers": 27554,
    34. "following": 0,
    35. "created_at": "2015-01-21T20:19:28Z",
    36. "updated_at": "2024-11-13T16:53:13Z"
    37. }
    38. """
    39. struct GithubUser: Codable {
    40. let login: String
    41. let avatarUrl: String
    42. let bio: String
    43. }
    44. let decoder = JSONDecoder()
    45. decoder.keyDecodingStrategy = .convertFromSnakeCase
    46. let user = try? decoder.decode(GithubUser.self, from: Data(response.utf8))
    Alles anzeigen
    Das funktioniert jetzt nicht, aber "bio": null soll ein String sein (GithubUser) und null ist ein Typ in JSON was wohl in Swift nil wäre. Es müsste let bio: String? heißen, da bio nicht immer gesetzt ist.
  • Wenn user = nil ist, dann müsste JSONDecoder einen Fehler geworfen haben.
    Ergänze mal einen do-catch Block, ändere try? zu try und gib den evtl. geworfenen Fehler aus, also so:

    Quellcode

    1. do {
    2. let decoder = JSONDecoder()
    3. decoder.keyDecodingStrategy = .convertFromSnakeCase
    4. let user = try decoder.decode(GithubUser.self, from: Data(response.utf8))
    5. print("user: \(user)"
    6. }
    7. catch {
    8. print("JSONDecoder error: \(error)")
    9. }
  • Der JSONDecoder gibt folgenden Fehler aus, wenn bio: String und nicht bio: String? ist: "valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "bio", intValue: nil)], debugDescription: "Cannot get value of type String -- found null value instead", underlyingError: nil))".

    Wenn man sich das Response Schema zu Get a User anguckt, dann sieht das abgespeckt folgend aus:

    Quellcode

    1. {
    2. "oneOf": [
    3. {
    4. "title": "Private User",
    5. "description": "Private User",
    6. "type": "object",
    7. "properties": {
    8. "avatar_url": {
    9. "type": "string",
    10. "format": "uri",
    11. "examples": [
    12. "https://github.com/images/error/octocat_happy.gif"
    13. ]
    14. },
    15. "name": {
    16. "type": [
    17. "string",
    18. "null"
    19. ],
    20. "examples": [
    21. "monalisa octocat"
    22. ]
    23. },
    24. "bio": {
    25. "type": [
    26. "string",
    27. "null"
    28. ],
    29. "examples": [
    30. "There once was..."
    31. ]
    32. },
    33. ...
    34. },
    35. "required": [
    36. "avatar_url",
    37. "bio",
    38. "name",
    39. ...
    40. ]
    41. },
    42. {
    43. "title": "Public User",
    44. "description": "Public User",
    45. "type": "object",
    46. "properties": {
    47. "avatar_url": {
    48. "type": "string",
    49. "format": "uri"
    50. },
    51. "name": {
    52. "type": [
    53. "string",
    54. "null"
    55. ]
    56. },
    57. "bio": {
    58. "type": [
    59. "string",
    60. "null"
    61. ]
    62. },
    63. ...
    64. },
    65. "required": [
    66. "avatar_url",
    67. "bio",
    68. "name",
    69. ...
    70. ],
    71. "additionalProperties": false
    72. }
    73. ]
    74. }
    Alles anzeigen
    Die drei Felder, avatar_url, name und bio, sind immer vorhanden und in JSON entweder null oder string. Das muss man in der Klasse GithubUser berücksichtigen. Ansonsten gibt's einen Fehler wenn etwas auf einmal nicht vorhanden ist.

    @Vetinari108

    In deinem Beispielcode wird der Fehler einfach weggefangen und ein neuer allgemeiner Fehler wird geworfen. Damit weißt Du halt nicht was der ursprüngliche Fehler war und müsstest im Debugger einen Breakpoint setzten, um den zu sehen. Oder gib den Fehler einfach aus. Den siehst Du dan in der Console in Xcode.

    Quellcode

    1. catch {
    2. print("Failed to get user because of an error: \(error)")
    3. throw GHError.invalidData
    4. }