2017-11-13 21 views
1

이 함수를 사용하여 currentWeather (currentWeather 유형)의 전역 변수를 수정하려고합니다.이 변수는 URL에서 검색 한 정보로 변수를 업데이트하고 해당 변수를 나타내는 bool을 반환합니다. 성공. 그러나 currentWeather는 여전히 nil이므로이 함수는 false를 반환합니다. 나는 dataTask가 비동기이며, 태스크가 어플리케이션과 평행 한 백그라운드에서 실행되고 있다는 것을 알고 있지만, 나는 이것을 달성하기 위해 무엇을 의미하는지 이해하지 못한다. do 블록 이후에 currentWeather를 업데이트 할 수 없기 때문에 날씨가 더 이상 블록을 종료 한 후에 인식되지 않습니다. 나는 "self.currentWeather"를 사용하려고 시도했으나 해결되지 않은 식별자라고 전했다. 아마도 함수가 글로벌이기도하고 "self"도 없기 때문일 것이다.글로벌 변수 내부 변경 수정 (Swift 4)

API 키를 가져 왔기 때문에 URL이 유효하지 않지만 예상대로 작동하며 CurrentWeather 구조체가 디코딩 가능합니다. 현재 인쇄중인 WindwinUnrapped도 일관되게 성공합니다.

나는 Stack Overflow와 Apple의 공식 문서를보고 내 질문에 대한 답변을 찾을 수 없었지만 아마도 충분히 철저하지 않았습니다. 이 질문이 중복되는 것이 유감입니다. 더 깊은 관련 독서 방향은 또한 높이 평가됩니다! 최고의 코딩 방법에 대한 적합성의 부족에 대해 사과드립니다 - 지금은 경험이별로 없습니다. 정말 고마워요!

func getCurrentWeather() -> Bool { 
let jsonUrlString = "https://api.wunderground.com/api/KEY/conditions/q/\(state)/\(city).json" 

guard let url = URL(string: jsonUrlString) else { return false } 

URLSession.shared.dataTask(with: url) { (data, response, err) in 
    // check error/response 

    guard let data = data else { return } 

    do { 
     let weather = try JSONDecoder().decode(CurrentWeather.self, from: data) 
     currentWeather = weather 
     if let currentWeatherUnwrapped = currentWeather { 
      print(currentWeatherUnwrapped) 
     } 
    } catch let jsonErr { 
     print("Error serializing JSON: ", jsonErr) 
    } 

    // cannot update currentWeather here, as weather is local to do block 

    }.resume() 

return currentWeather != nil 
} 
+0

왜 부검을 반환합니까? bool을 반환하는 목적은 무엇입니까? UI를 업데이트하기 위해 서버에서 가져온 아들이 필요하지 않습니까 ?? –

+0

이 시점에서 필자는 함수의 성공 여부를 확인하기 위해 bool 만 반환합니다. 나는 어제의 날씨와 오늘의 시간별 날씨를 얻기위한 비슷한 기능을 가지고 있으며, 세 가지 모두의 성공을 확인한 다음 이에 따라 UI를 업데이트하는 기능을 가지고 있습니다. 내가 무엇을 돌려 줄 것을 권하겠습니까? 감사! – Caleb

+0

'Void'를 반환해야합니다. 이 함수는 결과에 대한 매개 변수와 선택적 오류가있는 클로저를 허용해야합니다. 이 함수는'guard '에서 던져 질 수도 있습니다. – Paulw11

답변

2

는 당신이 필요로하는 모든이 폐쇄입니다.

본질적으로 비동기 인 웹 서비스 호출의 응답을 반환하는 동기식 return 문을 사용할 수 없습니다. 그것 때문에 폐쇄가 필요합니다.

다음과 같이 답변을 수정할 수 있습니다. 당신이 논평에서 내 질문에 대답하지 않았기 때문에, 나는 많은 의미를 가지지 않는 bool을 반환하는 것이 아니라 더 튼튼한 물체를 반환 할 수있는 자유를 얻었습니다.

으로 지적 : currentWeather 가정

