问题
Is there a workaround for value(for component: Calendar.Component) seemingly being broken? Or a dynamic way to call the property version?
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)
}
回答1:
Non-defined date components in a DateComponents
instance (valued nil
there) are represented in the wrapped NSDateComponents
instance by the global int
property NSDateComponentUndefined
This is not a bug/broken method, but follows from the fact that DateComponents
is tighly connected to NSDateComponents
(which derives from NSObject
: not using optionals an nil
to specify objects with no value). Specifically, the former's value(...)
method is, principally, a wrapper for the latter's value(forComponent:)
method (API Reference: Foundation -> NSDateComponents)
value(forComponent:)
Returns the value for a given NSCalendarUnit value.
Declaration
func value(forComponent unit: NSCalendar.Unit) -> Int
This method cannot return nil
, but represents date components without a value by the global int variable NSDateComponentUndefined.
let dateComponents = DateComponents(month: 3)
debugPrint("Enumeration", dateComponents.value(for: .hour))
// "Enumeration" Optional(9223372036854775807)
debugPrint("NSDateComponentUndefined", NSDateComponentUndefined)
// "NSDateComponentUndefined" 9223372036854775807
From API Reference: Foundation -> NSDateComponents we read:
...
An
NSDateComponents
object is not required to define all the component fields. When a new instance ofNSDateComponents
is created the date components are set toNSDateComponentUndefined
.
Hence, the return for attempting to call value(...)
for a Calendar.Component
member of DateComponents
that has not been initialized is not gibberish, but the default undefined value NSDateComponentUndefined
(a global int
property which happens return/be set to Int64.max = 9223372036854775807
).
To dwell deeper into the details of this, we may visit the source of 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 {
$0.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 {
$0.value(forComponent: Calendar._toCalendarUnit([component]))
}
}
property _handle
above wraps NSDateComponents
internal var _handle: _MutableHandle<NSDateComponents>
in a _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)
}
// ...
}
From the signature of value(...)
above ReturnType
is inferred to as Int?
, and _toCalendarValue
is defined as (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
}
Hence, whatToDo
in the body of the return of value(...)
can be deciphered to be equivalent to a call to
NSDateComponents.value(forComponent: NSCalendar.Unit)
And as descriped in the top of this answer, this call can never return nil
(return type Int
).
If we, finally, return to the source of DateComponents, we see that the following private getter and setter handles the "bridging" between NSDateComponentUndefined
and nil
.
/// 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 } }
来源:https://stackoverflow.com/questions/39473089/swift-3-0-broken-func-valuefor-component-calendar-component-int