Skip to content

Commit

Permalink
[Refactor] 에러 핸들링 개선 (#35)
Browse files Browse the repository at this point in the history
* [refactor] NetworkKit 에러 추가 및 개선

* [refactor] MusicKit 에러 추가

* [refactor] Encoder Error 추가

* [refactor] Decoder 에러 추가

* [refactor] AudioKit Error 추가

* [refactor] Repository Error 추가

* [refactor] Presentation Layer Error 추가

* [Refactor] 재사용성 및 가독성을 고려하여 enum을 struct로 변환

* [Refactor] #file의 절대경로가 아닌, 에러의 원천 파일명만 출력하도록 수정
  • Loading branch information
around-forest authored Feb 2, 2025
1 parent b5bea3c commit f3694f2
Show file tree
Hide file tree
Showing 40 changed files with 534 additions and 282 deletions.
80 changes: 42 additions & 38 deletions alsongDalsong/ASAudioKit/ASAudioAnalyzer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,49 @@ import Foundation

public enum ASAudioAnalyzer {
public static func analyze(data: Data, samplesCount: Int) async throws -> [CGFloat] {
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".m4a")
try data.write(to: tempURL)
let file = try AVAudioFile(forReading: tempURL)

guard
let format = AVAudioFormat(
commonFormat: .pcmFormatFloat32,
sampleRate: file.fileFormat.sampleRate,
channels: file.fileFormat.channelCount,
interleaved: false
),
let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(file.length))
else {
return []
do {
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".m4a")
try data.write(to: tempURL)
let file = try AVAudioFile(forReading: tempURL)

guard
let format = AVAudioFormat(
commonFormat: .pcmFormatFloat32,
sampleRate: file.fileFormat.sampleRate,
channels: file.fileFormat.channelCount,
interleaved: false
),
let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(file.length))
else {
return []
}

try file.read(into: buffer)
guard let floatChannelData = buffer.floatChannelData else {
return []
}

let frameLength = Int(buffer.frameLength)
let samples = Array(UnsafeBufferPointer(start: floatChannelData[0], count: frameLength))
var result = [CGFloat]()
let chunkedSamples = samples.chunked(into: samples.count / samplesCount)

for chunk in chunkedSamples {
let squaredSum = chunk.reduce(0) { $0 + $1 * $1 }
let averagePower = squaredSum / Float(chunk.count)
let decibels = 10 * log10(max(averagePower, Float.ulpOfOne))

let newAmplitude = 1.8 * pow(10.0, decibels / 20.0)
let clampedAmplitude = min(max(CGFloat(newAmplitude), 0), 1)
result.append(clampedAmplitude)
}

try? FileManager.default.removeItem(at: tempURL)

return result
} catch {
throw ASAudioErrors(type: .analyze, reason: error.localizedDescription, file: #file, line: #line)
}

try file.read(into: buffer)
guard let floatChannelData = buffer.floatChannelData else {
return []
}

let frameLength = Int(buffer.frameLength)
let samples = Array(UnsafeBufferPointer(start: floatChannelData[0], count: frameLength))
var result = [CGFloat]()
let chunkedSamples = samples.chunked(into: samples.count / samplesCount)

for chunk in chunkedSamples {
let squaredSum = chunk.reduce(0) { $0 + $1 * $1 }
let averagePower = squaredSum / Float(chunk.count)
let decibels = 10 * log10(max(averagePower, Float.ulpOfOne))

let newAmplitude = 1.8 * pow(10.0, decibels / 20.0)
let clampedAmplitude = min(max(CGFloat(newAmplitude), 0), 1)
result.append(clampedAmplitude)
}

try? FileManager.default.removeItem(at: tempURL)

return result
}
}

Expand Down
19 changes: 19 additions & 0 deletions alsongDalsong/ASAudioKit/ASAudioErrors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Foundation

