포스트

Todoey (2)

UserDefault 사용하기

  • UserDefault란 일종의 로컬 DB라고 볼 수 있다.
  • 사용자 기본 설정과 같은 단일 데이터 값에 적합하다.
    • 대량의 유사한 데이터를 저장해야할때는 sqlite 같은 DB를 쓰는게 더 좋다.
  • [데이터, 키]로 데이터를 저장한다, plist파일에 저장된다.

사용하기 위해서 오브젝트를 만들어 준다.

let defaults = UserDefaults.standard

UserDefaults에 저장하기.

self.defaults.set(self.itemArray, forKey: "TodoListArray")

하지만 실행시켜도 현재는 의미가 없다.

userdefault에 저장이 되지만 쓸수가 없기 때문이다.

UserDefaults 경로 확인하기.

AppDelegate.swilf에서 didFinishLaunchingWithOptions 함수를 사용하여 경로를 확인할것이다.

print(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last! as String)

이렇게 작성해준다.

앱을 실행하자마자 위와같이 경로가 나온다.

파인더를 통해 해당 경로를 확인해보자.

이때 마지막에 Documents 그대로 가지말고 Library → Preferences에 가면 plist파일로 있다.

더블클릭해서 실행해보면 우리가 추가한 값이 그대로 나온다.

그렇다면 왜 테이블뷰에 보이지 않았을까?

viewDidLoad에 세팅하기.

` itemArray = defaults.array(forKey: “TodoListArray”)`

이렇게 우리가 만든 배열에 userdefaults배열을 담아주자.

옵셔널 바인딩을 해주어 더 안전하게 할수도 있다.

이때 다운캐스팅을할때, 해당 배열에 어떤 데이터 타입을 담는지 정확하게 알고 다운캐스팅을 하도록 하자.

1
2
3
4
5
6
7
override func viewDidLoad() {
        super.viewDidLoad()
            
        if let items = defaults.array(forKey: "TodoListArray") as? [String] {
            itemArray = items
        }
    }

값을 새로 추가해보면?

새로 추가된 내용이 보이는걸 알 수 있다.

UserDefaults는 여러 데이터 타입을 담을 수 있다.

하지만 단점은 어떤 값을 가져오려면, plist안에 있는 모든 내용을 불러와야하기때문에 효율적이지 못하다.

즉, 많은양의 데이터를 저쟝하면 시간이 많이 소요된다.

UserDefaults를 DB처럼 사용할 수는 있지만, DB는 아니다. 앱의 속도에 영향을 주기때문이다.

문제 확인.

화면을 벗어날만큼 배열에 많은 값들이 들어가면 해당 내용을 보기위해 스크롤을 하게되면

Cell이 재사용 되면서 이상하게 보이게 된다.

let cell = tableView.dequeueReusableCell(withIdentifier: "ToDoItemCell", for: indexPath)

즉 첫번째 셀이 롤업하고 더 이상 보이지 않을때 재사용 가능한 셀로 다시 돌아온다.

문제 해결은 뒤로 두고

우선 Model화 시켜보자.

Model화

1
2
3
4
5
6
7
8
class Item {
    
    var title : String = ""
    var done : Bool = false

}

var itemArray = [Item]()

Reload 메서드 추가하여 갱신되게 해주기.

1
2
3
4
5
6
7
8
9
10
11
12
13
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //print(itemArray[indexPath.row])
        
        if itemArray[indexPath.row].done == false {
            itemArray[indexPath.row].done = true
        } else {
            itemArray[indexPath.row].done = false
        }
        
        tableView.reloadData() //new
        
        tableView.deselectRow(at: indexPath, animated: true)
    }

계속 리로드 하게 해주었다

1
2
3
4
5
if itemArray[indexPath.row].done == false {
            itemArray[indexPath.row].done = true
        } else {
            itemArray[indexPath.row].done = false
        }

이코드를 한줄로 간결하게 해보겠다.

itemArray[indexPath.row].done = !itemArray[indexPath.row].done

추가 문제 해결하기

테스트겸 추가를 해보니 App Crash가 발생한다.

아까는 잘되던게 안되던 이유는 바로

배열의 데이터 타입을 바꿔주었기 때문이다

String → Item

userdefault의 한계가 나타났다.

그래서 UserDefaults는 아주 간단한 타입의 데이터를 쓸때만 사용하는것이 좋다.

우리가 만든 Item이라는 사용자 지정 유형이나, 사용자 지정 개체는 사용 할 수 없기 떄문이다.

이젠 다른 방식으로 바꿔야할 때가 되었다.

NSCoder를 사용한 데이터 인코딩

ViewDidLoad에 다음과 같이 적어준다.

1
let dataFilePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)

실행해보니 다음과 같이 경로가 출력이 된다.

아까와 같은 경로가 출력이 된다.

해당 경로에 우리가 쓸 plist파일을 만들자

let dataFilePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("Items.plist")

실행하면?

self.defaults.set(self.itemArray, forKey: "TodoListArray") 이젠 사용하지 않으니 지워주자.

해당위치에 이제는 encoder를 사용할것이다.

let encoder = PropertyListEncoder()

1
2
3
4
5
6
do {
                let data = try encoder.encode(self.itemArray)
                try data.write(to: self.dataFilePath!)
            } catch{
                    print("Error encoding itemn array, \(error)")
            }

이전에 JSON 디코딩 할때와 비슷하게 실패할수도 있는경우가 생기기에, do-catch문으로 작성한다.

그리고 위에 try가 2개인데, 아래는 데이터를 작성할때 실패할 경우가 있으므로 try로 작성한다.

그리고 Model 도 Encodable 프로토콜을 채택해준다.

class Item : Encodable

그리고 작동 테스트를 하면

잘된다.

userdefault와의 차이는 위의 사진을 참고하자.

하지만 아직 체크 항목에 대한 이슈는 해결되지 않았다.

저장하는 부분만 따로 함수로 만들어 준다.

1
2
3
4
5
6
7
8
9
10
func saveItems() {
        let encoder = PropertyListEncoder()
        
        do {
            let data = try encoder.encode(itemArray)
            try data.write(to: dataFilePath!)
        } catch{
                print("Error encoding itemn array, \(error)")
        }
    }

데이터를 담고있는 plist load하기 (디코딩)

함수를 하나 만들어준다.

이걸 사용할건데 url은 바로 우리가 이전에 설정해둔 경로로 한다.

역시나 실패할 경우를 대비해 try를 사용해주고 옵셔널 바인딩도 사용해준다.

Decoding을 할때는 데이터 타입을 명시해준다.

1
2
3
4
5
6
7
8
9
10
11
func loadItems () {
        if let data = try? Data(contentsOf: dataFilePath!) {
            let decoder = PropertyListDecoder()
            do {
                itemArray = try decoder.decode([Item].self, from: data)
            } catch {
                print("error")
            }
        }
    }

위와 같이 적어준다.

이때 [Item].self 처럼 뒤에 self를 붙여주자.

그리고 ViewDidload에 해당 함수를 호출하면?

작동이 잘 된다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.