Skip to content

Commit 8d19005

Browse files
committed
[#69] S3 업로드 및 AI 처리 메모리 피크 최적화
1 parent 16d6f53 commit 8d19005

3 files changed

Lines changed: 54 additions & 33 deletions

File tree

Codive/Features/Closet/Data/Repositories/ClothAIRepositoryImpl.swift

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,34 +27,43 @@ final class ClothAIRepositoryImpl: ClothAIRepository {
2727
return Array(repeating: nil, count: images.count)
2828
}
2929

30-
// 2. S3 병렬 업로드 (개별 실패 허용)
31-
return await withTaskGroup(of: (Int, String?).self, returning: [String?].self) { group in
32-
for (index, (imageData, presignedInfo)) in zip(images, presignedInfos).enumerated() {
33-
group.addTask {
34-
do {
35-
let contentType = S3UploadHelpers.detectFormat(from: imageData).contentType
36-
try await self.apiService.uploadImageToS3(
37-
presignedUrl: presignedInfo.presignedUrl,
38-
imageData: imageData,
39-
contentMD5: presignedInfo.md5Hash,
40-
contentType: contentType
41-
)
42-
return (index, presignedInfo.finalUrl)
43-
} catch {
44-
#if DEBUG
45-
print("[ClothAI] S3 업로드 실패 (index: \(index)): \(error)")
46-
#endif
47-
return (index, nil)
30+
// 2. S3 업로드 (3장씩 청크 분할하여 동시 메모리 제한)
31+
let chunkSize = 3
32+
var results = Array<String?>(repeating: nil, count: images.count)
33+
let pairs = Array(zip(images, presignedInfos).enumerated())
34+
35+
for chunkStart in stride(from: 0, to: pairs.count, by: chunkSize) {
36+
let chunkEnd = min(chunkStart + chunkSize, pairs.count)
37+
let chunk = pairs[chunkStart..<chunkEnd]
38+
39+
await withTaskGroup(of: (Int, String?).self) { group in
40+
for (index, (imageData, presignedInfo)) in chunk {
41+
group.addTask {
42+
do {
43+
let contentType = S3UploadHelpers.detectFormat(from: imageData).contentType
44+
try await self.apiService.uploadImageToS3(
45+
presignedUrl: presignedInfo.presignedUrl,
46+
imageData: imageData,
47+
contentMD5: presignedInfo.md5Hash,
48+
contentType: contentType
49+
)
50+
return (index, presignedInfo.finalUrl)
51+
} catch {
52+
#if DEBUG
53+
print("[ClothAI] S3 업로드 실패 (index: \(index)): \(error)")
54+
#endif
55+
return (index, nil)
56+
}
4857
}
4958
}
59+
for await (index, url) in group {
60+
results[index] = url
61+
}
5062
}
51-
52-
var results = Array<String?>(repeating: nil, count: images.count)
53-
for await (index, url) in group {
54-
results[index] = url
55-
}
56-
return results
63+
// 이 청크의 TaskGroup 종료 → 캡처된 Data 해제 가능
5764
}
65+
66+
return results
5867
}
5968

6069
func extractClothInfo(clothImageUrls: [String]) async throws -> [ClothAIInfo] {

Codive/Features/Closet/Presentation/ViewModel/ClothAddViewModel.swift

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -271,18 +271,31 @@ final class ClothAddViewModel: ObservableObject, ClothAddViewModelInput, ClothAd
271271

272272
defer { self.isAIProcessing = false }
273273

274-
let imageDatas = selectedPhotos.compactMap { $0.croppedImage.jpegData(compressionQuality: 0.8) }
275-
guard !imageDatas.isEmpty else {
276-
return
277-
}
274+
let totalCount = selectedPhotos.count
275+
guard totalCount > 0 else { return }
278276

279-
let totalCount = imageDatas.count
280277
let chunkSize = 3
281278

282279
aiProcessingMessage = "AI가 옷을 분석하고 배경을 제거하고 있어요 (0/\(totalCount))"
283280

284-
// 1. S3 병렬 업로드 (개별 실패 허용)
285-
let uploadResults = await clothAIUseCase.uploadImages(images: imageDatas)
281+
// 1. S3 업로드 (3장씩 Data 변환 → 업로드 → 해제)
282+
var uploadResults: [String?] = []
283+
284+
for chunkStart in stride(from: 0, to: totalCount, by: chunkSize) {
285+
let chunkEnd = min(chunkStart + chunkSize, totalCount)
286+
let chunkPhotos = Array(selectedPhotos[chunkStart..<chunkEnd])
287+
288+
let chunkDatas = chunkPhotos.compactMap { $0.croppedImage.jpegData(compressionQuality: 0.8) }
289+
if chunkDatas.count != chunkPhotos.count {
290+
uploadResults.append(contentsOf: Array(repeating: nil, count: chunkPhotos.count))
291+
continue
292+
}
293+
294+
let chunkResults = await clothAIUseCase.uploadImages(images: chunkDatas)
295+
uploadResults.append(contentsOf: chunkResults)
296+
// chunkDatas 스코프 종료 → Data 해제
297+
}
298+
286299
let successUrls = uploadResults.compactMap { $0 }
287300

288301
guard !successUrls.isEmpty else {

Codive/Shared/Services/Network/S3UploadHelpers.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,8 @@ enum S3UploadHelpers {
105105
request.httpMethod = "PUT"
106106
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
107107
request.setValue(contentMD5, forHTTPHeaderField: "Content-MD5")
108-
request.httpBody = imageData
109108

110-
let (_, response) = try await URLSession.shared.data(for: request)
109+
let (_, response) = try await URLSession.shared.upload(for: request, from: imageData)
111110

112111
guard let httpResponse = response as? HTTPURLResponse else {
113112
throw S3UploadError.invalidResponse

0 commit comments

Comments
 (0)