func getCurrentWeather (completion : @escaping((CurrentWeather?) ->())){ 
     let jsonUrlString = "https://api.wunderground.com/api/KEY/conditions/q/" 

     guard let url = URL(string: jsonUrlString) else { return false } 

     URLSession.shared.dataTask(with: url) { (data, response, err) in 
      // check error/response 

      guard let data = data else { return } 

      do { 
       let weather = try JSONDecoder().decode(CurrentWeather.self, from: data) 
       CurrentWeather.currentWeather = weather 
       if let currentWeatherUnwrapped = currentWeather { 
        completion(CurrentWeather.currentWeather) 
       } 
      } catch let jsonErr { 
       print("Error serializing JSON: ", jsonErr) 
       completion(nil) 
      } 

      // cannot update currentWeather here, as weather is local to do block 

      }.resume() 
    } 

는 글로벌 변수를 업데이트뿐만 아니라

편집

위의 그림과 같이 호출자에게 실제 데이터를 반환 할 수 있습니다 CurrentWeather 클래스의 정적 변수 아래의 주석에서 Duncan에 의해 위의 코드는 백그라운드 스레드에서 완료 블록을 실행합니다. 모든 UI 작업은 주 스레드에서만 수행해야합니다. 따라서 UI를 업데이트하기 전에 스레드를 전환하는 것이 매우 중요합니다.

두 가지 방법 :

1 당신은 주 스레드에서 완료 블록을 실행해야합니다.

DispatchQueue.main.async { 
     completion(CurrentWeather.currentWeather) 
} 

이 사용 누구든지 당신의 미래에 getCurrentWeather이 방법은 그것을 담당하기 때문에 스레드 전환에 대해 걱정할 필요가 없다는 것을 확인합니다. 완료 블록에 UI를 업데이트하는 코드 만 포함 된 경우에 유용합니다. 이 접근법을 사용하는 완성 블록의 로직 길이가 길어지면 메인 스레드에 부담이됩니다.

2 - 당신은 당신이 UI 요소는

DispatchQueue.main.async { 
    //your code to update UI 
} 

편집 2에 그 문을 감싸 확인 업데이트 할 때마다 getCurrentWeather에 매개 변수로 전달할 것을 완료 블록의 다른 :

바와 같이 지적 아래의 댓글에서 Leo Dabus을 입력하면 guard let url = URL(string: jsonUrlString) else { return false } 대신 완료 블록을 실행해야합니다. 복사 붙여 넣기 오류였습니다. 나는 OP 질문을 복사했고 서둘러서 진술서가 있다는 것을 깨달았다.

이 경우에는 매개 변수로 오류가 발생하지만이 오류 처리 모델을 어떻게 설계했는지는 완전히 다릅니다. 레오 다 부스 (Leo Dabus)가 제안한 아이디어에 감사드립니다. 좀 더 일반적인 접근 방식이므로 내 대답이 다음과 같이 업데이트됩니다. 매개 변수

예를 들어 guard let data = data else { return }이 false를 반환하는 경우와 같이 사용자 지정 오류를 보내야하는 경우가 있습니다. 단순히 return을 호출하는 대신 올바르지 않은 입력 또는 이와 비슷한 오류를 반환해야 할 수 있습니다. data askasync 의미이며,

은 그래서 난 내 자신의 사용자 지정 오류를 선언하기 위해 자유를 촬영하고 당신이 지적한 것처럼 당신은뿐만 아니라

enum CustomError : Error { 
    case invalidServerResponse 
    case invalidURL 
} 

