본문 바로가기
스파르타코딩 클럽/팀프로젝트

팀프로젝트3 [날씨 앱 만들기(3) - UserDefaults 매니저 구현하기]

by UDDT 2025. 5. 25.

 

 UserDefaults 매니저가 필요하게 된 이유

 

     날씨 앱에서 유저가 입력한 검색 기록을 저장하고 있으면 UX적으로 좋을 것 같다는 내부 논의가 있었다.

   내부 논의를 토대로 필요한 데이터를 정리해보니 입력한 주소, 위도, 경도 데이터만 저장하면 구현이 가능할 것 같았다.

   비교적 단순한 데이터이므로, UserDefaults를 사용하여 구현하기로 합의했다.

struct UserLocationData: Codable {
    let address: String
    let lat: String
    let lon: String
}

 

   위 데이터 타입은 struct로 JSON Decoding, Encoding의 데이터 변환 과정이 필요하다

 

  https://uddt.tistory.com/279

 

Swift | 유저 디폴트(UserDefaults) 이해하기

유저 디폴트(UserDefaults) 전화번호부 앱을 만든다고 생각해보자 let이나 var로 선언한 배열 타입 안에 데이터를 append해서 저장할 수도 있겠지만, 이런 형태라면 앱을 껐다키면 앱을 실행하면서 저

uddt.tistory.com

 

    만약 UserDefaults Manager가 없다면, UserDefaults에 저장된 데이터를 사용하는 곳마다

   load, save하는 함수를 계속 써줘야 한다.

// 1번 VC에서 작성
    func loadKickBoardsFromUserDefaults() -> [KickBoard]? {
        guard let data = UserDefaults.standard.data(forKey: "kickBoardHistory") else { return nil }
        print(data)
        return try? JSONDecoder().decode([KickBoard].self, from: data)
    }
// 2번 VC에서 작성
    func loadCurrentUser() -> User? {
        guard let currentEmail = UserDefaults.standard.string(forKey: "currentUserEmail"),
              let data = UserDefaults.standard.data(forKey: "savedUsers"),
              let users = try? JSONDecoder().decode([User].self, from: data) else {
            return nil
        }
        return users.first(where: { $0.email == currentEmail })
    }
// 3번 VC에서도 작성
        if let savedData = defaults.data(forKey: "kickBoardHistory"),
           let decoded = try? JSONDecoder().decode([KickBoard].self, from: savedData) {
            savedKickboardData = decoded

            // 중복 이름 체크
            if savedKickboardData.contains(where: { $0.name == name }) {
                showAlert(title: "중복 경고", message: "이미 등록된 킥보드 입니다")
                return true
            }
        }

 

  이렇게 하면 decode하는 플로우는 똑같은데 계속 반복적으로 코드를 작성하게 된다

 

  이를 방지하기 위해 UserDefaults 매니저를 구현하여 재사용성을 높여보고자 한다. 

 UserDefaults Manager 구현하기

    먼저 UserDefaultsManager를 여러 곳에서 사용할 수 있도록 싱글톤 패턴을 적용해서 작성하고,

   메모리 누수를 방지하기 위해 init을 막아주었다

final class UserDefaultsManager {
    static let shared = UserDefaultsManager()
    private let defaults = UserDefaults.standard

    private init() {}
}

 

     필요한 데이터는 2가지 데이터다.

   1. 현재 위치가 저장된 유저의 데이터

   2. 기존 검색 기록이 저장되어 있는 유저의 데이터

 

     UserDefaults는 Key-Value Type의 쌍으로 데이터를 다루는데,

     Key를 하드코딩하게 되면 휴먼 에러의 위험이 있으므로 enum으로 userDefaultsKey를 분기해줬다

extension UserDefaultsManager {
    enum UserDefaultsKey: String {
        case currentLocation
        case locationHistory
    }
}

 

     이제 데이터의 CRUD를 작성하면 된다

   재사용이 가능하도록 Generic Type으로 작성해주면 된다.

 

      - Create (데이터 저장)

    /**
     key를 입력하고 value를 저장할 수 있는 메서드입니다
     - key: currentLocation(현재 위치), locationHistory(위치 전체 기록)
     - value: 제네릭 Type이므로 저장하는 타입에 따라 LocationHistory(history: [value]) 등으로 타입을 맞춰줘야 합니다.
     */
    func saveData<T: Encodable>(key: userDefaultsKey, value: T) {
        if let encodedData = try?JSONEncoder().encode(value) {
            defaults.set(encodedData, forKey: key.rawValue)
        }
    }

 

    데이터를 저장할 때는 Key를 가지고 Value를 저장해야하기 때문에,

   파라미터로 key와 value를 주입할 수 있게 작성했다.

 

     - Read

    /**
     key를 입력하고 value에 접근할 수 있는 메서드입니다
     - key: currentLocation(현재 위치), locationHistory(위치 전체 기록)
     */
    func getData<T: Decodable>(with key: userDefaultsKey) -> T? {
        if let data = defaults.data(forKey: key.rawValue) {
            if let decodedData = try?JSONDecoder().decode(T.self, from: data) {
                return decodedData
            }
        }
        return nil
    }

 

    데이터를 불러올 때는 Key를 가지고 Value에 접근할 수 있기 때문에

   파라미터에 Key만 입력해주고 Key에 해당하는 Value를 return하도록 작성했다

 

     - Update

    /**
       위치 데이터를 추가할 수 있는 메서드입니다.
     - 데이터는 배열형태로 저장되며, locationHistory key에 접근하여 확인할 수 있습니다.
     */
    func updateLocationHistory(with locationData: UserLocationData) {
        var userHistory = getData(with: .locationHistory) ?? LocationHistory(history: [])
            userHistory.history.insert(locationData, at: 0)
            saveData(key: .locationHistory, value: userHistory)
    }

 

    Update도 Create와 마찬가지로 저장할 때는 Key를 가지고 Value를 저장해야하기 때문에,

   파라미터로 key와 value를 주입할 수 있게 작성했다.

   입력된 데이터를 최신순으로 정렬하기 위해 append가 아닌 insert(_, at: 0)를 사용했다

 

   우리 앱에서 현재 위치를 불러와서 업데이트하는 기능은 없기 때문에, 

   Update는 Key를 고정해서 사용하도록 구현했다.

 

     - Delete

    /**
     인덱스에 해당하는 데이터를 삭제할 수 있는 메서드입니다.
     - 추후 테이블뷰에 적용할 때 선택한 indexPath.row의 값을 주입하여 삭제할 수 있습니다.
     */
    func removeData(index: Int) {
        var userHistory = getData(with: .locationHistory) ?? LocationHistory(history: [])
        userHistory.history.remove(at: index)
        saveData(key: .locationHistory, value: userHistory)
    }

    /**
     데이터를 삭제할 수 있는 메서드입니다.
     - 데이터는 배열형태로 저장되며, locationHistory key에 접근하여 확인할 수 있습니다.
     - 전체 삭제 구현 시에 사용할 수 있습니다.
     */
    func removeData(with key: userDefaultsKey) {
        defaults.removeObject(forKey: key.rawValue)
    }

 

 

   삭제는 2가지 방법으로 구현했다.

  Key의 Index 접근해서 데이터를 삭제하거나, Key에 접근해서 모든 데이터를 삭제하는 방법으로 구현했다.

 

최근댓글

최근글

skin by © 2024 ttuttak