Yêu cầu Swift GET với các tham số


81

Tôi là người mới làm quen với swift, vì vậy tôi có thể sẽ gặp nhiều lỗi trong mã của mình nhưng những gì tôi đang cố gắng đạt được là gửi một GETyêu cầu đến một máy chủ localhost với các tham số. Nhiều hơn nữa, vì vậy tôi đang cố gắng đạt được nó với hàm của tôi có hai tham số baseURL:string,params:NSDictionary. Tôi không chắc làm thế nào để kết hợp hai thứ đó vào URLRequest thực tế? Đây là những gì tôi đã thử cho đến nay

    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)

    }

}

Câu trả lời:


156

Khi xây dựng một GETyêu cầu, không có nội dung nào đối với yêu cầu mà thay vào đó, mọi thứ đều đi trên URL. Để tạo một URL (và phần trăm thoát nó một cách chính xác), bạn cũng có thể sử dụng URLComponents.

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

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

Mẹo duy nhất là hầu hết các dịch vụ web cần +phần trăm ký tự được thoát ra (bởi vì chúng sẽ diễn giải đó là một ký tự khoảng trắng theo quy định của application/x-www-form-urlencodedđặc tả ). Nhưng URLComponentssẽ không phần trăm thoát khỏi nó. Apple cho rằng đó +là một ký tự hợp lệ trong một truy vấn và do đó không nên thoát. Về mặt kỹ thuật, họ đúng, nó được cho phép trong một truy vấn của URI, nhưng nó có ý nghĩa đặc biệt trong application/x-www-form-urlencodedcác yêu cầu và thực sự không nên được chuyển đi mà không thoát.

Apple thừa nhận rằng chúng tôi phải thoát các +ký tự phần trăm , nhưng khuyên chúng tôi nên làm điều đó theo cách thủ công:

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")

Đây là một công việc không phù hợp, nhưng nó hoạt động và là những gì Apple khuyên nếu các truy vấn của bạn có thể bao gồm một +ký tự và bạn có một máy chủ giải thích chúng là khoảng trắng.

Vì vậy, kết hợp điều đó với sendRequestthói quen của bạn , bạn sẽ có một cái gì đó như:

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()
}

Và bạn sẽ gọi nó như sau:

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

    // use `responseObject` here
}

Cá nhân tôi, JSONDecoderngày nay tôi muốn sử dụng và trả về một tùy chỉnh structhơn là một từ điển, nhưng điều đó không thực sự phù hợp ở đây. Hy vọng rằng điều này minh họa ý tưởng cơ bản về cách mã hóa phần trăm các tham số thành URL của một yêu cầu GET.


Xem bản sửa đổi trước của câu trả lời này cho Swift 2 và các phiên bản thoát phần trăm thủ công.


Cảm ơn vì câu trả lời tuyệt vời. Tôi chỉ có một câu hỏi, tôi vẫn còn một chút bối rối là những gì extension stringđang làm với các giá trị? Ngoài ra khi nào tôi cần sử dụng HttpBodysau đó?
MrSSS16

Phần mở rộng chuỗi là phần trăm thoát các giá trị trên mỗi RFC 3986. Có một số ký tự nhất định có ý nghĩa đặc biệt trong URL (ví dụ: &tách một tham số với tham số tiếp theo, vì vậy nếu &xảy ra trong giá trị, bạn không thể bỏ qua nó bằng cách bỏ thoát). Về việc HTTPBody, bạn không nên sử dụng nó theo GETyêu cầu; Nó được sử dụng trong POSTnhưng không GET.
Rob

Điều này trông rất đơn giản, những lợi thế của việc sử dụng một cái gì đó như github.com/xyyc/SwiftSocket thay vì cái này? Tôi xin lỗi vì tôi chưa quen với tất cả những điều này.
jigzat

Đó có thể không phải là sự so sánh phù hợp, vì đó là thư viện ổ cắm và đây là HTTP. Closer là một cái gì đó giống như Alamofire , nhưng (a) linh hoạt hơn (có thể xử lý các yêu cầu JSON, các yêu cầu x-www-form-urlencoded, đa phần, v.v.); (b) chức năng hơn (ví dụ: xử lý các thách thức xác thực); và (c) giúp bạn thoát khỏi sự tồi tệ của lập trình HTTP. Nếu có bất cứ điều gì, câu trả lời của tôi ở trên là một câu chuyện cảnh báo về rủi ro khi chỉ tạo ra của riêng bạn NSMutableURLRequest, chỉ ra rằng có nhiều điều hơn OP đề xuất.
Rob

Đây là một giải pháp đẹp. Cảm ơn @Rob. Btw có sự khác biệt nào trong việc cung cấp các thông số như thế này NSJSONSerialization.dataWithJSONObject(parameters, options: nil, error: nil)không? parameterslà một loạt các từ điển[String: String]
Isuru

94

Sử dụng NSURLComponents để xây dựng NSURL của bạn như thế này

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

phông chữ: https://www.ralfebert.de/snippets/ios/encoding-nsurl-get-parameters/


5
Bất chấp câu trả lời ấn tượng của Rob, câu trả lời của bạn đơn giản hơn và hoạt động hiệu quả.
Tháng 1 ATAC

3
Đây phải là câu trả lời được chấp nhận. Bạn nên sử dụng NSURLComponents cùng với các mục truy vấn để tạo URL. An toàn hơn nhiều và ít bị lỗi hơn.
John Rogers

4

Tôi đang sử dụng cái này, hãy thử nó trong sân chơi. Xác định các url cơ sở là Cấu trúc trong Hằng số

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

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
    }
}

Tôi đã sử dụng nó để lấy tên hình ảnh UIImagePickerControllertrong 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)")
}

câu trả lời của bạn không trả lời câu hỏi OP
Ilias Karim

0

Bạn có thể mở rộng của mình Dictionaryđể chỉ cung cấp stringFromHttpParameternếu cả khóa và giá trị đều tuân theo CustomStringConvertablenhư thế này

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

điều này gọn gàng hơn nhiều và ngăn chặn các cuộc gọi tình cờ đến stringFromHttpParameterstừ điển không có doanh nghiệp nào gọi phương thức đó


-1

Tiện ích mở rộng mà @Rob đề xuất này hoạt động cho Swift 3.0.1

Tôi không thể biên dịch phiên bản mà anh ấy đưa vào bài đăng của mình với 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)!
    }

}

-2

Tôi sử dụng:

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);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.