본문 바로가기

학습 노트/iOS (2021)

212. Upload Task

Upload Task

Dropbox와 연동해 Upload를 Task를 구현해 본다.

Dropbox의 Developer app console에서 필요한 API Key를 얻을 수 있다.
파일을 쓰는데 문제가 없도록 write 권한을 활성화 한다.

Dropbox/upload

 

HTTP - Developers - Dropbox

Dropbox is a free service that lets you bring your photos, docs, and videos anywhere and share them easily. Never email yourself a file again!

www.dropbox.com

Dropbox의 upload url은 다음과 같다.

https://content.dropboxapi.com/2/files/upload

또한 필요한 HTTPHeader는 다음과 같다.

curl -X POST https://content.dropboxapi.com/2/files/upload \
--header "Authorization: Bearer <AccessToken>" \
--header "Dropbox-API-Arg: {\"autorename\":false,\"mode\":\"add\",\"mute\":false,\"path\":\"/Homework/math/Matrices.txt\",\"strict_conflict\":false}" \
--header "Content-Type: application/octet-stream" \
--data-binary @local_file.txt

이전에 사용한 POST보다 종류가 조금 늘어나고 복잡해 졌을 뿐이지 원리 자체는 동일하다.

Request 생성

   var dropboxUploadRequest: URLRequest {
      guard let apiUrl = URL(string: "https://content.dropboxapi.com/2/files/upload") else {
         fatalError("Invalid URL")
      }
      
      var request = URLRequest(url: apiUrl)
      request.httpMethod = "POST"
      request.setValue("Bearer \(dropBoxAccessToken)", forHTTPHeaderField: "Authorization")
      request.setValue("{\"autorename\":false,\"mode\":\"add\",\"mute\":false,\"path\":\"/girlpapico03.mp4\",\"strict_conflict\":false}", forHTTPHeaderField: "Dropbox-API-Arg")
      request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
      
      return request
   }

총 세 개의 필드를 추가해 전달해야한다.
각각 Authorization, Dropbox-API-Arg, Content-Type의 이름으로,
Authorization에는 자신의 API Token을 저장하고,
Content-Type에는 전송하는 데이터의 종류를 저장한다.

Dropbox-API-Arg는 Dropbox에서 요구하는 데이터 뭉치로,
경로, POST 요청의 종류, 이름 옵션, 알림 여부, 우선순위, 충돌 옵션, 해쉬 옵션을 포함한다.

Upload Task 생성

@IBAction func upload(_ sender: Any) {
  guard let resourceUrl = Bundle.main.url(forResource: "girlpapico03", withExtension: "mp4") else {
     fatalError("Invalid Resource")
  }

  guard let data = try? Data(contentsOf: resourceUrl) else {
     fatalError("Invalid Data")
  }
}

업로드 하기 위한 파일을 url로 지정하고, 이를 사용해 Data 인스턴스를 생성한다.

@IBAction func upload(_ sender: Any) {
  guard let resourceUrl = Bundle.main.url(forResource: "girlpapico03", withExtension: "mp4") else {
     fatalError("Invalid Resource")
  }

  guard let data = try? Data(contentsOf: resourceUrl) else {
     fatalError("Invalid Data")
  }

   let task = URLSession.shared.uploadTask(with: dropboxUploadRequest, from: data) { data, response, error in
       if let error = error {
           self.showErrorAlert(with: error.localizedDescription)
           print(error)
           return
       }

       guard let httpResponse = response as? HTTPURLResponse else {
           self.showErrorAlert(with: "Invalid Response")
           return
       }

       guard (200...299).contains(httpResponse.statusCode) else {
           self.showErrorAlert(with: "\(httpResponse.statusCode)")
           return
       }

       guard let data = data, let str = String(data: data, encoding: .utf8) else {
           fatalError("Invalid Data")
       }

       self.showInfoAlert(with: str)
   }

   task.resume()
}

