Swift와 Cocoa에 대한 버그가 NSUserDefaults와 함께 작동합니다. 유형 정보가 없기 때문에 항상 objectForKey
의 결과를 예상 한 것으로 캐스트해야합니다. 도망. 그것은 안전하지 못하고 비실용적입니다. 나는이 문제를 해결하기 위해 스위프트 랜드에서 NSUserDefaults를보다 실용적으로 만들고, 길을 따라 무언가를 배우기를 희망했다. 내 목표는 처음에 다음과 같습니다.Swift에서 NSUserDefaults - 유형 안전을 구현합니다.
- 완전한 유형 안전 : 각 키에는 관련된 유형이 하나 있습니다. 값을 설정할 때 해당 유형의 값만 받아 들여야하며 값을 가져올 때 결과는 올바른 유형으로 나와야합니다.
- 의미와 내용이 명확한 전체 키 목록. 목록은 작성, 수정 및 확장이 용이해야합니다.
가능한 경우 아래 첨자를 사용하여 구문을 삭제하십시오. 예를 들어, 이것은 이 완벽 할 것입니다 :
3.1. 집합 : UserDefaults [.MyKey] = 값
3.2. 얻을 : 자동 하여 NSCoding 프로토콜을 준수 클래스 값 = UserDefaults은 [.MyKey]
지원하자 [취소] NSUserDefaults에서 허용하는 모든 속성 목록 유형에 대한 그들에게
- 지원을 보관
나는이 일반적인 구조체를 생성하여 시작 :
struct UDKey <T> {
init(_ n: String) { name = n }
let name: String
}
은 그 때 나는 응용 프로그램에서 모든 키에 대한 컨테이너 역할이 다른 구조체를 생성 lication :
struct UDKeys {}
이것은 다음 필요한 곳마다 키를 추가 확장 할 수 있습니다 : 각 키는 그와 관련된 유형을 얼마나
extension UDKeys {
static let MyKey1 = UDKey<Int>("MyKey1")
static let MyKey2 = UDKey<[String]>("MyKey2")
}
참고. 저장 될 정보의 유형을 나타냅니다. 또한 name 속성은 NSUserDefaults의 키로 사용할 문자열입니다.
키는 모두 하나의 상수 파일에 나열되거나 데이터 저장에 사용되는 위치에 가까운 파일 단위로 확장명을 사용하여 추가 할 수 있습니다.
은 그 때 나는 정보를 얻기/설정 처리를 담당에 "UserDefaults"클래스를 만들었 :class UserDefaultsClass {
let storage = NSUserDefaults.standardUserDefaults()
init(storage: NSUserDefaults) { self.storage = storage }
init() {}
// ...
}
let UserDefaults = UserDefaultsClass() // or UserDefaultsClass(storage: ...) for further customisation
아이디어는 특정 도메인에 대한 하나 개의 인스턴스를 만든 다음 모든 방법은이 방법으로 액세스 할 수 있다는 것입니다 :
let value = UserDefaults.myMethod(...)
나는이 UserDefaults.sharedInstance.myMethod (...) (! 너무 오래) 같은 것들에 대한 접근 방식이나 모든 것에 클래스 메소드를 사용하여 선호합니다. 또한 다른 저장 장치 값을 가진 둘 이상의 UserDefaultsClass를 사용하여 동시에 여러 도메인과 상호 작용할 수 있습니다.
지금까지 항목 1과 2가 처리되었지만 이제 어려운 부분 인 나머지 : 나머지 부분을 준수하기 위해 UserDefaultsClass에서 메소드를 실제로 디자인하는 방법.
예를 들어,이 항목 4. 시작하자 먼저 나는 (이 코드는 UserDefaultsClass 안에)이 시도 :
subscript<T: NSCoding>(key: UDKey<T>) -> T? {
set { storage.setObject(NSKeyedArchiver.archivedDataWithRootObject(newValue), forKey: key.name) }
get {
if let data = storage.objectForKey(key.name) as? NSData {
return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? T
} else { return nil }
}
}
을하지만 그때 나는 스위프트는 일반적인 첨자를 허용하지 않는 것을 발견! 알았어, 그럼 기능을 사용해야 할 것 같아. 이 항목 3의 절반 ...
func set <T: NSCoding>(key: UDKey<T>, _ value: T) {
storage.setObject(NSKeyedArchiver.archivedDataWithRootObject(value), forKey: key.name)
}
func get <T: NSCoding>(key: UDKey<T>) -> T? {
if let data = storage.objectForKey(key.name) as? NSData {
return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? T
} else { return nil }
}
간다 그리고 그것은 잘 작동 : 나는 UserDefaults.get(.MyKey)
을
extension UDKeys { static let MyKey = UDKey<NSNotification>("MyKey") }
UserDefaults.set(UDKeys.MyKey, NSNotification(name: "Hello!", object: nil))
let n = UserDefaults.get(UDKeys.MyKey)
주 호출 할 수있는 방법. UDKeys.MyKey
을 사용해야합니다. 그리고 그것은 일반적인 구조체에 정적 변수를 가질 수 없기 때문에 그렇게 할 수 없습니다 !!
다음으로 5 번을 시도해보십시오. 이제 두통이 있었고 많은 도움이 필요한 부분입니다.
속성 목록 유형은 문서에 따라, 다음과 같습니다
,기본 객체 속성 목록이어야합니다, 즉, (의 인스턴스의 조합 모음 또는 )는의 인스턴스입니다 :있는 NSData,있는 NSString , NSNumber, NSDate, NSArray 또는 NSDictionary. 등 스위프트에
Int
의미
[Int]
,
[[String:Bool]]
,
[[String:[Double]]]
모든 속성 목록 유형입니다. 당신이 알 수 있습니다으로이 잘 작동하는 동안,
func set <T: AnyObject>(key: UDKey<T>, _ value: T) {
storage.setObject(value, forKey: key.name)
}
func get <T: AnyObject>(key: UDKey<T>) -> T? {
return storage.objectForKey(key.name) as? T
}
을 :하지만 처음에 나는 그냥 쓰기 만 PLIST 유형이 허용 기억이 코드를 사용하여 누구든지 믿을 수 있다고 생각
extension UDKeys { static let MyKey = UDKey<NSData>("MyKey") }
UserDefaults.set(UDKeys.MyKey, NSData())
let d = UserDefaults.get(UDKeys.MyKey)
extension UDKeys { static let MyKey = UDKey<[NSData]>("MyKey") }
UserDefaults.set(UDKeys.MyKey, [NSData()])
을 그리고이 중 하나를하지 않는 :
이되지 않습니다
extension UDKeys { static let MyKey = UDKey<[Int]>("MyKey") }
UserDefaults.set(UDKeys.MyKey, [0])
조차이 :
extension UDKeys { static let MyKey = UDKey<Int>("MyKey") }
UserDefaults.set(UDKeys.MyKey, 1)
문제는 그들이 모든 유효한 속성 목록 유형 스위프트가 분명하지 자신의 목표 - C 클래스 대응으로, 구조체로 배열과의 int를 해석 아직 있다는 것입니다. 그러나 :
func set <T: Any>(key: UDKey<T>, _ value: T)
값이되어야하기 때문에 다음 값 유형의 Obj-C의 클래스 사촌 예의가 아니라 사람이 인정되기 때문에
이 중 하나가 작동하지 않으며, storage.setObject(value, forKey: key.name)
가 더 이상 유효하지 않습니다 참조 유형.
프로토콜은 참조 유형 및 목표 - C에서 참조 유형으로 변환 할 수있는 모든 값 유형을 받아 들였다 스위프트에 존재하는 경우 (같은 [Int]
다른 예 내가 언급)이 문제가 해결 될 것입니다 :
func set <T: AnyObjectiveCObject>(key: UDKey<T>, _ value: T) {
storage.setObject(value, forKey: key.name)
}
func get <T: AnyObjectiveCObject>(key: UDKey<T>) -> T? {
return storage.objectForKey(key.name) as? T
}
AnyObjectiveCObject
는
, AFAIK이 존재하지 않습니다 ... 어떤 신속 클래스와 신속한 배열, 사전, 숫자 (int 치의, 수레, bools 등의 NSNumber로 변환), 문자열을 받아 들일 것입니다.
질문 : (과부하 일반적인 기능 또는 컬렉션) 일반적인 함수를 작성할 수 있습니다 어떻게
그 제네릭 형식 T 모든 참조 형 또는 스위프트가 변환 할 수있는 값 유형이 될 수 있습니다 Objective-C의 참조 유형?
해결
: 내가 가진 답변의 도움으로, 나는 내가 원하는 것을에 도착했다. 누군가 내 솔루션을보고 싶다면 here입니다.
나는별로 의미가 없다고 생각합니다. 사용자 기본값은 명령 호출 매개 변수를 저장하는 (다소 편한) 방법입니다. 완전한 객체를 저장하는 것이 아닙니다. –
@ThomasKilian 대부분의 프로젝트에서'[[String : [Int]] '와 같은 데이터 구조와'TimerInfo','Person','Event'와 같은 커스텀 객체를 저장합니다. 객체는 매우 복잡하지 않으며 CoreData와 같이보다 정교한 라이브러리를 사용할 이유가 없습니다. 왜 이것이 의미가 없는지 나는 생각하지 않는다. 게다가 훌륭한 학습 기회입니다. – Alex
보통 문자열/ints/float의 수를 줄이는 객체에 대해 직렬화를 수행합니다. 물론 YMMV. 그러나 사용자 기본값을 공유하지 않으므로 유형 안전이 덜 중요하다고 생각합니다.난 당신이 원하는 것을 얻을 수 있도록 주위에 래퍼 클래스를 만들 것입니다. –