func getCurrentWeather (completion : @escaping((CurrentWeather?,Error?) ->())){ 
     let jsonUrlString = "https://api.wunderground.com/api/KEY/conditions/q/" 

     guard let url = URL(string: jsonUrlString) else { 
      DispatchQueue.main.async { 
       completion(nil,CustomError.invalidURL) 
      } 
      return 
     } 

     URLSession.shared.dataTask(with: url) { (data, response, err) in 
      // check error/response 

      if err != nil { 
       DispatchQueue.main.async { 
        completion(nil,err) 
       } 
       return 
      } 

      guard let data = data else { 
       DispatchQueue.main.async { 
        completion(nil,CustomError.invalidServerResponse) 
       } 
       return 
      } 

      do { 
       let weather = try JSONDecoder().decode(CurrentWeather.self, from: data) 
       CurrentWeather.currentWeather = weather 

       if let currentWeatherUnwrapped = currentWeather { 
        DispatchQueue.main.async { 
         completion(CurrentWeather.currentWeather,nil) 
        } 
       } 

      } catch let jsonErr { 
       print("Error serializing JSON: ", jsonErr) 
       DispatchQueue.main.async { 
        completion(nil,jsonErr) 
       } 
      } 

      // cannot update currentWeather here, as weather is local to do block 

      }.resume() 
    } 
+1

리팩토링 된 방법을 보여주는 매우 철저한 답입니다. (투표가 끝났습니다.)하지만 완료 폐쇄는 백그라운드 스레드에서 호출 될 것이므로 모든 UI 변경 사항은'DispatchQueue.main.async()'호출로 래핑되어야합니다. –

+0

@ duncan-c : 고마워요 :) 스레드 전환 코드를 놓쳤습니다. 지적 해 주셔서 고마워요. 답변에서 동일하게 언급하고 그것을 업데이트했습니다 :) –

+1

첫 번째 가드하자 url = URL (문자열 : jsonUrlString) else {return false}'??? 거기에서'거짓을 돌려 줄 수는 없습니다. 그것은'completion (nil)'과'return'이어야합니다. 두 번째로 getCurrentWeather 완료 메소드에 오류 매개 변수를 추가해야하고, 언 래핑이 실패하면 'guard let data = data'가 완료되어야하며 단순히 반환하는 대신 전달 (nil, error)해야합니다. –

0

비동기 기능의 작동 원리를 근본적으로 오해합니다. 함수는 URLSession'sdataTask이 실행되기 전에 반환됩니다. 네트워크 요청을 완료하는 데 몇 초가 걸릴 수 있습니다. 데이터를 가져오고, 데이터를 다운로드 한 후 실행하는 코드 블록을 제공 한 다음 비즈니스를 계속 진행할 것을 요청합니다.

새 데이터가로드되기 전에 dataTask의 resume() 호출 이후의 행이 실행될 수 있습니다.

데이터 태스크의 완료 블록 내에서 데이터를 사용할 수있을 때 실행할 코드를 넣어야합니다. (귀하의 명세서 print(currentWeatherUnwrapped)은 데이터가 성공적으로 읽혀지면 실행됩니다.)

+0

당신 말이 맞아요. 오해하고, 저를 위해 분명히 해 주셔서 감사합니다. – Caleb

+0

위의 Sandeep의 대답은 방법을 변경하는 방법을 보여줍니다. –

1

이와 같이 비동기식 호출을 수행하면 dataTask가 반환 할 값을 갖기 오래 전에 함수가 반환됩니다. 당신이해야 할 일은 함수에서 완성 핸들러를 사용하는 것입니다. 이 같은 매개 변수로에 전달할 수 있습니다

func getCurrentWeather(completion: @escaping(CurrentWeather?, Error?) -> Void) { 
    //Data task and such here 
    guard error != nil else { 
     completion(nil, error!) 
     return 
    } 
    //Decode your current weather, etc. 
    completion(currentWeatherUnwrapped, nil) 
} 

그런 다음 그 함수를 호출하면 다음과 같습니다

getCurrentWeather(completion: { (weather, error) in 
    guard error == nil else { 
     print(error!.localizedDescription) 
     return 
    } 
    //Do something with your weather result 
    print(weather!) 
}) 
+0

오류 풀기를 강요 할 필요가 없습니다. (오류는 이미 선택 사항으로 선언되어 있지 않습니다.)'completion (nil, error)'. 당신의 완료에 사용할 현재 날씨의 포장을 풀 필요가 없습니다. 이 대답을 살펴보면 내가 무엇을 의미하는지 이해하게 될 것입니다. https://stackoverflow.com/a/47245036/2303865 –

-1

오류 처리를 다루는 모델을 사용할 수 있습니다 그것이 언제 완료 될지 당신은 모른다.

