UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Escaping closure captures 'inout' parameter

Forums > Swift

I'm trying to get a web page, parse it and return a value extracted from it. I'm getting this error. I don't really understand what I'm doing wrong. I am absolutely sure that the variable pensionAge will always be available and never go away, if that's relevant.

Any tips gratefully received.

Jeremy

func getPensionAge(dob: Date, pensionAge: inout String) {

let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let urlString = "https://www.gov.uk/state-pension-age/y/age/"+formatter.string(from: dob)
let reportFail = "Can’t get pension age."

pensionAge = "Looking for pension age…"

guard let url = URL(string: urlString) else { pensionAge = reportFail; return }

let rq = URLRequest(url: url)
URLSession.shared.dataTask(with: rq) { data, response, error in
    guard let data = data,
          let text = String(data: data, encoding: .utf8)
    else { DispatchQueue.main.async { pensionAge = reportFail }; return }

    let range = NSRange(location: 0, length: text.count)
    let re = try! NSRegularExpression(pattern: "State Pension age on *([0-9]* [A-Za-z]* [0-9]*)")
    let matches = re.matches(in: text, range: range)
    let nsText = text as NSString
    let results = matches.map { result in
        (0..<result.numberOfRanges).map {
            result.range(at: $0).location != NSNotFound ? nsText.substring(with: result.range(at: $0)) : ""
        }
    }

    if results.count>0 {
        formatter.dateFormat = "dd MMM yyyy"
        if let date = formatter.date(from: results[0][1]) {
            let diff = Calendar.current.dateComponents([.day], from: dob, to: date)
            let ageAtPension = (Double(diff.day ?? 0) / 365.25)
            formatter.dateFormat = "dd/MM/yyyy"
            let pensionDate = formatter.string(from: date)
            let str = "State pension date is \(pensionDate), age  \(formatNumber(ageAtPension))"
            DispatchQueue.main.async { pensionAge = str }
        }
    }
}.resume()

}

2      

I'm not sure why you're using an inout parameter here. You should probably be doing something like return a String value or a Result instead. I'm pretty sure inout params aren't a good fit for async code like this.

Perhaps something like this:

enum PensionError: Error {
    case malformedURL
    case badData
    case dateRegexFailed
    case invalidPensionDate
    case differenceError
}

func getPensionAge(dob: Date, completion: @escaping (Result<(String, Double), PensionError>) -> Void) {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd"
    let urlString = "https://www.gov.uk/state-pension-age/y/age/\(formatter.string(from: dob))"

    guard let url = URL(string: urlString) else {
        completion(.failure(.malformedURL))
        return
    }

    URLSession.shared.dataTask(with: url) { (data, response, error) in
        guard let data = data,
              let text = String(data: data, encoding: .utf8)  else {
            completion(.failure(.badData))
            return
        }

        guard let rng = text.range(of: #"State Pension age on *([0-9]* [A-Za-z]* [0-9]*)"#,
                                   options: .regularExpression) else {
            completion(.failure(.dateRegexFailed))
            return
        }

        let pensionDate = String(text[rng])
            .replacingOccurrences(of: "State Pension age on", with: "")
            .trimmingCharacters(in: .whitespacesAndNewlines)

        formatter.dateFormat = "dd MMM yyyy"
        guard let dt = formatter.date(from: pensionDate) else {
            completion(.failure(.invalidPensionDate))
            return
        }

        let diff = Calendar.current.dateComponents([.day], from: dob, to: dt)
        guard let daysDiff = diff.day else {
            completion(.failure(.differenceError))
            return
        }

        let pensionAge = Double(daysDiff) / 365.25

        completion(.success((pensionDate, pensionAge)))

    }.resume()
}

And then in the code that calls getPensionAge, you would respond accordingly depending on whether the Result was a success or a failure.

2      

I wasn't returning a value because, as I understood it, the function would return before the URL analysis had been done by the separate thread. Thanks for your code: very interesting.

Jeremy

2      

Yeah, sorry "return a String value or a Result" wasn't exactly the correct wording; I meant return in a completion handler like I show in that code.

2      

As it turns out, the simple approach was to take my version, stick it in a class coforming to ObservableObject and declare pensionAge to be @Published. But I have learned a bit by reading your suggestion, so thanks again.

Jeremy

2      

yeah,As it turns out, stick it in a class coforming to ObservableObject and declare pensionAge to be @Published.

lisahaley

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.