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: .
— . — , . , .