struct ASAudioErrors: LocalizedError {
let type: ErrorType
let reason: String
let file: String
let line: Int

enum ErrorType {
case analyze
case startPlaying, getDuration
case configureAudioSession
case startRecording
}

var errorDescription: String? {
return "[\(URL(fileURLWithPath: file).lastPathComponent):\(line)] \(type) 에러: \n\(reason)"
}
}
4 changes: 4 additions & 0 deletions alsongDalsong/ASAudioKit/ASAudioKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
4E8907C22CE2489A00D5B547 /* ASAudioKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E89071E2CE23D1B00D5B547 /* ASAudioKit.framework */; platformFilter = ios; };
4EB1ED372CE88E500012FFBA /* ASAudioKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E89071E2CE23D1B00D5B547 /* ASAudioKit.framework */; };
4EB1ED382CE88E500012FFBA /* ASAudioKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4E89071E2CE23D1B00D5B547 /* ASAudioKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8593659D2D3F77FE0086C8C4 /* ASAudioErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8593659C2D3F77F60086C8C4 /* ASAudioErrors.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -50,6 +51,7 @@
4E89071E2CE23D1B00D5B547 /* ASAudioKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ASAudioKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4E8907BE2CE2489A00D5B547 /* ASAudioKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ASAudioKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4EB1EC802CE7AA160012FFBA /* ASAudioDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ASAudioDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
8593659C2D3F77F60086C8C4 /* ASAudioErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASAudioErrors.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
Expand Down Expand Up @@ -123,6 +125,7 @@
isa = PBXGroup;
children = (
1B7EB01A2CFDA83B00B2BE2A /* ASAudioAnalyzer.swift */,
8593659C2D3F77F60086C8C4 /* ASAudioErrors.swift */,
4E8907AD2CE240D400D5B547 /* ASAudioKit */,
4E8907BF2CE2489A00D5B547 /* ASAudioKitTests */,
4EB1EC812CE7AA160012FFBA /* ASAudioDemo */,
Expand Down Expand Up @@ -303,6 +306,7 @@
buildActionMask = 2147483647;
files = (
1B7EB01C2CFDA83B00B2BE2A /* ASAudioAnalyzer.swift in Sources */,
8593659D2D3F77FE0086C8C4 /* ASAudioErrors.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,29 @@ public actor ASAudioPlayer: NSObject {
public var onPlaybackFinished: (@Sendable () async -> Void)?

/// 녹음파일을 재생하고 옵션에 따라 재생시간을 설정합니다.
public func startPlaying(data: Data, option: PlayType = .full) {
configureAudioSession()
public func startPlaying(data: Data, option: PlayType = .full) throws {
do {
try configureAudioSession()
audioPlayer = try AVAudioPlayer(data: data)
audioPlayer?.delegate = self
audioPlayer?.prepareToPlay()
audioPlayer?.isMeteringEnabled = true
} catch {
// TODO: 오디오 객체생성 실패 시 처리
throw ASAudioErrors(type: .startPlaying, reason: error.localizedDescription, file: #file, line: #line)
}

switch option {
case .full:
audioPlayer?.play()
case let .partial(time):
audioPlayer?.currentTime = 0
audioPlayer?.play()
DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(time)) {
Task {
await self.stopPlaying()
}
case .full:
audioPlayer?.play()
case let .partial(time):
audioPlayer?.currentTime = 0
audioPlayer?.play()
DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(time)) {
Task {
await self.stopPlaying()
}
}
}
}

Expand All @@ -51,12 +52,13 @@ public actor ASAudioPlayer: NSObject {
}

/// 녹음파일의 총 녹음시간을 리턴합니다.
public func getDuration(data: Data) -> TimeInterval {
public func getDuration(data: Data) throws -> TimeInterval {
if audioPlayer == nil {
do {
audioPlayer = try AVAudioPlayer(data: data)
} catch {
// TODO: 오디오 객체생성 실패 시 처리
throw ASAudioErrors(type: .getDuration, reason: error.localizedDescription, file: #file, line: #line)
}
}
return audioPlayer?.duration ?? 0
Expand All @@ -66,13 +68,14 @@ public actor ASAudioPlayer: NSObject {
onPlaybackFinished = handler
}

private func configureAudioSession() {
private func configureAudioSession() throws {
do {
let session = AVAudioSession.sharedInstance()
try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker])
try session.setActive(true, options: .notifyOthersOnDeactivation)
} catch {
// TODO: 세션 설정 실패에 따른 처리
throw ASAudioErrors(type: .configureAudioSession, reason: error.localizedDescription, file: #file, line: #line)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,34 @@ public actor ASAudioRecorder {

public init() {}
/// 녹음 후 저장될 파일의 위치를 지정하여 녹음합니다.
public func startRecording(url: URL) {
configureAudioSession()
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
]

public func startRecording(url: URL) throws {
do {
try configureAudioSession()
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
]
audioRecorder = try AVAudioRecorder(url: url, settings: settings)
audioRecorder?.prepareToRecord()
audioRecorder?.isMeteringEnabled = true
audioRecorder?.record()
} catch {
// TODO: AVAudioRecorder 객체 생성 실패 시에 대한 처리
throw ASAudioErrors(type: .startRecording, reason: error.localizedDescription, file: #file, line: #line)
}
}

/// 오디오 세션을 설정합니다.
private func configureAudioSession() {
private func configureAudioSession() throws {
do {
let session = AVAudioSession.sharedInstance()
try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker])
try session.setActive(true, options: .notifyOthersOnDeactivation)
} catch {
// TODO: 세션 설정 실패에 따른 처리
throw ASAudioErrors(type: .configureAudioSession, reason: error.localizedDescription, file: #file, line: #line)
}
}

Expand Down
4 changes: 4 additions & 0 deletions alsongDalsong/ASDecoder/ASDecoder.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
1B06307A2CE64E7C005300BF /* ASDecoder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B702E612CE5193F00124F73 /* ASDecoder.framework */; };
1B06307B2CE64E7C005300BF /* ASDecoder.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1B702E612CE5193F00124F73 /* ASDecoder.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1B702E6A2CE5193F00124F73 /* ASDecoder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B702E612CE5193F00124F73 /* ASDecoder.framework */; };
8593659A2D3F71E80086C8C4 /* ASDecoderErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 859365982D3F71E40086C8C4 /* ASDecoderErrors.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -47,6 +48,7 @@
1B06306B2CE64E74005300BF /* ASDecoderDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ASDecoderDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
1B702E612CE5193F00124F73 /* ASDecoder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ASDecoder.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1B702E692CE5193F00124F73 /* ASDecoderTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ASDecoderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
859365982D3F71E40086C8C4 /* ASDecoderErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASDecoderErrors.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
Expand Down Expand Up @@ -104,6 +106,7 @@
1B702E572CE5193F00124F73 = {
isa = PBXGroup;
children = (
859365982D3F71E40086C8C4 /* ASDecoderErrors.swift */,
1B702E632CE5193F00124F73 /* ASDecoder */,
1B702E6D2CE5193F00124F73 /* ASDecoderTests */,
1B06306C2CE64E74005300BF /* ASDecoderDemo */,
Expand Down Expand Up @@ -285,6 +288,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8593659A2D3F71E80086C8C4 /* ASDecoderErrors.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
12 changes: 8 additions & 4 deletions alsongDalsong/ASDecoder/ASDecoder/ASDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import Foundation

public enum ASDecoder {
public static func decode<T: Decodable>(_: T.Type, from data: Data) throws -> T {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601

return try decoder.decode(T.self, from: data)
return try decoder.decode(T.self, from: data)
} catch {
throw ASDecoderErrors(type: .decode, reason: error.localizedDescription, file: #file, line: #line)
}
}

public static func handleResponse<T: Decodable>(result: Result<Data, Error>) async throws -> T {
Expand Down
16 changes: 16 additions & 0 deletions alsongDalsong/ASDecoder/ASDecoderErrors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation

struct ASDecoderErrors: LocalizedError {
let type: ErrorType
let reason: String
let file: String
let line: Int

enum ErrorType {
case decode
}

var errorDescription: String? {
return "[\(URL(fileURLWithPath: file).lastPathComponent):\(line)] \(type) 에러: \n\(reason)"
}
}
4 changes: 4 additions & 0 deletions alsongDalsong/ASEncoder/ASEncoder.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
1B1327F02CE5F26400EF706D /* ASEncoder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B702E2F2CE5152600124F73 /* ASEncoder.framework */; };
1B1327F12CE5F26400EF706D /* ASEncoder.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1B702E2F2CE5152600124F73 /* ASEncoder.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1B702E382CE5152600124F73 /* ASEncoder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B702E2F2CE5152600124F73 /* ASEncoder.framework */; };
859365972D3F71390086C8C4 /* ASEncoderErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 859365952D3F71310086C8C4 /* ASEncoderErrors.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -47,6 +48,7 @@
1B702E2F2CE5152600124F73 /* ASEncoder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ASEncoder.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1B702E372CE5152600124F73 /* ASEncoderTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ASEncoderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
1BDDABBD2CE5E2E000147881 /* ASEncoderDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ASEncoderDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
859365952D3F71310086C8C4 /* ASEncoderErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASEncoderErrors.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
Expand Down Expand Up @@ -104,6 +106,7 @@
1B702E252CE5152600124F73 = {
isa = PBXGroup;
children = (
859365952D3F71310086C8C4 /* ASEncoderErrors.swift */,
1B702E312CE5152600124F73 /* ASEncoder */,
1B702E3B2CE5152600124F73 /* ASEncoderTests */,
1BDDABBE2CE5E2E000147881 /* ASEncoderDemo */,
Expand Down Expand Up @@ -278,6 +281,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
859365972D3F71390086C8C4 /* ASEncoderErrors.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
14 changes: 9 additions & 5 deletions alsongDalsong/ASEncoder/ASEncoder/ASEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import Foundation

public enum ASEncoder {
public static func encode<T: Encodable>(_ value: T) throws -> Data {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
encoder.keyEncodingStrategy = .useDefaultKeys
encoder.outputFormatting = [.prettyPrinted]
do {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
encoder.keyEncodingStrategy = .useDefaultKeys
encoder.outputFormatting = [.prettyPrinted]

return try encoder.encode(value)
return try encoder.encode(value)
} catch {
throw ASEncoderErrors(type: .encode, reason: error.localizedDescription, file: #file, line: #line)
}
}
}
16 changes: 16 additions & 0 deletions alsongDalsong/ASEncoder/ASEncoderErrors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation

struct ASEncoderErrors: LocalizedError {
let type: ErrorType
let reason: String
let file: String
let line: Int

enum ErrorType {
case encode
}

var errorDescription: String? {
return "[\(URL(fileURLWithPath: file).lastPathComponent):\(line)] \(type) 에러: \n\(reason)"
}
}
Loading

0 comments on commit f3694f2

Please sign in to comment.