Property wrappers in Swift with code examples

The translation of the article was prepared as part of the online course "iOS Developer. Professional" . If you are interested in learning more about the course, come to the Open House online.






Property Wrappers ( ) Swift -. WWDC 2019 Xcode 11 Swift 5 , . Swift, , , , .





Swift SE-0258. , @NSCopying



, , , , , .





?

, , . .





, . :





extension UserDefaults {

    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)
    static var hasSeenAppIntroduction: Bool
}
      
      



@UserDefault



. , , . , , . , . User Defaults.





UserDefaults

, . UserDefaults



, .





extension UserDefaults {

    public enum Keys {
        static let hasSeenAppIntroduction = "has_seen_app_introduction"
    }

    /// Indicates whether or not the user has seen the onboarding.
    var hasSeenAppIntroduction: Bool {
        set {
            set(newValue, forKey: Keys.hasSeenAppIntroduction)
        }
        get {
            return bool(forKey: Keys.hasSeenAppIntroduction)
        }
    }
}
      
      



:





UserDefaults.standard.hasSeenAppIntroduction = true

guard !UserDefaults.standard.hasSeenAppIntroduction else { return }
showAppIntroduction()
      
      



, . . @propertyWrapper



.





, . -, UserDefault. .





SwiftUI, , AppStorage. .





@propertyWrapper
struct UserDefault<Value> {
    let key: String
    let defaultValue: Value
    var container: UserDefaults = .standard

    var wrappedValue: Value {
        get {
            return container.object(forKey: key) as? Value ?? defaultValue
        }
        set {
            container.set(newValue, forKey: key)
        }
    }
}
      
      



, . , Value.





UserDefaults



:





extension UserDefaults {

    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)
    static var hasSeenAppIntroduction: Bool
}
      
      



, struct . , , false. :





UserDefaults.hasSeenAppIntroduction = false
print(UserDefaults.hasSeenAppIntroduction) // Prints: false
UserDefaults.hasSeenAppIntroduction = true
print(UserDefaults.hasSeenAppIntroduction) // Prints: true
      
      



. , , , . , , :





extension UserDefaults {
    static let groupUserDefaults = UserDefaults(suiteName: "group.com.swiftlee.app")!

    @UserDefault(key: "has_seen_app_introduction", defaultValue: false, container: .groupUserDefaults)
    static var hasSeenAppIntroduction: Bool
}
      
      



, . , .





extension UserDefaults {

    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)
    static var hasSeenAppIntroduction: Bool

    @UserDefault(key: "username", defaultValue: "Antoine van der Lee")
    static var username: String

    @UserDefault(key: "year_of_birth", defaultValue: 1990)
    static var yearOfBirth: Int
}
      
      



, , , .





, , , , . , AnyOptional



:





/// Allows to match for optionals with generics that are defined as non-optional.
public protocol AnyOptional {
    /// Returns `true` if `nil`, otherwise `false`.
    var isNil: Bool { get }
}
extension Optional: AnyOptional {
    public var isNil: Bool { self == nil }
}
      
      



UserDefault



, :





extension UserDefault where Value: ExpressibleByNilLiteral {
    
    /// Creates a new User Defaults property wrapper for the given key.
    /// - Parameters:
    ///   - key: The key to use with the user defaults store.
    init(key: String, _ container: UserDefaults = .standard) {
        self.init(key: key, defaultValue: nil, container: container)
    }
}
      
      



, .





, , :





@propertyWrapper
struct UserDefault<Value> {
    let key: String
    let defaultValue: Value
    var container: UserDefaults = .standard

    var wrappedValue: Value {
        get {
            return container.object(forKey: key) as? Value ?? defaultValue
        }
        set {
            // Check whether we're dealing with an optional and remove the object if the new value is nil.
            if let optional = newValue as? AnyOptional, optional.isNil {
                container.removeObject(forKey: key)
            } else {
                container.set(newValue, forKey: key)
            }
        }
    }

    var projectedValue: Bool {
        return true
    }
}
      
      



:





extension UserDefaults {

    @UserDefault(key: "year_of_birth")
    static var yearOfBirth: Int?
}

UserDefaults.yearOfBirth = 1990
print(UserDefaults.yearOfBirth) // Prints: 1990
UserDefaults.yearOfBirth = nil
print(UserDefaults.yearOfBirth) // Prints: nil
      
      



! . , , , Combine publisher, @Published



.





, , . . publisher Combine



, , .





user defaults



, publisher



, . : . :





import Combine
 
 @propertyWrapper
 struct UserDefault<Value> {
     let key: String
     let defaultValue: Value
     var container: UserDefaults = .standard
     private let publisher = PassthroughSubject<Value, Never>()
     
     var wrappedValue: Value {
         get {
             return container.object(forKey: key) as? Value ?? defaultValue
         }
         set {
             // Check whether we're dealing with an optional and remove the object if the new value is nil.
             if let optional = newValue as? AnyOptional, optional.isNil {
                 container.removeObject(forKey: key)
             } else {
                 container.set(newValue, forKey: key)
             }
             publisher.send(newValue)
         }
     }

     var projectedValue: AnyPublisher<Value, Never> {
         return publisher.eraseToAnyPublisher()
     }
 } 
We can now start 
      
      



:





let subscription = UserDefaults.$username.sink { username in
     print("New username: \(username)")
 }
 UserDefaults.username = "Test"
 // Prints: New username: Test 
      
      



! . , publisher . Combine, Combine Swift.





, ? , , , .





, -:





@propertyWrapper
struct SampleFile {

    let fileName: String

    var wrappedValue: URL {
        let file = fileName.split(separator: ".").first!
        let fileExtension = fileName.split(separator: ".").last!
        let url = Bundle.main.url(forResource: String(file), withExtension: String(fileExtension))!
        return url
    }

    var projectedValue: String {
        return fileName
    }
}
      
      



-, :





struct SampleFiles {
    @SampleFile(fileName: "sample-image.png")
    static var image: URL
}
      
      



projectedValue



, :





print(SampleFiles.image) // Prints: "../resources/sample-image.png"
print(SampleFiles.$image) // Prints: "sample-image.png"
      
      



, , () . , .





, . , , , , , .





, . filename



:





extension SampleFiles {
    static func printKey() {
        print(_image.fileName)
    }
}
      
      



, , .





API Swift. SwiftUI , @StateObject



@Binding



. : .





, . :





@Option(shorthand: "m", documentation: "Minimum value", defaultValue: 0)
var minimum: Int
      
      



, :





final class MyViewController {
    @UsesAutoLayout
    var label = UILabel()
}
      
      



, , translatesAutoresizingMaskIntoConstraints



false



. : Swift: .





— . — , . , .





Swift, swift. Twitter, . !








All Articles