5 분 소요

🤝 [4주차]

드디어 대망의 4주차 API 완성도 코 앞으로 다가왔고

API연결과 이오가 개발한 애니메이션만 적용하면 드디어 앱 개발이 끝난다!

…라고 생각했으나 문제가 정말로 많은 주였다.

자꾸만 HTTP request error 가 날 뿐더러, 계속해서 서버가 다운됐다(aws 프리)

독구 말로는 서버팀에서 로그한 기록까지 전부 용량으로 들어가서(aws 프리가 애초에 너무 용량이 부족했다)

서버가 다운된다고 했다. 로그 전부 기록 안되게 지우고, 디비 교체 작업도 끝냈다(이건 독구 친구분꺼를 빌리고 있던 거여서 교체했다)

드디어 두근두근한 마음으로 내 작업(앱 핵심 기능 작업[목표 추가, 삭제, 수정, 기록 추가, 삭제, 수정, 목표 리스트, 기록 리스트 작업])과 피치의 작업물(회원가입,로그인,유저 정보 수정, 이하 프로필과 관련된 모든 기능)

을 합쳤는데, 당연하게도 충돌이 발생했다.

이럴 줄 알았지..

수작업으로 충돌난 부분을 삭제했다(내 파트는 피치 부분을 삭제했고, 피치파트는 내 부분을 삭제했다-main에서 충돌이 많이 일어났는데 아마도 아이폰 기종을 다르게 선택해서 그런 모양이었다)

사실 수작업으로 진행했던거라 안될 줄 알았는데, 기적적으로 정상적으로 앱이 돌아갔다(이게 인생이지)

기존에 goal을 추가한 사용자 프로필 정보가 나타나지 않는 문제를 발견했다.

피치와 내 작업을 합치면서 찾아낸 문제였다.

이게 도대체 뭘까…뭘까하고 고민한지

약 3일째

독구가 드디어 문제를 찾아냈다!!

4-1

이 문제였다.

아아 앓던 사랑니가 빠진 기분이 이런 기분일까

하지만 곧바로 다시 문제가 발생했다.

기존 이미지가 해상도가 낮아져서 왜그러지 했는데,

나는 이미지 저장을 base64방식으로 이미지 데이터를 string으로 바꿔서 저장하고 있었다. 그런데 사진을 선택해서(imagepicker) 보여주는 버튼의 크기가 90이기 때문에, 그 사이즈에 맞춰서 넣어준 다음 서버에 보내는 방식이어서, 당연히 90짜리를 큰 UIImageView에서 보여주니까 해상도가 낮아질 수 밖에…라고 생각하며 자연스럽게 기본 이미지 크기 그대로 서버에 저장했더니, 데이터 양이 어마무시하게 나와서 앱이 다운됐다.

결국 우려했던 대로 이미지 크기가 문제가 됐고, 나는 multipart/form-data 형태로 이미지를 올리기로 결심했다. (이때가 오늘인 2023_2_5일이었다)