하나의 옵션은 래퍼 함수 getCurrentWeather을 반환 값을 제공하지 않고 비동기로 수정하는 것이지만 대신 콜백/종료를 수정하는 것입니다. 그렇다면 당신은 비동기 성질을 다루어야 할 것입니다.

당신은 아마 당신의 시나리오에서 원하는 것입니다 다른 옵션은 data tasksynchronous과 같이하는 것입니다 :

func getCurrentWeather() -> Bool { 
    let jsonUrlString = "https://api.wunderground.com/api/KEY/conditions/q/\(state)/\(city).json" 

    guard let url = URL(string: jsonUrlString) else { return false } 

    let dispatchGroup = DispatchGroup() // <=== 
    dispatchGroup.enter() // <=== 

    URLSession.shared.dataTask(with: url) { (data, response, err) in 
     // check error/response 

     guard let data = data else { 
      dispatchGroup.leave() // <=== 
      return 
     } 

     do { 
      let weather = try JSONDecoder().decode(CurrentWeather.self, from: data) 
      currentWeather = weather 
      if let currentWeatherUnwrapped = currentWeather { 
       print(currentWeatherUnwrapped) 
      } 
      dispatchGroup.leave() // <=== 
     } catch let jsonErr { 
      print("Error serializing JSON: ", jsonErr) 
      dispatchGroup.leave() // <=== 
     } 
     // cannot update currentWeather here, as weather is local to do block 

    }.resume() 

    dispatchGroup.wait() // <=== 

    return currentWeather != nil 
} 

wait 함수는 타임 아웃을 정의 할 수있는 매개 변수를 취할 수 있습니다. https://developer.apple.com/documentation/dispatch/dispatchgroup 그렇지 않으면 앱이 영원히 기다릴 수 없습니다. 그런 다음 사용자에게 표시 할 작업을 정의 할 수 있습니다.

Btw 학습용으로 완벽한 기능을 갖춘 날씨 앱을 만들었으니 여기 GitHub https://github.com/erikmartens/NearbyWeather에서 확인하십시오. 희망이 코드는 귀하의 프로젝트에 도움이 될 수 있습니다. 앱 스토어에서도 사용할 수 있습니다.

편집 :이 답변은 비동기 호출을 동기식으로 만드는 방법을 보여줍니다. 이것이 네트워크 호출을 처리하는 좋은 방법이라고 말하는 것은 아닙니다. 이것은 일 때이 비동기 호출을 내부에서 사용하더라도 함수에서 반환 값을 가져야하는 경우의 해킹 솔루션입니다.

+1

기술적으로 유효한 동안 당신의 대답은 문제를 해결하기위한 ** 위험한 ** 방법입니다. URLSession의 비동기 함수를 사용하여 주 스레드를 차단하여 동기화합니다. 아니, 아니! 이러지 마. 원격 서버가 끊어지면 앱의 UI가 잠기고 앱이 응답을 받기 위해 시스템에서 종료 될 수 있습니다. 이것은 정말로 나쁜 생각입니다. –

+0

물론, DispatchGroups는 제대로 사용하면 강력한 도구입니다. 그러나 비동기 작업 중에 주 스레드를 차단하기 위해 이러한 스레드를 사용하면 ** 적절한 사용이 아닙니다 **. –

+0

물론 이것은 DispatchGroup의 기본 작업이 아닙니다.DispatchGroups는 UI를 업데이트하기 전에 여러 비동기 작업이 완료 될 때까지 기다리고 싶을 때 특히 유용합니다. 그리고 당신은 대개 메인 스레드를 차단하지 않기 위해 비동기 백그라운드 스레드에 디스패치합니다. 어느 시점에서 다른 답변과 마찬가지로 클로저를 사용해야합니다. 그러나 질문자가 비동기식 함수에서 반환을 요청 했으므로 전달했습니다. 그러나 사실이 아닌 것은 서버 문제의 경우에 끝없는 대기입니다. 예를 들어 wait()의 기간을 제한하는 옵션이 있습니다. with wait (timeout :) – erikmartens