Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Property Wrappers

shtnkgm
October 31, 2019

Property Wrappers

Property Wrappers in Swift

shtnkgm

October 31, 2019
Tweet

More Decks by shtnkgm

Other Decks in Programming

Transcript

  1. Property Wrappers
    @shtnkgm

    View Slide

  2. Swift5.1ʢXcode11.0ʣ͔Β৽ػೳ
    Property Wrappers͕࢖͑Δʂ

    View Slide

  3. DocumentΛΈͯΈΔ
    https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617

    View Slide

  4. Property Wrappers
    A property wrapper adds a layer of separation between code that
    manages how a property is stored and the code that defines a
    property.

    View Slide

  5. !

    View Slide

  6. Property Wrappersͱ͸
    • ϓϩύςΟʹڞ௨ػೳΛ௥ՃͰ͖Δ
    • ϓϩύςΟ΁ͷΞΫηεํ๏ʹؔ͢ΔAttributeΛͭ͘ΔͨΊͷ
    Attribute

    View Slide

  7. Attribute?

    View Slide

  8. @͕͍ͭͯΔ͜Μͳ΍ͭ

    View Slide

  9. @autoclosure
    @escaping
    @convention
    @available
    @discardableResult
    @objc
    @nonobjc
    @objcMembers
    @GKInspectable
    @UIApplicationMain
    @NSApplicationMain
    @NSCopying
    @NSKeyedArchiverClassName
    @NSManaged
    @testable
    @IBAction
    @IBOutlet
    @IBDesignable
    @IBInspectable

    View Slide

  10. @ʙͱهड़͢Δ͜ͱͰɺϓϩύςΟ
    ΍ܕͷએݴʹิ଍৘ใΛઃఆͰ͖Δ
    ػೳ

    View Slide

  11. View Slide

  12. View Slide

  13. Swift5.1
    @functionBuilder
    @dynamicCallable
    @dynamicMemberLookup
    @inlinable
    @requiresstoredpropertyinits
    @usableFromInline
    @warnunqualifiedaccess

    View Slide

  14. @propertyWrapper

    View Slide

  15. ϓϩύςΟͷ஋ʹΞΫηε͢Δํ๏
    ΛAttributeͱͯ͠ڞ௨ͰఆٛͰ͖
    Δʂ

    View Slide

  16. AttributeΛͭͬͯ͘ΈΔ
    // 12ҎԼΛอূ͢Δ
    @propertyWrapper
    struct TwelveOrLess {
    private var number = 0
    // ඞਢ
    var wrappedValue: Int {
    get { return number }
    set { number = min(newValue, 12) }
    }
    }

    View Slide

  17. AttributeΛ͔ͭͬͯΈΔ
    struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
    }
    var rectangle = SmallRectangle()
    print(rectangle.height) // "0"
    rectangle.height = 13
    print(rectangle.height) // "12"

    View Slide

  18. 12Ҏ֎ʹ΋ରԠͰ͖ΔΑ͏ʹ֦ு
    ͍ͨ͠

    View Slide

  19. Property Wrappersʹॳظ஋Λ༩͑
    Δ

    View Slide

  20. @propertyWrapper
    struct SmallNumber {
    private var maximum: Int
    private var number: Int
    var wrappedValue: Int {
    get { return number }
    set { number = min(newValue, maximum) }
    }
    init() {
    maximum = Int.max
    number = 0
    }
    init(wrappedValue: Int) {
    maximum = Int.max
    number = min(wrappedValue, maximum)
    }
    init(wrappedValue: Int, maximum: Int) {
    self.maximum = maximum
    number = min(wrappedValue, maximum)
    }
    }

    View Slide

  21. ॳظ஋Λઃఆ͢Δ
    struct Rectangle {
    @SmallNumber var height: Int
    @SmallNumber(wrappedValue: 0, maximum: 10) var width: Int
    }
    var rectangle = Rectangle()
    rectangle.width = 20
    print(rectangle.height, rectangle.width) // 0 10

    View Slide

  22. Projected Value

    View Slide

  23. projected
    • ܭը͞Εͨ
    • ݟੵ΋ΒΕͨ
    • ౤Ө͞Εͨ

    View Slide

  24. !

    View Slide

  25. Projected ValueΛఆٛ͢Δ

    View Slide

  26. @propertyWrapper
    struct SmallNumber {
    private var number = 0
    var projectedValue = false
    var wrappedValue: Int {
    get { return number }
    set {
    if newValue > 12 {
    number = 12
    projectedValue = true
    } else {
    number = newValue
    projectedValue = false
    }
    }
    }
    }

    View Slide

  27. Projected ValueΛࢀর͢Δʢ$ϓϩύςΟ
    ໊ʣ
    struct SomeStructure {
    @SmallNumber var someNumber: Int
    }
    var someStructure = SomeStructure()
    someStructure.someNumber = 4
    print(someStructure.$someNumber)
    // Prints "false"
    someStructure.someNumber = 55
    print(someStructure.$someNumber)
    // Prints "true"

    View Slide

  28. Property Wrapper Composition
    @A @B var x: Int

    View Slide

  29. Property Wrapperͷ׆༻

    View Slide

  30. CodableͳArrayͷdecode
    Ұ෦ͷཁૉ͕decodeʹࣦഊ͢Δͱશཁૉࣦഊ͢Δ

    View Slide

  31. struct Response: Codable {
    var values: [Int]
    }
    let json = #"{ "values": [1, 2, null, 4, 5, null] }"#.data(using: .utf8)!
    let result = try JSONDecoder().decode(Response.self, from: json) // nil

    View Slide

  32. Property WrappersΛ࢖Θͳ͍ํ๏

    View Slide

  33. FailableܕΛఆٛ

    View Slide

  34. public struct Failable: Codable {
    public let value: Wrapped?
    public init(from decoder: Decoder) throws {
    do {
    let container = try decoder.singleValueContainer()
    value = try container.decode(Wrapped.self)
    } catch {
    value = nil
    }
    }
    public func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    if let value = self.value {
    try container.encode(value)
    } else {
    try container.encodeNil()
    }
    }
    }

    View Slide

  35. public struct Response: Codable {
    public let query: String
    public let items: [Item]
    public init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.query = try container.decode(String.self, forKey: .query)
    self.items = try container.decode([Failable].self, forKey: .suggests).compactMap { $0.value }
    }
    public init(query: String, items: [Item]) {
    self.query = query
    self.items = items
    }
    }

    View Slide

  36. Property WrappersΛ࢖ͬͨํ๏

    View Slide

  37. @LossyArray

    View Slide

  38. @propertyWrapper
    public struct LossyArray: Codable {
    private struct AnyDecodableValue: Codable {}
    private struct LossyDecodableValue: Codable {
    let value: Value
    public init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    value = try container.decode(Value.self)
    }
    }
    public var wrappedValue: [T]
    public init(wrappedValue: [T]) {
    self.wrappedValue = wrappedValue
    }
    public init(from decoder: Decoder) throws {
    var container = try decoder.unkeyedContainer()
    var elements: [T] = []
    while !container.isAtEnd {
    do {
    let value = try container.decode(LossyDecodableValue.self).value
    elements.append(value)
    } catch {
    _ = try? container.decode(AnyDecodableValue.self)
    }
    }
    self.wrappedValue = elements
    }
    public func encode(to encoder: Encoder) throws {
    try wrappedValue.encode(to: encoder)
    }
    }

    View Slide

  39. struct Response: Codable {
    @LossyArray var values: [Int]
    }
    let json = #"{ "values": [1, 2, null, 4, 5, null] }"#.data(using: .utf8)!
    let result = try JSONDecoder().decode(Response.self, from: json)
    print(result) // [1, 2, 4, 5]

    View Slide

  40. CodableͰσϑΥϧτ஋Λઃఆ͢Δ
    ͷ͸ͪΐͬͱखؒ
    init(from decoder: Decoder)Λॻ͔ͳ͍ͱ͍͚ͳ͍

    View Slide

  41. struct User: Codable {
    var isAdmin: Bool
    }
    let json = #"{ "isAdmin": null }"#.data(using: .utf8)!
    let user = try? JSONDecoder().decode(User.self, from: json)
    print(user) // nil

    View Slide

  42. @DefaultFalse

    View Slide

  43. @propertyWrapper
    public struct DefaultFalse: Codable, Equatable, Hashable {
    public var wrappedValue: Bool
    public init(wrappedValue: Bool) {
    self.wrappedValue = wrappedValue
    }
    public init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    self.wrappedValue = (try? container.decode(Bool.self)) ?? false
    }
    public func encode(to encoder: Encoder) throws {
    try wrappedValue.encode(to: encoder)
    }
    }

    View Slide

  44. struct User: Codable {
    @DefaultFalse var isAdmin: Bool
    }
    let json = #"{ "isAdmin": null }"#.data(using: .utf8)!
    let user = try JSONDecoder().decode(User.self, from: json)
    print(user) // User(isAdmin: false)

    View Slide

  45. APIϨεϙϯε͕૝ఆͷܕͰͳ͍
    or
    APIʹΑΓܕ͕ҟͳΔ

    View Slide

  46. struct Response: Codable {
    var price: String
    var isDiscount: Bool
    }
    let json = #"{ "price": 12345, "isDiscount": "true" }"#.data(using: .utf8)!
    let result = try? JSONDecoder().decode(Response.self, from: json) // nil

    View Slide

  47. @LosslessValue

    View Slide

  48. @propertyWrapper
    public struct LosslessValue: Codable {
    private let type: LosslessStringCodable.Type
    public var wrappedValue: T
    public init(from decoder: Decoder) throws {
    do {
    self.wrappedValue = try T.init(from: decoder)
    self.type = T.self
    } catch let error {
    func decode(_: T.Type) -> (Decoder) -> LosslessStringCodable? {
    return { try? T.init(from: $0) }
    }
    let types: [(Decoder) -> LosslessStringCodable?] = [
    decode(String.self),decode(Bool.self),decode(Int.self),
    decode(Int8.self),decode(Int16.self),decode(Int64.self),
    decode(UInt.self),decode(UInt8.self),decode(UInt16.self),
    decode(UInt64.self),decode(Double.self),decode(Float.self),
    ]
    guard
    let rawValue = types.lazy.compactMap({ $0(decoder) }).first,
    let value = T.init("\(rawValue)")
    else { throw error }
    self.wrappedValue = value
    self.type = Swift.type(of: rawValue)
    }
    }
    public func encode(to encoder: Encoder) throws {
    let string = String(describing: wrappedValue)
    guard let original = type.init(string) else {
    let description = "Unable to encode '\(wrappedValue)' back to source type '\(type)'"
    throw EncodingError.invalidValue(string, .init(codingPath: [], debugDescription: description))
    }
    try original.encode(to: encoder)
    }
    }

    View Slide

  49. struct Response: Codable {
    @LosslessValue var price: String
    @LosslessValue var isDiscount: Bool
    }
    let json = #"{ "price": 12345, "isDiscount": "true" }"#.data(using: .utf8)!
    let result = try? JSONDecoder().decode(Response.self, from: json)
    print(result) // Response(price: "12355", isDiscount: true)

    View Slide

  50. View Slide

  51. SwiftUIͰఆٛ͞Εͨ
    Property Wrappers

    View Slide

  52. struct ContentView : View {
    @State var value: Bool = false
    var body: some View {
    Toggle(isOn: $value) {
    Text("The value is " + "\($value)")
    }
    }
    }

    View Slide

  53. @Published
    @ObservedObjectʢچ: @ObjectBindingʣ
    @Environment
    @EnvironmentObject
    @State
    @FetchRequest

    View Slide

  54. ·ͱΊ
    • @͸Attributeɺ@propertyWrapper͸Attributeͷͻͱͭ
    • ϑϨʔϜϫʔΫͰఆٛ͞ΕͨAttributeͰͳ͍΋ͷ͸
    @propertyWrapperͰఆٛ͞ΕͯΔ͔΋
    • $ϓϩύςΟ໊ͰProjected ValueʹΞΫηεͰ͖Δ
    • Property WrappersͰϓϩύςΟͷڞ௨ػೳΛͭ͘Ζ͏ʂ

    View Slide

  55. ࢀߟ
    • https://github.com/marksands/BetterCodable
    • https://www.hackingwithswift.com/quick-start/swiftui/
    understanding-property-wrappers-in-swift-and-swiftui
    • SwiftUIͷProperty Wrappersͱσʔλ΁ͷΞΫηεํ๏

    View Slide

  56. ͓ΘΓ

    View Slide