티스토리 뷰

반응형

App과 AppExtension (Today Extension, Widget Extension, Siri Extension 등..)은 UserDefaults를 공유하지 않습니다.

 

그 이유를 살펴볼게요

App Extension Programming Guide 를 보면 App과 App Extension의 관계가 나옵니다.

Extension은 App안에 있지만 두개는 각각 다른 container를 가지고 있습니다. 

그래서 기본적으로 둘 사이의 데이터는 공유되지 않습니다. 

그래서 UserDefaults는 공유되지 않습니다. 

 

 

 

하지만 위의 그림처럼 shared container를 사용해서 UserDefaults를 공유할 수 있어요

 

[1] App Group 설정

 

Capability에 AppGroup를 추가하고 App과 Extension에 같은 App Group Identifier를 등록해주세요

(App Group Identifier는 bundle identifier 처럼 유니크한 값이에요)

 

 

 

[2] UserDefaults 공유 설정

 

UserDefaults.standard 말고

아래와 같은 extension을 만들고 UserDefaults.shared를 써주세요

appGroupId를 suitName으로 설정한 UserDefaults를 쓰겠다고 구현해준 것입니다. 

extension UserDefaults {
    static var shared: UserDefaults {
        let appGroupId = "group.com.eunjin.githubprviewer"
        return UserDefaults(suiteName: appGroupId)!
    }
}

 

이제 App 또는 App Extension에서 shared UserDefaults에 새로 저장한 값은 App과 Extension에서 모두 쓸 수 있게 됩니다. 

 

 

[3] Combinded UserDefaults

 

그러면 shared UserDefaults를 쓰기 전에 아래와 같이 App쪽에서 standard에 미리 저장했던 데이터는 어떻게 될까요..?! 

 UserDefaults.standard.setValue("테스트입니다", forKey: "test")

 

이렇게 출력해보면 nil이 나옵니다. 

 print(UserDefaults.shared.value(forKey: "test"))

 

아예 standard UserDefaults와 다른 새로운 UserDefaults를 만들었기 때문에

standard UserDefaults에 저장했던 데이터는 새로운 UserDefaults에 없는 것이 당연합니다.

 

 

그럴때는 addSuite 함수를 사용하여서 standard UserDefaults와 새로운 UserDefaults를 combine시킬 수 있습니다!+!

extension UserDefaults {
    static var shared: UserDefaults {
        let combined = UserDefaults.standard
        let appGroupId = "group.com.eunjin.githubprviewer"
        combined.addSuite(named: appGroupId)
        return combined
    }
}

 

print를 다시 해보면 이전에 저장했던 값이 잘 나오는 것을 알수있습니다. 

 print(UserDefaults.shared.value(forKey: "test"))

 

하지만 타겟을 Widget Extension으로 바꾸고 위젯쪽에서 출력해보면 nil이 나옵니다.

 print(UserDefaults.shared.value(forKey: "test"))

 

왜냐면 UserDefaults.standard는 앱과 위젯에서 각각 다른 저장소이기 때문입니다. 

위젯 extension으로 돌리면 UserDefaults.standard는 위젯 extension의 저장소를 말하는 것이고

여기에는 저 값이 없어서 nil이 나오는 게 맞습니다-!! 

(아까 App쪽 UserDefaults.standard에 저장했으니까)

 

 

그래서 shared userdefaults 쓰기전, App쪽 standard userdefaults에 이미 저장되어있는 데이터를 쓰고 싶다면

이 코드로 다시 원복하고

extension UserDefaults {
    static var shared: UserDefaults {
        let appGroupId = "group.com.eunjin.githubprviewer"
        return UserDefaults(suiteName: appGroupId)!
    }
}

 

App쪽 Userdefaults standard에 저장된 값들을 모두 꺼내 shared userdefaults에 저장하면 됩니다-!!

UserDefaults.standard.dictionaryRepresentation().forEach { (key, value) in
    UserDefaults.shared.set(value, forKey: key)
}

 

 

[ 더보면 좋을 것 ] 

removeSuite(named:) 라는 함수도 있습니다-! 

 

 

[ 유닛 테스트 타겟 ] 

그렇담 앱 타겟과 UnitTest 타겟은 ? 

 

1) UnitTest 타겟이 Hosting Application으로 앱 타켓을 포함하지 않는 경우:

UserDefaults 를 공유하지 않는다.  (당연히 다른 번들이기 때문) 

 

 

2) UnitTest 타겟이 Hosting Application으로 앱 타켓을 포함하는 경우: 

테스트는 앱의 UserDefaults 를 READ 할 수 있지만 WRITE 할 수 없다.

('테스트만 앱을 알고 앱은 테스트를 알면 안된다.' 라는 방향에 맞는다.) 

 

양방향이 아니기 때문에 UserDefaults 를 공유한다고 말하지는 않겠다. 

자세히 말하면 다음과 같다.  (관련 공식 문서를 못찾음. 내가 직접 테스트해본 결과임) 

 

READ

1) 앱의 UserDefaults.standard 에 저장된 값을 테스트에서도 읽어올 수 있다. 

 

2) 앱의 UserDefaults(suiteName:) 에 저장된 값을 테스트에서도 읽어올 수 있다.   // test target에 appGroup 추가 안하고 app target에만 추가한 경우도.

 

 

WRITE 

하지만 테스트에서 UserDefaults 값을 변경해도 app에서 찍어보면 반영되지 않는다. 

 

 

------

참고로 번들 단위인 것 같다.

앱 타겟 - 유닛 테스트 타겟 과

Swift Pacakge 내의 Source - Tests 는 좀 다르다. 

Swift Pacakge > 테스트에서 UserDefaults 값을 변경하면 source 쪽에도 반영되기 때문이다.  (양방향) 

 

 

Reference 

www.swiftbysundell.com/articles/the-power-of-userdefaults-in-swift/

 

The power of UserDefaults in Swift | Swift by Sundell

The UserDefaults API can at first glance appear to be somewhat limited. However, it turns out that the power of UserDefaults extends far beyond simply storing and loading basic value types. This week, let’s take a look at what some of that power comes fr

www.swiftbysundell.com

 

 

 

 

 

반응형
댓글