Upload Task 메서드는 파라미터로 전송할 데이터를 받는다는 특징이 있다.
간단하게는 지금과 같이 Completion Handler를 사용할 수 있지만,
이 경우 '데이터가 전송 완료 된 다음 한 번'만 호출 되기 때문에 작업이 오래 걸리는 겨웅 진행 상태를 알 수 없다는 단점이 있다.

Upload Task의 상태 확인 하기

Task의 상태를 모니터링 하기 위해선 Completion Handler 방식이 아닌 Delegate 방식을 사용해야 한다.

Session 생성

var uploadTask: URLSessionUploadTask?

lazy var session: URLSession = { [weak self] in
    let config = URLSessionConfiguration.default
    let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
    return session
}()

Delegate 방식으로 Task 모니터링을 진행하기 위해선 Task 메서드를 사용하는 것이 아닌,
직접 정의하고 설정해 사용해야 할 필요가 있다.
Task를 저장할 변수를 선언하고, URLSession을 정의한다.
Task 생성에 필요한 Session은 Default Session Configuration을 사용한다.

@IBAction func uploadWithProgress(_ sender: Any) {
  guard let resourceUrl = Bundle.main.url(forResource: "girlpapico03", withExtension: "mp4") else {
     fatalError("Invalid Resource")
  }

  guard let data = try? Data(contentsOf: resourceUrl) else {
     fatalError("Invalid Data")
  }

  uploadProgressView.progress = 0.0

   uploadTask = session.uploadTask(with: dropboxUploadRequest, from: data)
   uploadTask?.resume()
}

동일하게 파일의 url을 사용해 Data 인스턴스를 생성하고, 이를 업로드 하는 Upload Task를 생성한다.
이전에 생성한 Session을 사용해 Upload Task를 생성하고 resume 메서드를 사용해 실행하도록 구현했다.

extension UploadTaskViewController: URLSessionTaskDelegate {
	func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
		let current = formatter.string(fromByteCount: totalBytesSent)
		let total = formatter.string(fromByteCount: totalBytesExpectedToSend)
		sizeLabel.text = "\(current)/\(total)"
		
		uploadProgressView.progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
	}
}

Task를 직접 생성했으므로 Delegate 방식을 사용할 수 있다.

urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)

 

Apple Developer Documentation

 

developer.apple.com

해당 메서드는 Upload Task가 데이터를 업로드 하는 동안 반복해서 호출된다.
세 번째 파라미터로 전송된 데이터의 크기가 전달되고,
네 번째 파라미터로 지금까지 전송한 크기가 전달된다.
다섯번째 파라미터레는 전송하는 파일의 총 크기가 전달된다.

해당 메서드로 전달되는 파라미터 만으로 로딩바의 역할을 할 수 있는 데이터를 만족하기 때문에,
이들을 적절히 이용하는 것으로 간단하게 구현할 수 있다.

extension UploadTaskViewController: URLSessionTaskDelegate {
	func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
		let current = formatter.string(fromByteCount: totalBytesSent)
		let total = formatter.string(fromByteCount: totalBytesExpectedToSend)
		sizeLabel.text = "\(current)/\(total)"
		
		uploadProgressView.progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
	}
    
	func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
		print(error ?? "Done")
	}
}

urlSession(_:task:didCompleteWithError:)

 

Apple Developer Documentation

 

developer.apple.com

해당 메서드는 Upload Task가 완료되거나 오류가 발생한 경우 호출된다.

override func viewWillDisappear(_ animated: Bool) {
  super.viewWillDisappear(animated)

   session.invalidateAndCancel()
}

 그리고 직접 정의한 Task는 반드시 직접 정리하는 것을 잊으면 안 된다.

 

결과

 

'학습 노트 > iOS (2021)' 카테고리의 다른 글

214. Background Download  (0) 2022.08.31
213. Download Task  (0) 2022.08.30
211. Post Request  (0) 2022.08.25
210. SessionConfiguration  (0) 2022.08.23
209. URL Session Delegate  (0) 2022.08.23