Szybkie żądanie GET z parametrami

81

Jestem bardzo nowy w szybkości, więc prawdopodobnie będę miał wiele błędów w moim kodzie, ale to, co próbuję osiągnąć, to wysłać GETżądanie do serwera lokalnego z parametrami. Co więcej, staram się to osiągnąć, biorąc pod uwagę, że moja funkcja ma dwa parametry baseURL:string,params:NSDictionary. Nie jestem pewien, jak połączyć te dwa elementy w rzeczywistym żądaniu adresu URL? Oto, czego próbowałem do tej pory

    func sendRequest(url:String,params:NSDictionary){
       let urls: NSURL! = NSURL(string:url)
       var request = NSMutableURLRequest(URL:urls)
       request.HTTPMethod = "GET"
       var data:NSData! =  NSKeyedArchiver.archivedDataWithRootObject(params)
       request.HTTPBody = data
       println(request)
       var session = NSURLSession.sharedSession()
       var task = session.dataTaskWithRequest(request, completionHandler:loadedData)
       task.resume()

    }

}

func loadedData(data:NSData!,response:NSURLResponse!,err:NSError!){
    if(err != nil){
        println(err?.description)
    }else{
        var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
        println(jsonResult)

    }

}
MrSSS16
źródło

Odpowiedzi:

160

Podczas budowania GETżądania nie ma treści, ale raczej wszystko trafia do adresu URL. Aby zbudować adres URL (i odpowiednio zmienić wartość procentową zmiany znaczenia), możesz również użyć URLComponents.

var url = URLComponents(string: "https://www.google.com/search/")!

url.queryItems = [
    URLQueryItem(name: "q", value: "War & Peace")
]

Jedyna sztuczka polega na tym, że większość usług internetowych wymaga +znaku procentowego ucieczki (ponieważ będą interpretować to jako znak spacji zgodnie ze application/x-www-form-urlencodedspecyfikacją ). Ale URLComponentsprocent nie ucieknie od tego. Firma Apple twierdzi, że +jest to prawidłowy znak w zapytaniu i dlatego nie należy go zmieniać. Technicznie są poprawne, że jest to dozwolone w zapytaniu o identyfikator URI, ale ma to specjalne znaczenie w application/x-www-form-urlencodedżądaniach i naprawdę nie powinno być przekazywane bez zmiany znaczenia.

Apple przyznaje, że musimy procentować unikaniem +znaków, ale radzi, abyśmy robili to ręcznie:

var url = URLComponents(string: "https://www.wolframalpha.com/input/")!

url.queryItems = [
    URLQueryItem(name: "i", value: "1+2")
]

url.percentEncodedQuery = url.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")

Jest to nieeleganckie obejście, ale działa i Apple radzi, jeśli twoje zapytania mogą zawierać +znak, a masz serwer, który interpretuje je jako spacje.

Łącząc to ze swoją sendRequestrutyną, otrzymujesz coś takiego:

func sendRequest(_ url: String, parameters: [String: String], completion: @escaping ([String: Any]?, Error?) -> Void) {
    var components = URLComponents(string: url)!
    components.queryItems = parameters.map { (key, value) in 
        URLQueryItem(name: key, value: value) 
    }
    components.percentEncodedQuery = components.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
    let request = URLRequest(url: components.url!)

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data,                            // is there data
            let response = response as? HTTPURLResponse,  // is there HTTP response
            (200 ..< 300) ~= response.statusCode,         // is statusCode 2XX
            error == nil else {                           // was there no error, otherwise ...
                completion(nil, error)
                return
        }

        let responseObject = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any]
        completion(responseObject, nil)
    }
    task.resume()
}

I nazwałbyś to tak:

sendRequest("someurl", parameters: ["foo": "bar"]) { responseObject, error in
    guard let responseObject = responseObject, error == nil else {
        print(error ?? "Unknown error")
        return
    }

    // use `responseObject` here
}

Osobiście użyłbym JSONDecoderteraz i zwróciłbym structraczej niestandardowy niż słownik, ale to nie jest tutaj naprawdę istotne. Mamy nadzieję, że ilustruje to podstawową koncepcję kodowania procentowego parametrów w adresie URL żądania GET.


Zobacz poprzednią wersję tej odpowiedzi dla wersji Swift 2 i ręcznych wersji ze ucieczką procentową.

Obrabować
źródło
Dzięki za fantastyczną odpowiedź. Mam tylko jedno pytanie, nadal jestem trochę zdezorientowany, co extension stringrobi z wartościami? Kiedy miałbym HttpBodywtedy użyć ?
MrSSS16
Rozszerzenie ciągu znaków to procent ucieczki wartości zgodnie z RFC 3986. Istnieją pewne znaki, które mają specjalne znaczenie w adresach URL (np. &Oddzielają jeden parametr od następnego, więc jeśli &występuje w wartości, nie można go puścić bez zmiany znaczenia). W związku z HTTPBodytym nie powinieneś używać go na GETżądanie; Jest używany, POSTale nie GET.
Rob
Wygląda to tak prosto, jakie są zalety używania czegoś takiego jak github.com/xyyc/SwiftSocket zamiast tego? Przepraszam, że jestem nowy w tym wszystkim.
jigzat
To może nie być właściwe porównanie, ponieważ jest to biblioteka gniazd, a to jest HTTP. Closer jest czymś w rodzaju Alamofire , ale jest (a) bardziej elastyczne (może obsługiwać żądania JSON, żądania x-www-form-urlencoded, wieloczęściowe itp.); b) bardziej funkcjonalny (np. obsługuje wyzwania związane z uwierzytelnianiem); i (c) pozwala uwolnić się od problemów związanych z programowaniem HTTP. Jeśli już, moja odpowiedź powyżej ma być przestrogą o ryzyku związanym z samym stworzeniem własnego NSMutableURLRequest, wskazując, że chodzi o coś więcej, niż sugeruje PO.
Rob
To piękne rozwiązanie. Dzięki @Rob. Przy okazji, czy jest jakaś różnica w dostarczaniu takich parametrów NSJSONSerialization.dataWithJSONObject(parameters, options: nil, error: nil)? parametersbędąc tablicą słowników[String: String]
Isuru
94

