Swift 3.0 - 损坏的函数值(对于组件:Calendar.Component)-> Int?

Swift 3.0 - Broken func value(for component: Calendar.Component) -> Int?

是否有 value(对于组件:Calendar.Component)似乎被破坏的解决方法?或者调用 属性 版本的动态方式?

func dateComponentsValueShouldBeNil() {
     let dateComponents = DateComponents(month: 3)
     debugPrint("Month", dateComponents.month) // "Month" Optional(3)
     debugPrint("Property", dateComponents.hour) // "Property" nil
     debugPrint("Enumeration", dateComponents.value(for: .hour)) // "Enumeration" Optional(9223372036854775807)
}

DateComponents 实例中的非定义日期组件(那里的值为 nil)在包装的 NSDateComponents 实例中由全局 int 属性 NSDateComponentUndefined

这不是 bug/broken 方法,而是从 DateComponentsNSDateComponents 紧密相连的事实得出的(它派生自 NSObject:不使用可选项nil 来指定没有值的对象)。具体来说,前者的 value(...) 方法主要是后者 value(forComponent:) 方法的包装器 (API Reference: Foundation -> NSDateComponents)

value(forComponent:)

Returns the value for a given NSCalendarUnit value.

Declaration

func value(forComponent unit: NSCalendar.Unit) -> Int

此方法不能 return nil,但表示 global int variable NSDateComponentUndefined.

没有值的日期组件
let dateComponents = DateComponents(month: 3)
debugPrint("Enumeration", dateComponents.value(for: .hour))
    // "Enumeration" Optional(9223372036854775807)
debugPrint("NSDateComponentUndefined", NSDateComponentUndefined)
    // "NSDateComponentUndefined" 9223372036854775807

API Reference: Foundation -> NSDateComponents我们读到:

...

An NSDateComponents object is not required to define all the component fields. When a new instance of NSDateComponents is created the date components are set to NSDateComponentUndefined.

因此,尝试为 DateComponentsCalendar.Component 成员调用 value(...) 的 return 还没有被初始化不是乱码,而是默认的未定义值NSDateComponentUndefined(全局 int 属性 恰好 return/be 设置为 Int64.max = 9223372036854775807)。


要深入了解这方面的细节,我们可以访问 DateComponents (swift-corelibs-foundation/Foundation/DateComponents.swift)

的来源
/// Set the value of one of the properties, using an enumeration value instead of a property name.
///
/// The calendar and timeZone and isLeapMonth properties cannot be set by this method.
public mutating func setValue(_ value: Int?, for component: Calendar.Component) {
    _applyMutation {
        [=13=].setValue(_setter(value), forComponent: Calendar._toCalendarUnit([component]))
    }
}

/// Returns the value of one of the properties, using an enumeration value instead of a property name.
///
/// The calendar and timeZone and isLeapMonth property values cannot be retrieved by this method.
public func value(for component: Calendar.Component) -> Int? {
    return _handle.map {
        [=13=].value(forComponent: Calendar._toCalendarUnit([component]))
    }
}

属性 _handle 上面换行 NSDateComponents

internal var _handle: _MutableHandle<NSDateComponents>

_MutableHandle (swift-corelibs-foundation/Foundation/Boxing.swift)

internal final class _MutableHandle<MutableType : NSObject> where MutableType : NSCopying {
    fileprivate var _pointer : MutableType

    // ...

    /// Apply a closure to the reference type.
    func map<ReturnType>(_ whatToDo : (MutableType) throws -> ReturnType) rethrows -> ReturnType {
        return try whatToDo(_pointer)
    }

    // ...
}

从上面value(...)的签名推导出ReturnTypeInt?_toCalendarValue定义为(swift-corelibs-foundation/Foundation/Calendar.swift)

internal static func _toCalendarUnit(_ units: Set<Component>) -> NSCalendar.Unit {
    let unitMap: [Component : NSCalendar.Unit] =
        [.era: .era,
         .year: .year,
         .month: .month,
         .day: .day,
         .hour: .hour,
         .minute: .minute,
         .second: .second,
         .weekday: .weekday,
         .weekdayOrdinal: .weekdayOrdinal,
         .quarter: .quarter,
         .weekOfMonth: .weekOfMonth,
         .weekOfYear: .weekOfYear,
         .yearForWeekOfYear: .yearForWeekOfYear,
         .nanosecond: .nanosecond,
         .calendar: .calendar,
         .timeZone: .timeZone]

    var result = NSCalendar.Unit()
    for u in units {
        let _ = result.insert(unitMap[u]!)
    }
    return result
}

因此,value(...) 的 return 正文中的 whatToDo 可以破译为等同于对

的调用
NSDateComponents.value(forComponent: NSCalendar.Unit)

并且如该答案顶部所述,此调用永远不会 return nil(return 类型 Int)。

如果我们最后 return 到 DateComponents 的来源,我们会看到下面的私有 getter 和 setter 处理 "bridging" 之间NSDateComponentUndefinednil.

/// Translate from the NSDateComponentUndefined value into a proper Swift optional
private func _getter(_ x : Int) -> Int? { return x == NSDateComponentUndefined ? nil : x }

/// Translate from the proper Swift optional value into an NSDateComponentUndefined
private func _setter(_ x : Int?) -> Int { if let xx = x { return xx } else { return NSDateComponentUndefined } }