2015-01-25 4 views
11

Swift와 Cocoa에 대한 버그가 NSUserDefaults와 함께 작동합니다. 유형 정보가 없기 때문에 항상 objectForKey의 결과를 예상 한 것으로 캐스트해야합니다. 도망. 그것은 안전하지 못하고 비실용적입니다. 나는이 문제를 해결하기 위해 스위프트 랜드에서 NSUserDefaults를보다 실용적으로 만들고, 길을 따라 무언가를 배우기를 희망했다. 내 목표는 처음에 다음과 같습니다.Swift에서 NSUserDefaults - 유형 안전을 구현합니다.

  1. 완전한 유형 안전 : 각 키에는 관련된 유형이 하나 있습니다. 값을 설정할 때 해당 유형의 값만 받아 들여야하며 값을 가져올 때 결과는 올바른 유형으로 나와야합니다.
  2. 의미와 내용이 명확한 전체 키 목록. 목록은 작성, 수정 및 확장이 용이해야합니다.
  3. 가능한 경우 아래 첨자를 사용하여 구문을 삭제하십시오. 예를 들어, 이것은 이 완벽 할 것입니다 :

    3.1. 집합 : UserDefaults [.MyKey] = 값

    3.2. 얻을 : 자동 하여 NSCoding 프로토콜을 준수 클래스 값 = UserDefaults은 [.MyKey]

  4. 지원하자 [취소] NSUserDefaults에서 허용하는 모든 속성 목록 유형에 대한 그들에게

  5. 지원을 보관

나는이 일반적인 구조체를 생성하여 시작 :

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입니다.

+0

나는별로 의미가 없다고 생각합니다. 사용자 기본값은 명령 호출 매개 변수를 저장하는 (다소 편한) 방법입니다. 완전한 객체를 저장하는 것이 아닙니다. –

+0

@ThomasKilian 대부분의 프로젝트에서'[[String : [Int]] '와 같은 데이터 구조와'TimerInfo','Person','Event'와 같은 커스텀 객체를 저장합니다. 객체는 매우 복잡하지 않으며 CoreData와 같이보다 정교한 라이브러리를 사용할 이유가 없습니다. 왜 이것이 의미가 없는지 나는 생각하지 않는다. 게다가 훌륭한 학습 기회입니다. – Alex

+1

보통 문자열/ints/float의 수를 줄이는 객체에 대해 직렬화를 수행합니다. 물론 YMMV. 그러나 사용자 기본값을 공유하지 않으므로 유형 안전이 덜 중요하다고 생각합니다.난 당신이 원하는 것을 얻을 수 있도록 주위에 래퍼 클래스를 만들 것입니다. –

답변

4

자랑하지 말고 ... 농담입니까? 나는 totally do입니다!

Preferences.set([NSData()], forKey: "MyKey1") 
Preferences.get("MyKey1", type: type([NSData])) 
Preferences.get("MyKey1") as [NSData]? 

func crunch1(value: [NSData]) 
{ 
    println("Om nom 1!") 
} 

crunch1(Preferences.get("MyKey1")!) 

Preferences.set(NSArray(object: NSData()), forKey: "MyKey2") 
Preferences.get("MyKey2", type: type(NSArray)) 
Preferences.get("MyKey2") as NSArray? 

func crunch2(value: NSArray) 
{ 
    println("Om nom 2!") 
} 

crunch2(Preferences.get("MyKey2")!) 

Preferences.set([[String:[Int]]](), forKey: "MyKey3") 
Preferences.get("MyKey3", type: type([[String:[Int]]])) 
Preferences.get("MyKey3") as [[String:[Int]]]? 

func crunch3(value: [[String:[Int]]]) 
{ 
    println("Om nom 3!") 
} 

crunch3(Preferences.get("MyKey3")!) 
+0

예! 고맙습니다. 프로토콜'_ObjectiveCBridgeable'은 제가 원했던 것입니다 !! – Alex

+0

Ups, yep ...하지만'_' 접두사는 프로덕션 코드에서 사용할 때주의해야한다고 생각합니다. –

+0

일반적으로'_'는 문서화되지 않은 내부 유형 프로토콜 등에 사용됩니다. 이는 통신/문서화없이 미래에 변경 될 수 있음을 의미합니다. Swift는 여전히 거대한 진화를 거듭하고 있으므로이 솔루션은 _time_에서 안정적이지 않을 수 있습니다. 그것이 개인 api의 일부가 아닌 경우, 당신은 그것을 사용할 수 있습니다 ... 그냥 그것을 알아. 어쨌든 이것은 당신의 앱이 당신의 현재 빠른 런타임을 묶음으로 가질 것이라는 사실에 의해 완화 될 것입니다. 따라서 배포가 완료되면 Apple의 업데이트 후에도 작동합니다. –

-1

제 생각을 소개하고 싶습니다. (사전에 불쌍한 내 영어 죄송합니다.)

let plainKey = UDKey("Message", string) 
let mixedKey 
    = UDKey("Mixed" 
     , array(dictionary(
      string, tuple(
       array(integer), 
       optional(date))))) 

let ud = UserDefaults(NSUserDefaults.standardUserDefaults()) 
ud.set(plainKey, "Hello") 
ud.set(plainKey, 2525) // <-- compile error 
ud.set(mixedKey, [ [ "(^_^;)": ([1, 2, 3], .Some(NSDate()))] ]) 
ud.set(mixedKey, [ [ "(^_^;)": ([1, 2, 3], .Some(NSData()))] ]) // <-- compile error 

유일한 차이점은 UDKey() 지금 # 2 인자, BiMap 클래스의 값을 필요로한다는 것이다. 나는 원래 UDKey의 작업을 형식의 값을 다른 형식의 값으로 변환하는 BiMap으로 연결 해제했습니다.

public class BiMap<A, B> { 
    public func AtoB(a: A) -> B? 
    public func BtoA(b: B) -> A? 
} 

따라서 set/get 캔 허용 유형 BiMap 의해 수행되지 않으며, 더 이상 as 자동 AnyObject로부터/ 를 전송할 수있는 타입으로 제한 (구체적 NSUserDefaults 캔 허용 유형.).

BiMap은 제네릭 클래스이기 때문에 원하는 하위 유형을 쉽게 만들 수 있으며 원하는 두 가지 유형을 교환 할 수 있습니다.

다음은 전체 소스 코드입니다. (하지만 아직 해결해야 할 버그가 있습니다.) https://gist.github.com/hisui/47f170a9e193168dc946