// MARK: - [URL Session Post 멀티 파트 사진 데이터 업로드]
       func requestPOST() {
           
           // MARK: [사진 파일 파라미터 이름 정의 실시]
           let file = "image"
           
           let userIdx = UserDefaults.standard.integer(forKey: "userIdx")
           let accessToken = UserDefaults.standard.string(forKey: "accessToken")
           
           
           // MARK: [전송할 데이터 파라미터 정의 실시]
           var reqestParam : Dictionary<String, Any> = [String : Any]()
           //reqestParam["userIdx"] = userIdx // 일반 파라미터
           reqestParam["\(file)"] = self.imageData // 사진 파일
           
           // MARK: [URL 지정 실시]
           let urlComponents = URLComponents(string: "https://www.pigmoney.xyz/goal/uploadGoalImage/\(goalIdx!)/\(userIdx)")
           print("\n\n\n여기가 마지막 보루다!!:", urlComponents as Any)
           // [boundary 설정 : 바운더리 라인 구분 필요 위함]
           let boundary = "Boundary-\(UUID().uuidString)" // 고유값 지정
           
           print("")
           print("====================================")
           print("[A_Image >> requestPOST() :: 바운더리 라인 구분 확인 실시]")
           print("boundary :: ", boundary)
           print("====================================")
           print("")
           
           
           // [http 통신 타입 및 헤더 지정 실시]
           var requestURL = URLRequest(url: (urlComponents?.url)!) // url 주소 지정
           requestURL.httpMethod = "POST" // POST 방식
           requestURL.setValue(accessToken!, forHTTPHeaderField: "X-ACCESS-TOKEN")
           requestURL.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") // 멀티 파트 타입
           print("header!!!",requestURL.allHTTPHeaderFields as Any)
           
           // [서버로 전송할 uploadData 데이터 형식 설정]
           var uploadData = Data()
           let boundaryPrefix = "--\(boundary)\r\n"
           
           
           
           // [멀티 파트 전송 파라미터 삽입 : 딕셔너리 for 문 수행]
           for (key, value) in reqestParam {
               if "\(key)" == "\(file)" { // MARK: [사진 파일 인 경우]
                   print("")
                   print("====================================")
                   print("[A_Image >> requestPOST() :: 멀티 파트 전송 파라미터 확인 실시]")
                   print("타입 :: ", "사진 파일")
                   print("key :: ", key)
                   print("value :: ", value)
                   print("====================================")
                   print("")
                   
                   uploadData.append(boundaryPrefix.data(using: .utf8)!)
                   uploadData.append("Content-Disposition: form-data; name=\"\(file)\"; filename=\"\(file)\"\r\n".data(using: .utf8)!) // [파라미터 key 지정]
                   uploadData.append("Content-Type: \("image/jpg")\r\n\r\n".data(using: .utf8)!) // [전체 이미지 타입 설정]
                   uploadData.append(value as! Data) // [사진 파일 삽입]
                   uploadData.append("\r\n".data(using: .utf8)!)
                   uploadData.append("--\(boundary)--".data(using: .utf8)!)
               }
               else { // MARK: [일반 파라미터인 경우]
                   print("")
                   print("====================================")
                   print("[A_Image >> requestPOST() :: 멀티 파트 전송 파라미터 확인 실시]")
                   print("타입 :: ", "일반 파라미터")
                   print("key :: ", key)
                   print("value :: ", value)
                   print("====================================")
                   print("")
                   
                   uploadData.append(boundaryPrefix.data(using: .utf8)!)
                   uploadData.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!) // [파라미터 key 지정]
                   uploadData.append("\(value)\r\n".data(using: .utf8)!) // [value 삽입]
               }
           }

           
           
           // [http 요쳥을 위한 URLSessionDataTask 생성]
           print("")
           print("====================================")
           print("[A_Image >> requestPOST() :: 사진 업로드 요청 실시]")
           print("url :: ", requestURL)
           print("uploadData :: ", uploadData)
           print("====================================")
           print("")
           
           // MARK: [URLSession uploadTask 수행 실시]
           let dataTask = URLSession(configuration: .default)
           dataTask.configuration.timeoutIntervalForRequest = TimeInterval(20)
           dataTask.configuration.timeoutIntervalForResource = TimeInterval(20)
           dataTask.uploadTask(with: requestURL, from: uploadData) { (data: Data?, response: URLResponse?, error: Error?) in

               // [error가 존재하면 종료]
               guard error == nil else {
                   print("")
                   print("====================================")
                   print("[A_Image >> requestPOST() :: 사진 업로드 요청 실패]")
                   print("fail : ", error?.localizedDescription ?? "")
                   print("====================================")
                   print("")
                   return
               }

               // [status 코드 체크 실시]
               let successsRange = 200..<300
               guard let statusCode = (response as? HTTPURLResponse)?.statusCode, successsRange.contains(statusCode)
               else {
                   print("")
                   print("====================================")
                   print("[A_Image >> requestPOST() :: 사진 업로드 요청 에러]")
                   print("error : ", (response as? HTTPURLResponse)?.statusCode ?? 0)
                   print("allHeaderFields : ", (response as? HTTPURLResponse)?.allHeaderFields ?? "")
                   print("msg : ", (response as? HTTPURLResponse)?.description ?? "")
                   print("====================================")
                   print("")
                   return
               }

               // [response 데이터 획득, json 형태로 변환]
               let resultCode = (response as? HTTPURLResponse)?.statusCode ?? 0
               let resultLen = data! // 데이터 길이
               do {
                   guard let jsonConvert = try JSONSerialization.jsonObject(with: data!) as? [String: Any] else {
                       print("")
                       print("====================================")
                       print("[A_Image >> requestPOST() :: 사진 업로드 요청 에러]")
                       print("error : ", "json 형식 데이터 convert 에러")
                       print("====================================")
                       print("")
                       return
                   }
                   guard let JsonResponse = try? JSONSerialization.data(withJSONObject: jsonConvert, options: .prettyPrinted) else {
                       print("")
                       print("====================================")
                       print("[A_Image >> requestPOST() :: 사진 업로드 요청 에러]")
                       print("error : ", "json 형식 데이터 변환 에러")
                       print("====================================")
                       print("")
                       return
                   }
                   guard let resultString = String(data: JsonResponse, encoding: .utf8) else {
                       print("")
                       print("====================================")
                       print("[A_Image >> requestPOST() :: 사진 업로드 요청 에러]")
                       print("error : ", "json 형식 데이터 >> String 변환 에러")
                       print("====================================")
                       print("")
                       return
                   }
                   print("")
                   print("====================================")
                   print("[A_Image >> requestPOST() :: 사진 업로드 요청 성공]")
                   print("allHeaderFields : ", (response as? HTTPURLResponse)?.allHeaderFields ?? "")
                   print("resultCode : ", resultCode)
                   print("resultLen : ", resultLen)
                   print("resultString : ", resultString)
                   print("====================================")
                   print("")
               } catch {
                   print("")
                   print("====================================")
                   print("[A_Image >> requestPOST() :: 사진 업로드 요청 에러]")
                   print("error : ", "Trying to convert JSON data to string")
                   print("====================================")
                   print("")
                   return
               }
           }.resume()
       }

