틈틈히 적어보는 개발 일기

[iOS][Swift] 커스텀 클래스를 UserDefaults에 저장해보기, Singleton 패턴 적용하기 본문

📱 iOS, Swift

[iOS][Swift] 커스텀 클래스를 UserDefaults에 저장해보기, Singleton 패턴 적용하기

itllbegone 2021. 1. 5. 23:00

결론

원하는 Class에 NSObject, NSCoding을 채택 후 각각의 변수들에 decode, encode 메소드를 작성해줍니다.
class PTimer: NSObject, NSCoding {
    var time: Int = -1
    var name: String = "Timer"
    
    required init?(coder: NSCoder) {
        self.time = coder.decodeInteger(forKey: "time")
        // 문자열은 Object로 불러와 String으로 타입캐스팅 해준다
        self.name = coder.decodeObject(forKey: "name") as? String
    }
    
    func encode(with coder: NSCoder) {
        coder.encode(self.time, forKey: "time")
        coder.encode(self.name, forKey: "name")
    }
}
Timer 클래스를 NSKeyedArchiver, NSKeyedUnarchiver 를 통해 data로 변환을 거쳐 UserDefaults에 저장합니다.
do {
    // Timer 클래스를 data로 변환합니다
    let pushTimer = try NSKeyedArchiver.archivedData(withRootObject: self.timer, requiringSecureCoding: false)
    // UserDefaults에 저장합니다
    UserDefaults.standard.set(pushTimer, forKey: "pushTimer")
} catch {

}
// UserDefaults로 부터 data를 가져옵니다
let archivedData = UserDefaults.standard.object(forKey: "pushTimer")
do {
    // 가져온 data를 timer클래스로 변환합니다
    let timer = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(archivedData as! Data) as! PTimer
    self.timer = timer
} catch {

}

본론

기존 찰랑말랑 서비스에 작업이 덜 되었던 부분을 이번에 처리하기로 했다. 애를 먹었던 부분이 Timer 부분이었는데 푸시 수신을 받는 시점으로부터 24시간을 체크해야 하는 타이머였다. (여타 게임 등에서 특정시간 이후 아이템을 얻을 수 있는 그런 타이머를 생각하면 된다!)

결론적으로는 중구난방으로 작성된 코드들이 다시 읽기도 불편했을 뿐 더러 수정하기에도 불편하여 결국 타이머 구성 자체를 갈아 엎었다.

 

기존에 타이머 관련 정보를 모두 UserDefaults에 각각 저장하여 각각의 key값을 외우는 것도 문제였으며 파편화된 코드로 인해 가독성이 무척이나 떨어졌다.

그래서 수정안으로 선택한 것이 Singleton 이었다.

 

먼저 내가 사용하는 타이머는 특정 시간에 단 하나의 타이머만 작동 하며 그리고 앱의 실행 여부와 상관없이 Timer 의 시간이 흘러가야 한다.

그래서 Singleton 객체인 PushTimer 클래스를 생성 후 관련 메소드와 변수들을 작성하였다.

이 때, Timer의 정보(나는 타이머의 종료 시간과 몇가지의 필수적인 정보들을 저장해야 했다)가 앱 실행 여부와 상관이 없어야 하므로 PushTimer에 Timer 클래스를 담는 변수를 따로 지정해주었고 해당 클래스를 UserDefaults에 저장을 하기 위해 결론과 같은 방법을 사용한 것이다.

class PushTimer {
    /// timer 싱글톤 패턴
    static let shared = PushTimer()
    var timer = PTimer()
    var currentTime: Int {
        get {
            let date = Date()
            let formatter = DateFormatter()
            formatter.locale = Locale.current
            formatter.dateFormat = "HH:mm:ss"
            
            let calendar = Calendar.current
            let hour = calendar.component(.hour, from: date)
            let minutes = calendar.component(.minute, from: date)
            let seconds = calendar.component(.second, from: date)
            
            // 초 단위로 변환
            let currentTime = hour * 3600 + minutes * 60 + seconds
            return currentTime
        }
    }
    var leftTime: Int {
        get {
            let _leftTime = self.timer.expireTime - self.currentTime
            if _leftTime < 0 {
                self.terminateTimer()
            }
            return _leftTime
        }
    }

이로써 매 초 마다 시간을 갱신하여 보여줘야 하는 코드 들이 VC에 직접적으로 작성되어 있지도 않게 되었고 간단하게 불러와서 갱신만 하면 되었다! 

애초에 처음 코드를 작성할때 부터 시간에 쫒겨 급하게 쓰면서도 이건 아닌데... 싶었던게 이렇게 돌아올 줄은 상상도 못 했다. 이렇게 따로 모듈화 시키고 나니 VC에 작성되어 있는 코드가 훨씬 간결해지고 가독성도 좋아졌고 추후 유지보수에 있어서도 용이해 진 것 같다.

 

전체 코드는 모두 첨부를 하지 않아 듬성듬성 안 맞는 부분이 있을 수도 있으니 참고용으로만 보면 된다.

NEXTERS 에서 하나의 팀으로 시작했던 찰랑말랑이 어느새 내 개발 능력에 많은 도움이 됐을 뿐 더러 실질적인 운영 및 작업 플로우에 대해 많은걸 배울 수 있는 의미있는 서비스가 되었다. 그만큼 애정도 많고 관심도 많이 가는 만큼 언젠가 싹 다 리팩해서 깔끔하게 코드를 손 봐야 겠다.

Comments