Things take time

[SWIFT] 이미지 파일 서버(php) 업로드 하기 본문

iOS (기능)

[SWIFT] 이미지 파일 서버(php) 업로드 하기

겸손할 겸 2017. 5. 17. 10:52

[업로드]


나 같은 경우, 스위프트3부터 시작한 케이스라 스위프트2나 Object-C같은 문법은 전혀 알지 못한다.

안드로이드는 그나마 계속 하다보니 조금은 더 편하고 익숙하지만.. 스위프트는 영 편하지 못하다.


그러다가 사진을 업로드해야하는 경우가 있어 알아보던 중, 모든 소스가 NS로 시작하는 prefix가 붙은 함수들에, 기존 문서들이라 3버전으로 된 것이 없었다.

물론 결론적으로는 NS만 빼고, 나오는 양식대로 하면 되긴 하는데, 초보자가 보기엔 처음 보는 것들이다.

안드로이드의 경우 포럼이나 문서들이 참 많은데 스위프트는 어째.. 최신 정보도 별로 없는 것 같고 영어 문서만 가득하다. Object-C를 했어야 하나..


어쨌든 오늘은 그 내용을 정리한다. 이전에 했던 카메라, 앨범을 키고 저장하는 것부터 시작한다.


안드로이드와의 차이점은 안드로이드는 해당 이미지의 경로를 전달해서 해당 경로 URI(모든 파일의 주소)값을 바탕으로 해당 위치를 객체화 시켜서 이를 inputStream으로 읽어 들이고, outputStream으로 바이트 단위로 쪼개서 보내는 방식을하는데 Swift에서는 그냥 UIImage타입 객체를 전달하면 바로 되는 방식이라 간편하다.


코드는 2가지다. 기존에 사용하는 NSMutableData라는 데이터를 사용하거나, 아님 NS가 없는 Data를 이용한 방식 (requestBody 부분)

    // 사진 찍은 후, 앨범에서 사진을 가져온 후 실행되는 함수
    @available(iOS 2.0, *)
    public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]){
        let mediaType = info[UIImagePickerControllerMediaType] as! NSString
        
        if mediaType.isEqual(to: kUTTypeImage as NSString as String) {
            captureImage = info[UIImagePickerControllerOriginalImage] as! UIImage
            
            if flagImageSave {
                UIImageWriteToSavedPhotosAlbum(captureImage, self, nil, nil)
            }
            
            imageView.image = captureImage
        }
        
        uploadImage(image :captureImage)
        self.dismiss(animated: true, completion: nil)
    }

기존에 했던 포스팅에 이어, 사진이 선택되었을 때 호출되는 함수의 밑에 uploadImage(image)라는 함수를 주목하면 된다. UIImage객체를 파라미터로 전달한다.

