프로젝트에서 Alamofire 사용했다가 Sparrow SAST 에 위험 경고가 많이 잡혀서 삭제하고 블러그 여기저기 조합해서 URLSession 으로 초기 버전을 만들었다.
import Foundation
enum NetworkManagerError: Error {
case error(errorString: String), invalidResponse(code: Int), invalidData
var description: String {
switch self {
case .error(errorString: let error):
return "\(error)"
case .invalidResponse(code: let code):
return "response error code : \(code)"
case .invalidData:
return "Invalied Data"
}
}
}
class NetworkManager: NSObject {
typealias Percentage = Double
typealias ProgressHandler = (Percentage) -> Void
typealias CompletionHandler = (Result<Void, Error>) -> Void
static let shard = NetworkManager()
private lazy var session = URLSession(configuration: .default,
delegate: self,
delegateQueue: .main)
private var progressHandlersByTaskID = [Int : ProgressHandler]()
private var completionHandlersByTaskID = [Int : CompletionHandler]()
override init() {
super.init()
}
private func generateStringData(_ params: Dictionary<String, Any>? ) -> Data? {
var paramString: String = ""
if let p = params {
for (key,value) in p {
if paramString.count == 0 {
paramString = "\(key)=\(value)"
}
else {
paramString = paramString + "&\(key)=\(value)"
}
}
}
return paramString.data(using: .utf8)
}
/// Codable Struct Model 이용해 데이터 형태로 받아 처리할 수 있음.
func requestDecodable<T:Decodable>(url: URL, method: HTTPMethod,_ params: Dictionary<String, Any>?, of type: T.Type = T.self, completion: @escaping (Result<T?, NetworkManagerError>) -> Void) {
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
if let parameter = generateStringData(params) {
request.httpBody = parameter
}
let task = session.dataTask(with: request, completionHandler: { data, response, error in
guard error == nil else {
completion(.failure(.error(errorString: error?.localizedDescription ?? "")))
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
completion(.failure(.invalidResponse(code: (response as? HTTPURLResponse)!.statusCode)))
return
}
guard let data = data else {
completion(.failure(.invalidData))
return
}
do {
let response = try JSONDecoder().decode(T.self, from: data)
completion(.success(response))
} catch let error {
completion(.failure(.error(errorString: error.localizedDescription )))
}
})
task.resume()
}
func request(url: URL, _ params: Dictionary<String, Any>?, completion: @escaping (Result<Dictionary<String, Any>?, NetworkManagerError>) -> Void) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
if let parameter = generateStringData(params) {
request.httpBody = parameter
}
let task = session.dataTask(with: request, completionHandler: { data, response, error in
guard error == nil else {
completion(.failure(.error(errorString: error?.localizedDescription ?? "")))
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
completion(.failure(.invalidResponse(code: (response as? HTTPURLResponse)!.statusCode)))
return
}
guard let data = data else {
completion(.failure(.invalidData))
return
}
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any] else {
return
}
completion(.success(json))
} catch let error {
completion(.failure(.error(errorString: error.localizedDescription )))
}
})
task.resume()
}
func downloadRequest(url: URL,
progressHandler: @escaping ProgressHandler,
completionHandler: @escaping CompletionHandler) {
var request = URLRequest(url: url)
request.addValue("", forHTTPHeaderField: "Accept-Encoding")
let task = session.downloadTask(with: request)
progressHandlersByTaskID[task.taskIdentifier] = progressHandler
completionHandlersByTaskID[task.taskIdentifier] = completionHandler
task.resume()
}
func uploadFile(at fileURL: URL,
to targetURL: URL,
progressHandler: @escaping ProgressHandler,
completionHandler: @escaping CompletionHandler) {
var request = URLRequest(
url: targetURL,
cachePolicy: .reloadIgnoringLocalCacheData
)
request.httpMethod = "POST"
let task = session.uploadTask(
with: request,
fromFile: fileURL,
completionHandler: { data, response, error in
// Validate response and call handler
if error != nil {
completionHandler(.failure(error!))
} else {
completionHandler(.success(()))
}
}
)
progressHandlersByTaskID[task.taskIdentifier] = progressHandler
task.resume()
}
}
// MARK: SessionDelegate
extension NetworkManager: URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate) {
completionHandler(.rejectProtectionSpace, nil)
}
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
completionHandler(.useCredential, credential)
}
}
}
// MARK: SessionDownloadDelegate
extension NetworkManager: URLSessionDownloadDelegate {
// 다운로드 성공
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
log(direction: .ETC, ofType: self, datas: "Finished Downloading to \(location)")
guard let sourcURL = downloadTask.originalRequest?.url else {return}
log(direction: .ETC, ofType: self, datas: "Source URL : \(sourcURL)")
let fileManager = FileManager.default
let documentURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let destinationURL = documentURL.appendingPathComponent(sourcURL.lastPathComponent)
log(direction: .ETC, ofType: self, datas: "Destination URL : " ,destinationURL)
try? fileManager.removeItem(at: destinationURL)
do{
try fileManager.copyItem(at: location, to: destinationURL)
let handler = completionHandlersByTaskID[downloadTask.taskIdentifier]
handler?(.success(()))
}
catch let error {
log(direction: .ETC, ofType: self, datas: "Could not copy file to disk: \(error.localizedDescription)")
let handler = completionHandlersByTaskID[downloadTask.taskIdentifier]
handler?(.failure(error))
}
}
// 다운로드 중 (프로그래스)
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64) {
guard let url = downloadTask.originalRequest?.url else {
return
}
let currentProgress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
let totalSize = ByteCountFormatter.string(fromByteCount: totalBytesExpectedToWrite, countStyle: .file)
let handler = progressHandlersByTaskID[downloadTask.taskIdentifier]
handler?(currentProgress)
}
// 다운로드 실패
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if error != nil {
let handler = completionHandlersByTaskID[task.taskIdentifier]
handler?(.failure(error!))
}
}
}
// MARK: Upload Session
extension NetworkManager: URLSessionTaskDelegate {
func urlSession(_ session: URLSession,
task: URLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64
) {
guard let url = task.originalRequest?.url else {
return
}
let currentProgress = Double(totalBytesSent) / Double(totalBytesExpectedToSend)
let totalSize = ByteCountFormatter.string(fromByteCount: totalBytesExpectedToSend, countStyle: .file)
let handler = progressHandlersByTaskID[task.taskIdentifier]
handler?(currentProgress)
}
}
[Post 데이터 전송]
앱스토어에서 앱 버전정보 가져오기 예제
let url = URL(string: "https://itunes.apple.com/kr/lookup?bundleId=kr.xxx.xxxxxxx")
NetworkManger.shard.request(url: url!, nil) { result in
switch result {
case .success(let object):
guard let result = (object?["results"] as? [Any])?.first as? [String: Any],
let version = result["version"] as? String,
let trackViewUrl = result["trackViewUrl"] as? String else {
return
}
DispatchQueue.main.async {
completion(version, trackViewUrl, nil)
}
case .failure(let error):
log(direction: .ERROR, ofType: self, datas: error.description)
}
}
[파일 다운로드]
if let url = URL(string: "https://file-examples-com.github.io/uploads/2017/10/file-example_PDF_1MB.pdf") {
NetworkManger.shard.downloadRequest(url: url, progressHandler: { percentage in
log(direction: .ETC, ofType: self, datas: "다운로드", percentage)
}, completionHandler: { result in
switch result {
case .success:
log(direction: .ETC, ofType: self, datas: "다운로드 완료")
case .failure(let error):
log(direction: .ETC, ofType: self, datas: "다운로드 실패", error.localizedDescription)
}
})
}
'Swift > 기타' 카테고리의 다른 글
[SWIFT]Gif 이미지를 이용한 로딩 UI (0) | 2023.07.19 |
---|---|
[SWIFT]다른모양으로 반반 나눠서 선 그리기(애니메이션 효과 포함) (0) | 2023.07.19 |
[SWIFT]Alamofire HTTPS TLS 에러 예외처리 (0) | 2023.07.04 |
[SWIFT]Filemanager 디렉토리 생성 및 파일 저장 (0) | 2023.06.30 |
[SWIFT]Gif 애니메이션 Loading UI (0) | 2023.06.26 |