iyOmSd/Title: Swift

[Swift] - MultiPart통신 (멀티파트 이미지업로드)

냄수 2020. 4. 11. 10:10
반응형

안녕하세요😁😁

 

body와 이미지를 같이통신을 하는 예제가 많이 없어서 정리해봤어요

이번 게시물은 통신을 하는데 이미지를 올릴 때...

어떻게 올리지?? 해본적이 있을거에요

그 방법에 대해서 알아볼거에요

 

 

 

이미지를 서버로 전송하는 방법에는 여러 방법이 있을거에요

 

제가아는 방법에는

 

1.

이미지를 baseString형으로 전환해서 String으로 전송하는 방법

(로그 찍어보시면... 엄청..길어요..)

이 방식을 사용한다면 문자열이 엄청 길어서 데이터가 크기때문에 좋지 않은것 같아요

 

2.

이미지를 Data로 전환해서 멀티파트로 전송하는 방법 

저는 이 방법을 이제부터 설명할거에요!!

 

정의를 보면

multipart/form-data는 파일 업로드가 있는 양식요소에 사용되는 enctype 속성의 값중 하나이고, multipart는 폼데이터가 여러 부분으로 나뉘어 서버로 전송되는 것을 의미

라고 하네요

 

간단하게는 ContentType이 multipart/form-data인 타입의 통신이고

데이터를 심어서 보낼수 있다 라고 생각하면 좋겠네요

 

 

제일 편리하게 사용 할 수 있는 예제는 Alamofire에서 지원해주는 MultiPart를 사용하는 거에요

먼저 코드를 확인해볼게요

Alamofire을 이용한 MultiPart

func requestIdentify(userName: String,
                         imgData: Data,
                         completion: @escaping (DataResponse<HttpStatusCode>) -> Void) {

        var urlComponent = URLComponents(string: BaseAPI.shared.getBaseString())
        urlComponent?.path = RequestURL.identify.getRequestURL
        let header: [String: String] = [
            "Content-Type": "multipart/form-data"
        ]
        let parameters = [
            "userName" : userName
        ]
        guard let url = urlComponent?.url else {
            return
        }

        Alamofire.upload(multipartFormData: { multipartFormData in
            for (key, value) in parameters {
                multipartFormData.append("\(value)".data(using: .utf8)!, withName: key, mimeType: "text/plain")
            }

            multipartFormData.append(imgData, withName: "img", fileName: "\(userName).jpg", mimeType: "image/jpg")

        }, to: url, method: .post, headers: header) { result in
            switch result {
            case .success(let upload, _, _):
                upload.responseJSON { response in
                    print(response)
                    guard let data = response.data else { return }
                    if let decodedData = try? JSONDecoder().decode(ResponseSimple<Int>.self, from: data) {
                        print(decodedData)
                        guard let httpStatusCode
                            = HttpStatusCode(rawValue: decodedData.statusCode) else {
                                completion(.failed(NSError(domain: "status error",
                                                           code: 0,
                                                           userInfo: nil)))
                                return
                        }
                        completion(.success(httpStatusCode))

                    } else {
                        completion(.failed(NSError(domain: "decode error",
                                                   code: 0,
                                                   userInfo: nil)))
                        return
                    }
                }
            case .failure(let err):
                completion(.failed(err))
            }
        }
    }

 

Alamofire를 써본적이 있다면 통신 방식은 비슷해요

여기서 중요한부분은 데이터를 보내는 부분이죠

// body 추가
for (key, value) in parameters {
	multipartFormData.append("\(value)".data(using: .utf8)!, withName: key, mimeType: "text/plain")
}

// 이미지 추가
multipartFormData.append(imgData, withName: "img", fileName: "\(userName).jpg", mimeType: "image/jpg")

 

각 속성을 설명하자면

withname key값
fileName 파일이름
mimeType png면 image/png, text면 text/plain 이런 표현이 있어요

 

코드를 구현해놓고 보니까 너무 간단해요.. 하핳...

처음에 접할때는 어떻게하지 되게 어려웠거든요...

 

 

 

다음으로

좀더 심화된 코드로 작성해볼거에요

URLSession을 이용한 MultiPart - Data (Swift)

아래의 코드와 동일하고 createBody 부분만 타입을 다르게 써서 다른 구현방식이에요

    func requestIdentify() {
        guard let sendData = imgObservable.value else {
            return
        }
        let boundary = generateBoundaryString()
        let body: [String: String] = ["userName": userName]
        let bodyData = createBody(parameters: body,
                                  boundary: boundary,
                                  data: sendData,
                                  mimeType: "image/jpg",
                                  filename: "identifyImage.jpg")

        service.requestIdentifys(boundary: boundary, bodyData: bodyData) { response in
            switch response {
            case .success(let statusCode):
                print(statusCode)
            case .failed(let err):
                print(err)
            }
        }
    }
    
    private func generateBoundaryString() -> String {
        return "Boundary-\(UUID().uuidString)"
    }
    
    private func createBody(parameters: [String: String],
                            boundary: String,
                            data: Data,
                            mimeType: String,
                            filename: String) -> Data {
        var body = Data()
        let imgDataKey = "img"
        let boundaryPrefix = "--\(boundary)\r\n"
        
        for (key, value) in parameters {
            body.append(boundaryPrefix.data(using: .utf8)!)
            body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
            body.append("\(value)\r\n".data(using: .utf8)!)
        }
        
        body.append(boundaryPrefix.data(using: .utf8)!)
        body.append("Content-Disposition: form-data; name=\"\(imgDataKey)\"; filename=\"\(filename)\"\r\n".data(using: .utf8)!)
        body.append("Content-Type: \(mimeType)\r\n\r\n".data(using: .utf8)!)
        body.append(data)
        body.append("\r\n".data(using: .utf8)!)
        body.append("--".appending(boundary.appending("--")).data(using: .utf8)!)
        
        return body as Data
    }

 