func uploadImage(image: UIImage){ let currentDate = Date() let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyyMMdd_HHmmss" let randomNumber = arc4random_uniform(1000) let fileName = dateFormatter.string(from: currentDate) + "_\(randomNumber)" + ".jpg" print(fileName) var request = URLRequest(url: URL(string: "http://android.okdongchang.kr/UploadFileIOS.php")!) request.httpMethod = "POST" // 파라미터 전달할 경우 사용하면 됨, php에서 $_POST로 받음 let params = [ "first" : "gyeom" ] let boundary = "Boundary-\(UUID().uuidString)" request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") request.httpBody = createBody(parameters: params, boundary: boundary, data: UIImageJPEGRepresentation(image, 0.7)!, mimeType: "image/jpg", filename: fileName) // URLSession.shared.uploadTask(withStreamedRequest: r).resume() URLSession.shared.uploadTask(with: request, from: request.httpBody, completionHandler: { (data, response, error) -> Void in print("Data : \(String(describing: data))") // echo는 데이터에서 온다 print("Data Echo : \(String(describing: String(data: data!, encoding: .utf8)))") print("Response : \(String(describing: response))") print(error ?? "no error") }).resume() } func createBody(parameters: [String: String], boundary: String, data: Data, mimeType: String, filename: String) -> Data { // let legacyBody = NSMutableData() var body = Data() let boundaryPrefix = "--\(boundary)\r\n" for (key, value) in parameters { //legacyBody.appendString(boundaryPrefix) //legacyBody.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n") //legacyBody.appendString("\(value)\r\n") 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)!) } /* legacyBody.appendString(boundaryPrefix) legacyBody.appendString("Content-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\n") legacyBody.appendString("Content-Type: \(mimeType)\r\n\r\n") legacyBody.append(data) legacyBody.appendString("\r\n") legacyBody.appendString("--".appending(boundary.appending("--"))) */ body.append(boundaryPrefix.data(using: .utf8)!) body.append("Content-Disposition: form-data; name=\"file\"; 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 legacyBody as Data return body } } extension NSMutableData { func appendString(_ string: String) { let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false) append(data!) } }

어려운 코드는 아니다. 간략히 설명하면, 파일 이름을 만들 때 날짜와 랜덤 변수를 통해 만들어준다. (이 부분은 업무용이라 사용자 마음대로 한다. test.jpg라고 바로 명명을 하고 테스트해도 된다)

그리고 php에서 $_POST로 받을 부분이 param이며 딕셔너리 중 key값이 php의 key값이다. 즉, 위의 코드로는 php에서는 $_POST['first']로 받으면 gyeom이란 값을 얻을 수 있다는 뜻이다.


주석처리된 부분은 기존에 찾은 소스였다. NSMutableData타입으로 http body를 만들어 넣는 방식이며, NSMutableData타입을 확장하여 appendingString이란 메소드로 호출하여 해당 파라미터 값을 넣는 것이다.


여기서 URLRequest에 들어가는 httpBody에 대한 설명은 하지 않는다. 실제 문법과 같은 개념이라 통신할때는 어떤 키워드에 어떤 양식으로 넣어야하고, 이런게 있기 때문에..

간단하게는 바운더리는 http통신시.. 보내는 양식에서 각 속성? 특징을 구분짓는 단위라고 생각하면 된다. 그래서 boundary라는 변수가 들어가는 부분을 보면 Content-Disposition 이후라던지.. 양식의 경우는 문법으로 봐야하므로 그렇구나 하고 넘어간다. 마지막의 부분에는 --와 함께 boundary를 같이 붙여야 하며 \r\n은 각 줄을 구분짓는 문법이다. 이 부분은 multipart양식이라던지 키워드가 여러개 있으므로 파면 된다. 스위프트 문법은 아니므로..


NSURLSession이란 함수가 기존 레거시로 사용하던 함수인데, 이제는 URLSession을 사용해야한다. 그리고 URLSession.shared.dataTask라는 함수가 있고, URLSession.shared.uploadTask가 있다.


함수명에서 보이듯, 전자는 통신과 데이터를 받아올 때, 후자는 업로드를 위함이므로 후자를 사용한다. 전자의 경우 대표적인 사용예가 URL과 통신하여 해당 URL이 던져주는 JSON데이터를 받아와 처리하는 것이라 할 수 있겠다.


또한, php에서 찍는 echo를 보기 위해서는 completeHanlder의 data부분을 가져와야하며 이를 utf8로 인코딩 한다. (물론 php의 파일도utf8) Data Echo부분을 보면 된다.


php 코드는 아래와 같다.

$firstName = $_POST['first']; echo $firstName; $fileName = $_FILES['file']['name']; echo $fileName; $fileTmpName = $_FILES['file']['tmp_name']; echo $fileTmpName; $file_path = "newImage/" . $fileName; if(move_uploaded_file($_FILES['file']['tmp_name'], $file_path)) { echo $file_path; } else { echo "fail"; }

php소스는 현재 php파일이 있는 디렉토리에서 newImage라는 폴더 밑에 $fileName이란 이름으로 파일을 넣는다는 의미이며, 추가적으로 newImage와 같은 폴더가 없을 경우 생성하는 로직은 빠져있다.


또한, $_FILES['file']['name'], $_FILES['file']['tmp_name']은 php에서 사용하는  상수와 같은 개념이므로 정의해주지 않아도 접근할 수 있다. ['name']의 경우 Content-Disposition의 filename이랑 일치한다는 것을 알 수 있다. 이처럼 보이지 않는 규약이라 생각하면 된다.


이렇게 하고 실행해보면 디버깅 창에 echo가 차례차례 오는 것을 확인할 수 있다.