Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
742aeb8
[Feature] DiskCache 클래스 구현 (#109)
NeoSelf1 Feb 22, 2025
3cf7d37
[Delete] 불필요 라이브러리 import문 제거 (#109)
NeoSelf1 Feb 22, 2025
cab7e9d
[Refactor] 동시성 제어 관련 컴파일 에러 해결 (#109)
NeoSelf1 Feb 22, 2025
2d105ab
[Refactor] Actor 어노테이션 불필요한 곳 수정 (#109)
NeoSelf1 Feb 22, 2025
722a706
[Feat] ImageProcessor 추가 (#109)
NeoSelf1 Feb 24, 2025
5e82583
[Feat] ImageWrapper 추가 (#109)
NeoSelf1 Feb 24, 2025
daddb31
[Feat] ImageDownloader 구현 (#109)
NeoSelf1 Feb 24, 2025
852c52c
[Fix] Task-isolated options 관련 에러 수정 위해 임시 커밋 (#109)
NeoSelf1 Feb 24, 2025
152b503
[Fix] Concurrency 문제 해결 (#109)
NeoSelf1 Feb 24, 2025
c1006f6
[Fix] NeoImageWrapper 자체를 Sendable하게 변경하여 해결 (#109)
NeoSelf1 Feb 24, 2025
ee1273c
[Refactor] ImageCache 패키지 구현 (#109)
NeoSelf1 Feb 24, 2025
f636f62
[Refactor] 디렉토리 구조 변경 (#109)
NeoSelf1 Feb 26, 2025
0942e0b
[Feat] 메모리 및 디스크 저장소 조회하여 기존 이미지 가져오는 로직 추가 (#109)
NeoSelf1 Feb 26, 2025
7e72a5c
[Refactor] BookMatchKit loggers 초기과 구문 임시 저장
NeoSelf1 Mar 2, 2025
b2922a3
[Feat] LogCategory, LogSubsystem, LogLevel 열거형 정의
NeoSelf1 Mar 2, 2025
f56862b
[Feat] LogKitActor 및 LogKit 클래스 구현체 구현
NeoSelf1 Mar 2, 2025
a50887a
[Feat] LogKit Facade 계층 구현
NeoSelf1 Mar 2, 2025
72831f4
[Refactor] CSV 파일 형식에 맞춰 다중라인 출력 String문 단일라인으로 수정
NeoSelf1 Mar 2, 2025
422e571
[Delete] 각종 모듈에서 사용되고 있던 Logger 일괄 제거 및 LogKit로 통합
NeoSelf1 Mar 2, 2025
f345304
[Refactor] 각 패키지에 대해 LogKit 패키지 의존성 추가
NeoSelf1 Mar 2, 2025
6d43051
[Refactor] Log 관련 메서드 모두 LogKit 패키지 메서드로 일괄 변경
NeoSelf1 Mar 2, 2025
610e367
[Refactor] DesignSystem 라이브러리 내부 Kingfisher 라이브러리 의존성 제거 및 LogKit 내부 …
NeoSelf1 Mar 2, 2025
4a9ef1f
[Feat] Swift 6 점진적 대응 위한 컴파일 버전 낮추기
ericKwon95 Mar 18, 2025
7d1c4bb
[Feat] FIFO 순서의 로깅 위한 시리얼큐 적용
ericKwon95 Mar 18, 2025
67f3355
[Refactor] 파일 쓰기, 로그 엔진, 로그 엔진 래퍼로 역할 및 파일 분리
ericKwon95 Mar 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions BookKitty/BookKitty.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
E5A6B99E2D5F54C300A2E06D /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = E5A6B99C2D5F54C300A2E06D /* PrivacyInfo.xcprivacy */; };
E5DE19642D62A7D3007D37E2 /* BookOCRKit in Frameworks */ = {isa = PBXBuildFile; productRef = E5DE19632D62A7D3007D37E2 /* BookOCRKit */; };
E5FC698C2D52414B002875FD /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = E5FC698B2D52414B002875FD /* SnapKit */; };
E5FFE2F02D6A3F4200A0F7CF /* NeoImage in Frameworks */ = {isa = PBXBuildFile; productRef = E5FFE2EF2D6A3F4200A0F7CF /* NeoImage */; };
E93048662D559553008E9467 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = E93048652D559553008E9467 /* RxCocoa */; };
E97DC3702D50C161009ADFEA /* DesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = E97DC36F2D50C161009ADFEA /* DesignSystem */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -87,6 +88,7 @@
606DA2402D4206F200C7FAA3 /* RxRelay in Frameworks */,
E97DC3702D50C161009ADFEA /* DesignSystem in Frameworks */,
4584C5AF2D685AB300173282 /* FirebaseAnalytics in Frameworks */,
E5FFE2F02D6A3F4200A0F7CF /* NeoImage in Frameworks */,
60A1CC0E2D54A2DB00091568 /* BookRecommendationKit in Frameworks */,
606DA2422D4206F200C7FAA3 /* RxSwift in Frameworks */,
4584C5B32D685AB300173282 /* FirebaseCrashlytics in Frameworks */,
Expand Down Expand Up @@ -170,6 +172,7 @@
4584C5AE2D685AB300173282 /* FirebaseAnalytics */,
4584C5B02D685AB300173282 /* FirebaseCore */,
4584C5B22D685AB300173282 /* FirebaseCrashlytics */,
E5FFE2EF2D6A3F4200A0F7CF /* NeoImage */,
);
productName = BookKitty;
productReference = 60551C652D40E6E800CFC16A /* BookKitty.app */;
Expand Down Expand Up @@ -346,7 +349,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUHZ238M29;
DEVELOPMENT_TEAM = H856SYKNM8;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BookKitty/App/Info.plist;
Expand Down Expand Up @@ -385,7 +388,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUHZ238M29;
DEVELOPMENT_TEAM = H856SYKNM8;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BookKitty/App/Info.plist;
Expand Down Expand Up @@ -704,6 +707,10 @@
package = E5FC698A2D52414B002875FD /* XCRemoteSwiftPackageReference "SnapKit" */;
productName = SnapKit;
};
E5FFE2EF2D6A3F4200A0F7CF /* NeoImage */ = {
isa = XCSwiftPackageProductDependency;
productName = NeoImage;
};
E93048652D559553008E9467 /* RxCocoa */ = {
isa = XCSwiftPackageProductDependency;
package = 606DA23C2D4206F200C7FAA3 /* XCRemoteSwiftPackageReference "RxSwift" */;
Expand Down
4 changes: 2 additions & 2 deletions BookKitty/BookKitty/DesignSystem/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ let package = Package(
.package(url: "https://github.com/SnapKit/SnapKit.git", from: "5.7.1"),
.package(url: "https://github.com/devxoul/Then.git", from: "3.0.0"),
.package(url: "https://github.com/airbnb/lottie-spm.git", from: "4.5.1"),
.package(url: "https://github.com/onevcat/Kingfisher.git", from: "8.2.0"),
.package(path: "../NeoImage"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
Expand All @@ -31,7 +31,7 @@ let package = Package(
.product(name: "SnapKit", package: "SnapKit"),
.product(name: "Then", package: "Then"),
.product(name: "Lottie", package: "lottie-spm"),
.product(name: "Kingfisher", package: "Kingfisher"),
.product(name: "NeoImage", package: "NeoImage"),
],
resources: [
.process("Resource/Fonts"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by 임성수 on 2/4/25.
//

import Kingfisher
import NeoImage
import SnapKit
import UIKit

Expand Down Expand Up @@ -95,14 +95,16 @@ extension FlexibleImageView {
return
}

kf.setImage(
let options = NeoImageOptions(
transition: .fade(0.2),
retryStrategy: .times(3)
)

neo.setImage(
with: url,
placeholder: UIImage(named: "DefaultBookImage", in: Bundle.module, compatibleWith: nil),
options: [
.transition(.fade(0.2)), // 부드러운 페이드 효과
.cacheOriginalImage, // 원본 이미지 캐싱
],
completionHandler: { [weak self] result in
options: options,
completion: { [weak self] result in
guard let self else {
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by 임성수 on 2/4/25.
//

import Kingfisher
import NeoImage
import SnapKit
import UIKit

Expand Down Expand Up @@ -110,14 +110,16 @@ extension HeightFixedImageView {
return
}

kf.setImage(
let options = NeoImageOptions(
transition: .fade(0.2),
retryStrategy: .times(3)
)

neo.setImage(
with: url,
placeholder: UIImage(named: "DefaultBookImage", in: Bundle.module, compatibleWith: nil),
options: [
.transition(.fade(0.2)), // 부드러운 페이드 효과
.cacheOriginalImage, // 원본 이미지 캐싱
],
completionHandler: { [weak self] result in
options: options,
completion: { [weak self] result in
guard let self else {
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by 임성수 on 2/4/25.
//

import NeoImage
import SnapKit
import UIKit

Expand Down Expand Up @@ -107,14 +108,16 @@ extension WidthFixedImageView {
return
}

kf.setImage(
let options = NeoImageOptions(
transition: .fade(0.2),
retryStrategy: .times(3)
)

neo.setImage(
with: url,
placeholder: UIImage(named: "DefaultBookImage", in: Bundle.module, compatibleWith: nil),
options: [
.transition(.fade(0.2)), // 부드러운 페이드 효과
.cacheOriginalImage, // 원본 이미지 캐싱
],
completionHandler: { [weak self] result in
options: options,
completion: { [weak self] result in
guard let self else {
return
}
Expand Down
162 changes: 162 additions & 0 deletions BookKitty/BookKitty/LogKit/Sources/LogKit/LogKitActor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import Foundation
import OSLog

struct LoggerKey: Hashable {
let subSystem: LogSubSystem
let category: LogCategory
}

/// 단순히 전역 액터 속성만 정의
@globalActor
public actor LogKitActor {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FileManager 자체가 thread-safe한 것으로 알고 있는데, actor로 한 번 더 감싸주신 이유가 궁금합니다~!

public static let shared = LogKitActor()
}

public final class _LogKit {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_ 를 쓴 이유가 궁금해요!

// MARK: - Static Properties
/// @globalActor를 사용해 shared 인스턴스에만 격리 도메인을 지정하는 것은 동시성 격리를 부분적으로 선택적으로 적용
/// 어떤 부분이 격리되어야 하고 어떤 부분이 일반 동기 코드로 실행되어도 되는지 더 세밀하게 제어
/// 실제로 모든 코드가 항상 격리될 필요는 없기 때문에 성능상 이점도 존재
/// 공유 자원에 대한 접근은 조정해야 하지만, 모든 기능이 액터 내부에 있을 필요는 없는 경우 적합
@LogKitActor
static let shared = _LogKit()

/// logger 인스턴스를 생성하는 로직을 초기화기 내부에서 호출 하는 것은 actor 내에 actor-isolated 메서드를 동기적으로(synchronous)
/// 호출하는 것입니다.
/// Actor의 초기화 과정에서는 actor의 격리 매커니즘이 완전히 설정되지 않았기 때문에 actor-isolated 메서드를 직접 호출할 수 없습니다.
/// `actor는 공유 가변 상태에 대한 안전한 접근을 보장하기 위한 동시성 타입`
/// 이를 위해 actor 내부 모든 메서드와 프로퍼티는 기본적으로 actor-isolated 되어있기에, actor 외부에선 await 키워드와 함께 호출되어야 하며,
/// 동시에 외부에서 호출되더라도 자동으로 직렬화됩니다.
///
/// 각 카테고리에 해당한느 로거 인스턴스를 Actor 속성이 아닌, 정적 속성으로 생성
/// static 프로퍼티 및 메서드는 인스턴스와 무관하게 타입 자체에 속하기에 actor의 격리 메커니즘 밖에 존재하여, actor-isolation 제약을 받지 않게
/// 됨.
private static let loggers: [LoggerKey: Logger] = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static 변수는 lazy하게 초기화되며 스레드 안전하지 않기 때문에, loggers를 여러 곳에서 동시적으로 호출하지는 않는지 확인이 필요해 보입니다~!

var map: [LoggerKey: Logger] = [:]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굉장히 사소한 부분이긴 한데요, map 대신 더 구체적인 변수명을 사용하면 좋지 않을까 생각이 들었습니다!


for subSystem in LogSubSystem.allCases {
for category in LogCategory.allCases {
let logger = Logger(subsystem: subSystem.rawValue, category: category.rawValue)
map[LoggerKey(subSystem: subSystem, category: category)] = logger
}
}

return map
}()

private let dateFormatter: DateFormatter
private let fileManager: FileManager

private let appStartTime: String
private var currentCSVFileID: Int = 1
private var currentCSVFileURL: URL?
private var currentCSVFileSize: UInt64 = 0
private let maxCSVFileSize: UInt64 = 6 * 1024 // 6KB size limit

// MARK: - Lifecycle

private init() {
dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"

fileManager = FileManager.default

let startTimeFormatter = DateFormatter()
startTimeFormatter.dateFormat = "yyyyMMdd_HHmm"
appStartTime = startTimeFormatter.string(from: Date())

let documentsPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]

createNewCSVFile()
}

// MARK: - Functions

// MARK: - Public Methods

func log(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public 선언이 안 되어 있는 듯 합니당

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인터널로 사용할 계획이셨군요~ 짱짱입니다!

_ level: LogLevel,
message: String,
subSystem: LogSubSystem = .app,
category: LogCategory = .general,
file: String = #file,
function: String = #function,
line: Int = #line
) async {
let fileName = (file as NSString).lastPathComponent

let logger = getLogger(subSystem: subSystem, category: category)
logger.log(level: level.osLogType, "[\(fileName):\(line)] \(function) - \(message)")

let timestamp = dateFormatter.string(from: Date())

// CSV 로그 추가
writeToCSVFile(
timestamp: timestamp,
level: level.rawValue,
fileName: fileName,
line: String(line),
function: function,
message: message,
subSystem: subSystem.rawValue,
category: category.rawValue
)
}
// MARK: - Private Methods

private func getLogger(subSystem: LogSubSystem, category: LogCategory) -> Logger {
let key = LoggerKey(subSystem: subSystem, category: category)

if let logger = _LogKit.loggers[key] {
return logger
} else {
return Logger(subsystem: subSystem.rawValue, category: category.rawValue)
}
}

private func createNewCSVFile() {
let documentsPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileName = "\(appStartTime)-\(currentCSVFileID).csv"
let fileURL = documentsPath.appendingPathComponent(fileName)

// CSV 헤더 생성
let headerRow = "Timestamp,Level,FileName,Line,Function,Message,SubSystem,Category\n"

do {
try headerRow.write(to: fileURL, atomically: true, encoding: .utf8)
currentCSVFileURL = fileURL
currentCSVFileSize = UInt64(headerRow.utf8.count)
print("새 CSV 로그 파일이 생성되었습니다: \(fileName)")
} catch {
print("CSV 로그 파일 생성 실패: \(error)")
}
}

private func writeToCSVFile(timestamp: String, level: String, fileName: String, line: String, function: String, message: String, subSystem: String, category: String) {
let escapedMessage = message.replacingOccurrences(of: "\"", with: "\"\"")
let escapedFunction = function.replacingOccurrences(of: "\"", with: "\"\"")

// CSV 행 생성
let csvRow = "\"\(timestamp)\",\"\(level)\",\"\(fileName)\",\"\(line)\",\"\(escapedFunction)\",\"\(escapedMessage)\",\"\(subSystem)\",\"\(category)\"\n"

guard let csvData = csvRow.data(using: .utf8) else { return }
let dataSize = UInt64(csvData.count)

// 현재 파일이 최대 크기를 초과하는지 확인
if currentCSVFileSize + dataSize > maxCSVFileSize {
currentCSVFileID += 1
createNewCSVFile()
}

// CSV 파일에 로그 추가
guard let fileURL = currentCSVFileURL else { return }

if let fileHandle = try? FileHandle(forWritingTo: fileURL) {
fileHandle.seekToEndOfFile()
fileHandle.write(csvData)
try? fileHandle.close()

currentCSVFileSize += dataSize
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions BookKitty/BookKitty/NeoImage/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "NeoImage",
platforms: [.iOS(.v16)],
products: [
.library(
name: "NeoImage",
targets: ["NeoImage"]
),
],
targets: [
.target(
name: "NeoImage"
),
.testTarget(
name: "NeoImageTests",
dependencies: ["NeoImage"]
),
]
)
Loading