위의 코드와 동일하고 createBody 부분만 타입을 다르게 써서 다른 구현방식이에요

URLSession을 이용한 MultiPart - NSMutableData (Objective-C)

    func requestIdentify() {
        guard let sendData = imgObservable.value else {
            return
        }
        let boundary = generateBoundaryString()
        let body: [String: String] = ["userName": userName]
        let bodyData = createBody(parameters: body,
                                  boundary: boundary,
                                  data: sendData,
                                  mimeType: "image/jpg",
                                  filename: "identifyImage.jpg")

        service.requestIdentifys(boundary: boundary, bodyData: bodyData) { response in
            switch response {
            case .success(let statusCode):
                print(statusCode)
            case .failed(let err):
                print(err)
            }
        }
    }
    
    private func generateBoundaryString() -> String {
        return "Boundary-\(UUID().uuidString)"
    }
    // MARK: 멀티파트 이미지 데이터로 변환
    private func createBody(parameters: [String: String],
                            boundary: String,
                            data: Data,
                            mimeType: String,
                            filename: String) -> Data {
        let body = NSMutableData()
        let imgDataKey = "img"
        let boundaryPrefix = "--\(boundary)\r\n"
        
        for (key, value) in parameters {
            body.appendString(boundaryPrefix)
            body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
            body.appendString("\(value)\r\n")
        }
        
        body.appendString(boundaryPrefix)
        body.appendString("Content-Disposition: form-data; name=\"\(imgDataKey)\"; filename=\"\(filename)\"\r\n")
        body.appendString("Content-Type: \(mimeType)\r\n\r\n")
        body.append(data)
        body.appendString("\r\n")
        body.appendString("—".appending(boundary.appending("—")))
        
        return body as Data
    }
extension NSMutableData {
    func appendString(_ string: String) {
        let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false)
        append(data!)
    }
}

보자마자 뭔가 복잡해보이죠..

우선 모든 통신은 utf8 형식으로 변환 해줄거구요

 

큰 단위로 함수부터 설명할게요

requestIdentify()

boundary 와 body데이터를 이용해서 멀티파트의 data를 만들고 서비스요청을 하는 함수

 

createBody() 

boundary - 멀티파트 데이터의 시작과 끝을 알리는 문자열 (이 글에서는 UUID값을 사용)

 

body에 대한 정보를 멀티파트 데이터로 가공

ImgData를 멀티파트 데이터로 가공

 

이러한 정보들을 한번에 모아서 Data타입으로 반환해주는 함수

 

generateBoundaryString()

boundary구분 UUID문자열을 반환해주는 함수

 

 

여기까지 기본적인 설정코드들을 살펴 봤어요

이제 실제 통신에서 어떻게 쓰는지 볼게요

 

 

 

    func requestIdentifys(boundary: String,
                         bodyData: Data,
                         completion: @escaping (DataResponse<HttpStatusCode>) -> Void) {
        var urlComponent = URLComponents(string: BaseAPI.shared.getBaseString())
        urlComponent?.path = RequestURL.identify.getRequestURL
        let header: [String: String] = [
            "Content-Type": "multipart/form-data; boundary=\(boundary)"
        ]
        guard let url = urlComponent?.url,
            let request = requestMaker.makeRequest(url: url,
                                                   method: .post,
                                                   header: header,
                                                   body: bodyData) else {
                                                    return
                                                    
        }
        
        network.dispatch(request: request) { result in
            switch result {
            case .success(let data):
                
                if let decodedData = try? JSONDecoder().decode(ResponseSimple<String>.self,
                                                               from: data) {
                    print(decodedData)
                    guard let httpStatusCode = HttpStatusCode(rawValue: decodedData.statusCode) else {
                        return completion(.failed(NSError(domain: "status error",
                                                          code: 0,
                                                          userInfo: nil)))
                    }
                    completion(.success(httpStatusCode))
                } else {
                    completion(.failed(NSError(domain: "decode error",
                                               code: 0,
                                               userInfo: nil)))
                    return
                }
                
            case .failure(let error):
                completion(.failed(error))
                return
            }
        }
    }

내부적으로 통신코드가 처리되어서 기본적인 변수나 함수가 아닌게 많아요

 

하지만 통신하는 흐름만 보면되니까 설명은 스킵할게요!

통신을 할때 url을 넣어주고

header 잘 넣어주고

멀티파트 데이터로 반환받은 Data타입을 넣어주고

통신하면 끝이에요!!

 

통신코드는 별 복잡한게 없어보이네요...

 

이렇게 두가지를 해보면서 많은 삽질과 노력끝에...

멀티파트가 이제 뭔지좀 알거같아요 ㅎㅎ...

 

반응형

'iyOmSd > Title: Swift' 카테고리의 다른 글

[Swift] - Apple Login (2/2)  (0) 2020.04.14
[Swift] - Apple Login (1/2)  (0) 2020.04.14
[Swift] XML 파싱  (0) 2020.04.03
[Swift] Font Custom ( 원하는 글자만 폰트적용하기 )  (0) 2020.03.01
[Swift] APNS - Apple Push Notification  (1) 2020.02.28