Skip to content

Commit b741cab

Browse files
committed
[Refactor] #192 - ImageUploadClient 에 있던 SwiftUI 의존성 제거 및 Data extension으로 구현했던 비즈니스 로직을 ImageUploadClient 내부 private 메서드로 이동
1 parent 0512dda commit b741cab

2 files changed

Lines changed: 76 additions & 30 deletions

File tree

Neki-iOS/Core/Sources/ImagePicker/Domain/ImageUploadClient.swift

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,12 @@
55
// Created by OneTen on 1/21/26.
66
//
77

8+
import Foundation
89
import ComposableArchitecture
9-
import SwiftUI
10-
import PhotosUI
1110

1211
public struct ImageUploadClient {
1312
public var upload: @Sendable (_ data: [ImageUploadEntity], _ mediaType: ImageMediaType) async throws -> [Int]
1413
public var uploadConcurrentlyFromURLs: @Sendable (_ fileURLs: [URL], _ mediaType: ImageMediaType) async throws -> [Int]
15-
public var convert: @Sendable (_ items: [PhotosPickerItem]) async -> [ImageUploadEntity]
1614
}
1715

1816
extension ImageUploadClient: DependencyKey {
@@ -50,32 +48,6 @@ extension ImageUploadClient: DependencyKey {
5048
uploadedMediaIDs.append(contentsOf: resultIDs)
5149
}
5250
return uploadedMediaIDs
53-
},
54-
55-
convert: { items in
56-
await withTaskGroup(of: ImageUploadEntity?.self) { group in
57-
for item in items {
58-
group.addTask {
59-
guard let data = try? await item.loadTransferable(type: Data.self) else { return nil }
60-
let dimensions = data.imageDimensions
61-
62-
return ImageUploadEntity(
63-
data: data,
64-
format: data.detectedImageFormat,
65-
width: dimensions?.width,
66-
height: dimensions?.height,
67-
size: data.count
68-
)
69-
}
70-
}
71-
72-
var results: [ImageUploadEntity] = []
73-
for await result in group {
74-
guard let result else { continue }
75-
results.append(result)
76-
}
77-
return results
78-
}
7951
}
8052
)
8153
}()

Neki-iOS/Core/Sources/ImagePicker/Presentation/ImagePickerFeature.swift

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import ComposableArchitecture
99
import SwiftUI
1010
import PhotosUI
11+
import ImageIO
1112

1213
@Reducer
1314
public struct ImagePickerFeature {
@@ -60,7 +61,7 @@ public struct ImagePickerFeature {
6061
state.isLoading = true
6162

6263
return .run { send in
63-
let entities = await imageUploadClient.convert(items)
64+
let entities = await Self.convert(items: items)
6465
await send(.imageConverted(entities))
6566
}
6667

@@ -101,7 +102,80 @@ public struct ImagePickerFeature {
101102

102103
default:
103104
return .none
105+
106+
}
107+
}
108+
}
109+
}
110+
111+
112+
// MARK: - ImagePickerFeature Private Func
113+
114+
private extension ImagePickerFeature {
115+
static func convert(items: [PhotosPickerItem]) async -> [ImageUploadEntity] {
116+
await withTaskGroup(of: ImageUploadEntity?.self) { group in
117+
for item in items {
118+
group.addTask {
119+
guard let data = try? await item.loadTransferable(type: Data.self) else { return nil }
120+
let dimensions = extractDimensions(from: data)
121+
let format = detectFormat(from: data)
122+
123+
return ImageUploadEntity(
124+
data: data,
125+
format: format,
126+
width: dimensions?.width,
127+
height: dimensions?.height,
128+
size: data.count
129+
)
130+
}
104131
}
132+
133+
var results: [ImageUploadEntity] = []
134+
for await result in group {
135+
guard let result else { continue }
136+
results.append(result)
137+
}
138+
return results
139+
}
140+
}
141+
142+
static func detectFormat(from data: Data) -> ImageFileFormat {
143+
// 데이터가 너무 짧으면 기본값(JPEG) 반환
144+
guard data.count > 12 else { return .jpeg }
145+
146+
let header = data.prefix(12)
147+
let firstByte = header[0]
148+
149+
// PNG 확인 (0x89로 시작)
150+
if firstByte == 0x89 {
151+
return .png
152+
}
153+
154+
// WebP 확인
155+
// WebP 파일 구조:
156+
// Offset 0-3: "RIFF" (0x52, 0x49, 0x46, 0x46)
157+
// Offset 8-11: "WEBP" (0x57, 0x45, 0x42, 0x50)
158+
if header[0] == 0x52 && header[1] == 0x49 && header[2] == 0x46 && header[3] == 0x46 && // "RIFF"
159+
header[8] == 0x57 && header[9] == 0x45 && header[10] == 0x42 && header[11] == 0x50 { // "WEBP"
160+
return .webp
161+
}
162+
163+
// 나머지는 JPEG로
164+
return .jpeg
165+
}
166+
167+
static func extractDimensions(from data: Data) -> (width: Int, height: Int)? {
168+
let options: [CFString: Any] = [kCGImageSourceShouldCache: false]
169+
170+
guard let source = CGImageSourceCreateWithData(data as CFData, options as CFDictionary),
171+
let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, options as CFDictionary) as? [CFString: Any] else {
172+
return nil
105173
}
174+
175+
var width = properties[kCGImagePropertyPixelWidth] as? Int
176+
var height = properties[kCGImagePropertyPixelHeight] as? Int
177+
178+
if let w = width, let h = height { return (w, h) }
179+
return nil
106180
}
107181
}

0 commit comments

Comments
 (0)