     }
 }
 
+public protocol DefaultsCompatible {}
+extension String: DefaultsCompatible {}
+extension Int: DefaultsCompatible {}
+extension Double: DefaultsCompatible {}
+extension Bool: DefaultsCompatible {}
+extension Array: DefaultsCompatible where Element: DefaultsCompatible {}
+extension Dictionary: DefaultsCompatible where Key == String, Value: DefaultsCompatible {}
+extension Data: DefaultsCompatible {}
+extension Date: DefaultsCompatible {}
+extension URL: DefaultsCompatible {}
+
+extension UserDefaults {
+    public subscript<T: DefaultsCompatible>(key: DefaultsKey<T>) -> T? {
+        get { return self[key.keyString].flatMap({ $0 as? T }) }
+        set { self[key.keyString] = newValue }
+    }
+
+    private subscript(key: String) -> Any? {
+        get { return object(forKey: key) }
+        set {
+            guard let newValue = newValue else {
+                removeObject(forKey: key)
+                return
+            }
+
+            switch newValue {
+            // This should always be on top of Int because a cast
+            // from Double to Int will always succeed.
+            case let value as Double:
+                self.set(value, forKey: key)
+            case let value as Int:
+                self.set(value, forKey: key)
+            case let value as Bool:
+                self.set(value, forKey: key)
+            case let value as URL:
+                self.set(value, forKey: key)
+            default:
+                self.set(newValue, forKey: key)
+            }
+        }
+    }
+}
+
+extension MutableProperty where Value: Equatable {
+    func setValueIfDifferent(_ newValue: Value) {
+        if self.value != newValue { self.value = newValue }
+    }
+}
+
+// MARK: -
+
 extension DefaultsKeys {
-    static let cachedUsername = DefaultsKey<String>("UserIDCached")
     static let currentUsername = DefaultsKey<String>("InLoginStateID")
 
     static let forumOrder = DefaultsKey<[[String]]>("Order")

        super.init()

        // Login
        bind(propertyKeyPath: \.cachedUsername, to: .cachedUsername)
        bind(propertyKeyPath: \.currentUsername, to: .currentUsername)

        // Settings

        let keyPath = "\(key.keyString)"
        S1LogDebug("Register KVO keypath: \(keyPath)")
        defaults.addObserver(self, forKeyPath: keyPath, options: [.new], context: nil)
        observingKeyPaths[keyPath] = { (newValue) in
            if newValue is NSNull {
                property.setValueIfDifferent(.none)
            } else if let newValue = newValue as? T {
                property.setValueIfDifferent(.some(newValue))
            } else {
                S1LogError("Expect newValue \(newValue) has Type \(T.self) or NSNull but got \(type(of: newValue))")
                property.setValueIfDifferent(.none)
            }
        }
    }

    // swiftlint:disable block_based_kvo
    @objc override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
        guard let keyPath = keyPath else {
            fatalError("keyPath should not be nil.")
        }

        guard let newValue = change?[.newKey] else {
            fatalError("changes[.newKey] should not be nil.")
        }

        guard let callback = observingKeyPaths[keyPath] else {
            fatalError("observingKeyPaths should have information about \(keyPath)")
        }

        callback(newValue)
    }
}