Użyj NSURLComponents, aby zbudować swój identyfikator NSURL w ten sposób

var urlComponents = NSURLComponents(string: "https://www.google.de/maps/")!

urlComponents.queryItems = [
  NSURLQueryItem(name: "q", value: String(51.500833)+","+String(-0.141944)),
  NSURLQueryItem(name: "z", value: String(6))
]
urlComponents.URL // returns https://www.google.de/maps/?q=51.500833,-0.141944&z=6

czcionka: https://www.ralfebert.de/snippets/ios/encoding-nsurl-get-parameters/

Ben-Hur Batista
źródło
5
Pomimo imponującej odpowiedzi Roba, twoja jest prostsza i działa.
Jan ATAC
3
To powinna być akceptowana odpowiedź. Do konstruowania adresów URL zaleca się używanie składników NSURLComponents wraz z elementami zapytania. Znacznie bezpieczniejsze i mniej podatne na błędy.
John Rogers
5

Używam tego, spróbuj na placu zabaw. Zdefiniuj podstawowe adresy URL jako Struct w stałych

struct Constants {

    struct APIDetails {
        static let APIScheme = "https"
        static let APIHost = "restcountries.eu"
        static let APIPath = "/rest/v1/alpha/"
    }
}

private func createURLFromParameters(parameters: [String:Any], pathparam: String?) -> URL {

    var components = URLComponents()
    components.scheme = Constants.APIDetails.APIScheme
    components.host   = Constants.APIDetails.APIHost
    components.path   = Constants.APIDetails.APIPath
    if let paramPath = pathparam {
        components.path = Constants.APIDetails.APIPath + "\(paramPath)"
    }
    if !parameters.isEmpty {
        components.queryItems = [URLQueryItem]()
        for (key, value) in parameters {
            let queryItem = URLQueryItem(name: key, value: "\(value)")
            components.queryItems!.append(queryItem)
        }
    }

    return components.url!
}

let url = createURLFromParameters(parameters: ["fullText" : "true"], pathparam: "IN")

//Result url= https://restcountries.eu/rest/v1/alpha/IN?fullText=true
anoop4real
źródło
1

Swift 3 :

extension URL {
    func getQueryItemValueForKey(key: String) -> String? {
        guard let components = NSURLComponents(url: self, resolvingAgainstBaseURL: false) else {
              return nil
        }

        guard let queryItems = components.queryItems else { return nil }
     return queryItems.filter {
                 $0.name.lowercased() == key.lowercased()
                 }.first?.value
    }
}

Użyłem go, aby uzyskać nazwę obrazu UIImagePickerControllerw func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]):

var originalFilename = ""
if let url = info[UIImagePickerControllerReferenceURL] as? URL, let imageIdentifier = url.getQueryItemValueForKey(key: "id") {
    originalFilename = imageIdentifier + ".png"
    print("file name : \(originalFilename)")
}
Danut Pralea
źródło
Twoja odpowiedź nie odpowiada na pytanie PO
Ilias Karim
0

Możesz rozszerzyć swoje, Dictionaryaby zapewnić tylko stringFromHttpParameterwtedy, gdy zarówno klucz, jak i wartość są zgodne z CustomStringConvertabletym

extension Dictionary where Key : CustomStringConvertible, Value : CustomStringConvertible {
  func stringFromHttpParameters() -> String {
    var parametersString = ""
    for (key, value) in self {
      parametersString += key.description + "=" + value.description + "&"
    }
    return parametersString
  }
}

jest to znacznie czystsze i zapobiega przypadkowym wywołaniom stringFromHttpParameterssłowników, które nie mają żadnego interesu w wywoływaniu tej metody

Reza Shirazian
źródło
-1

To rozszerzenie, które @Rob zasugerował, działa dla Swift 3.0.1

Nie byłem w stanie skompilować wersji, którą zamieścił w swoim poście, z Xcode 8.1 (8B62)

extension Dictionary {

    /// Build string representation of HTTP parameter dictionary of keys and objects
    ///
    /// :returns: String representation in the form of key1=value1&key2=value2 where the keys and values are percent escaped

    func stringFromHttpParameters() -> String {

        var parametersString = ""
        for (key, value) in self {
            if let key = key as? String,
               let value = value as? String {
                parametersString = parametersString + key + "=" + value + "&"
            }
        }
        parametersString = parametersString.substring(to: parametersString.index(before: parametersString.endIndex))
        return parametersString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
    }

}
etayluz
źródło
-3

Używam:

let dictionary = ["method":"login_user",
                  "cel":mobile.text!
                  "password":password.text!] as  Dictionary<String,String>

for (key, value) in dictionary {
    data=data+"&"+key+"="+value
    }

request.HTTPBody = data.dataUsingEncoding(NSUTF8StringEncoding);
Fabio Guerra
źródło