LoginSignup
20
14

More than 5 years have passed since last update.

【Swift】multipart/form-dataを実装してみた

Last updated at Posted at 2015-05-04

備忘録です。

ライブラリを使えば大体対応されているものですが、 自前で書いたことがなかったのでやってみました。

力技な感じも否めませんが、問題なくできたのでとりあえず大丈夫かと。

来月当たりに自分がみたときにもっと改良できることを祈っています。

ソースコード

とりあえずソースコード載せます。

NSURLRequestを作成する部分だけに絞りますので、リクエストの送り方などは他でお願いします。

RequestData.swift
/*
送りたいバイナリデータを持つクラス
MIME-TYPEとfile nameとNSDataを保持します
*/

class RequestData {

    enum MimeType: String {
        case JPEG = "image/jpeg"
        case MP4 = "video/mp4"
    }

    var data: NSData
    var mimeType: MimeType
    var filename: String

    init(data: NSData, mimeType: MimeType, filename: String) {
        self.data = data
        self.mimeType = mimeType
        self.filename = filename
    }
}

API.swift
/*
NSURLRequestを作成するクラス
ここにAPIにRequestを送る処理も書いていました
*/
class API {

    enum Method: String {
        case GET = "GET"
        case POST = "POST"
    }

    class func baseURL() -> NSURL {
        return NSURL(string: "リクエストを送るURL")
    }

    class func URLRequest(#method: Method, path: String, parameters: [String:AnyObject]) -> NSURLRequest? {
        if let components = NSURLComponents(URL: baseURL(), resolvingAgainstBaseURL: true) {
            let request = NSMutableURLRequest()
            request.HTTPMethod = method.rawValue

            if method == .GET {
                components.query = API.Helper.stringFromObject(parameters, encoding: NSUTF8StringEncoding)
            } else if method == .POST {
                var contentType: String?
                var paramsData: NSData?
                if API.Helper.isMultiParams(parameters) {
                    let boundary = "POST-boundary-\(arc4random())-\(arc4random())"
                    paramsData = API.Helper.multiDataFromObject(parameters, boundary: boundary)
                    contentType = "multipart/form-data; boundary=\(boundary)"
                } else {
                    paramsData = API.Helper.dataFromObject(parameters, encoding: NSUTF8StringEncoding)
                    contentType = "application/x-www-form-urlencoded"
                }

                if let params = paramsData {
                    request.setValue(contentType, forHTTPHeaderField: "Content-Type")
                    request.setValue("\(params.length)", forHTTPHeaderField: "Content-Length")
                    request.HTTPBody = params
                }
            }

            components.path = (components.path ?? "").stringByAppendingPathComponent(path).stringByAppendingString("/")
            request.URL = components.URL
            // responseがjsonの場合
            request.setValue("application/json", forHTTPHeaderField: "Accept")
            return request
        }

        return nil
    }

    class Helper {

        class func isMultiParams(params: [String:AnyObject]) -> Bool {

            var isMultiParams = false

            for (_, value) in params {
                if value is RequestData {
                    isMultiParams = true
                    break
                }
            }

            return isMultiParams
        }

        class func dataFromObject(object: AnyObject, encoding: NSStringEncoding) -> NSData? {
            let string = stringFromObject(object, encoding: encoding)
            return string.dataUsingEncoding(encoding, allowLossyConversion: false)
        }

        class func stringFromObject(object: AnyObject, encoding: NSStringEncoding) -> String {
            var pairs = [String]()

            if let dictionary = object as? [String: AnyObject] {
                for (key, value) in dictionary {
                    let string = (value as? String) ?? "\(value)"
                    let pair = "\(key)=\(string.escape())"
                    pairs.append(pair)
                }
            }

            return join("&", pairs)
        }

        class func multiDataFromObject(object: [String:AnyObject], boundary: String) -> NSData? {
            var data = NSMutableData()

            let prefixString = "--\(boundary)\r\n"
            let prefixData = prefixString.dataUsingEncoding(NSUTF8StringEncoding)!

            let seperatorString = "\r\n"
            let seperatorData = seperatorString.dataUsingEncoding(NSUTF8StringEncoding)!

            for (key, value) in object {

                var valueData: NSData?
                var valueType: String?
                var filenameClause = ""

                if value is RequestData {
                    let requestData = value as! RequestData
                    if let data = requestData.getData() {
                        valueData = data
                        valueType = requestData.mimeType.rawValue
                        filenameClause = " filename=\"\(requestData.filename)\""
                    }
                } else {
                    let stringValue = "\(value)"
                    valueData = stringValue.dataUsingEncoding(NSUTF8StringEncoding)!
                }

                if valueData == nil {
                    continue
                }
                data.appendData(prefixData)
                let contentDispositionString = "Content-Disposition: form-data; name=\"\(key)\";\(filenameClause)\r\n"
                let contentDispositionData = contentDispositionString.dataUsingEncoding(NSUTF8StringEncoding)
                data.appendData(contentDispositionData!)
                if let type = valueType {
                    let contentTypeString = "Content-Type: \(type)\r\n"
                    let contentTypeData = contentTypeString.dataUsingEncoding(NSUTF8StringEncoding)
                    data.appendData(contentTypeData!)
                }
                data.appendData(seperatorData)
                data.appendData(valueData!)
                data.appendData(seperatorData)
            }

            let endingString = "--\(boundary)--\r\n"
            let endingData = endingString.dataUsingEncoding(NSUTF8StringEncoding)!
            data.appendData(endingData)

            return data
        }
    }
}

とまあこんな感じでできました。

使い方

Playgoround.swift
// UIImageをjpegにしてリクエストする
let image = UIImage(named: "hoge")
let jpegData = UIImageJPEGRepresentation(image, 1)
let requestData = RequestData(data: jpegData, mimeType: JPEG, "hoge.jpeg")
let parameters = ["title":"hogehoge", "text": "hoge", "post_image": requestData]
if let request = API.URLRequest(method: .POST, path: "/hogehoge", parameters: parameters) {
    // requestを送る処理
}

みたいになります。

やっていること

至極簡単です。

multipart/form-dataのことを調べてもらえればわかるかと思います。

for-inkeyvalueに分けて、クラス判別をして作業を分けています。

いろいろなライブラリのソースコードを参考にしましたが大体同じことをしていました。

最後に

クラス分け、enumの使い所など、参考になる点が多々あるのでライブラリを読むのって大事ですね。

以上です。

20
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
14