그래도 기존에 있던 postGoal() 함수를 버리기엔 아까워서, 이미지 따로, 나머지 목표 관련 정보 따로 올리기로 했다.

위는 이미지를 서버에 올리기 위해, 이미지 데이터를 서버로 보내는 함수이다.

문제는 URLSession의 복잡한 쿼리였다. URLSession은 함수자체가 비동기로 작성되어있어서

postGoal() 함수에서 이미지를 제외한 목표 정보를 서버에 올리고 goalIdx를 받아오면, 그 goalIdx로 이미지를 올리는 절차였는데

이 postGoal() 안에 있던 URLSession이 비동기라 시간이 너무 많이 걸린다고 여겼는지 goalIdx를 받아오지 않은 채, 바로 이미지를 받아오는 함수 requestPost()로 이동하는 것이었다. (그러니까 이미지를 못받을 수 밖에…)

별의 별 방법을 전부 사용해보았다.

4-3 이런 식으로 while 문을 이용해 함수 자체를 나가지 못하게 만들어보았고( 이건 request time 이 초과되는 에러가 발생했다)

4-4 semaphore를 이용해서도 해보았다(이건 뭐가 문제인지 앱 자체가 멈춰버린다)

어쩔 수 없이 DispatchQeue를 이용한 delay를 사용해 앱 속도를 조금 늦추더라도 goalIdx를 받아오기로 했다.

다만 기다리는 시간이 조금 어색할 수 있으니 로딩 페이지를 만들어 뷰 위에 올렸다.

댓글남기기