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

Phi*_*son 4 swift3 xcode8

是否存在针对值(对于组件: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)
}
Run Code Online (Sandbox Code Playgroud)

dfr*_*fri 5

DateComponents实例中的未定义日期成分(在nil此处有值)在包装的NSDateComponents实例中由global int属性表示NSDateComponentUndefined

这不是错误/无效的方法,而是DateComponents基于紧密连接的事实NSDateComponents(它源自NSObject:不使用optionals nil指定没有值的对象)。具体来说,前者的value(...)方法主要是后者value(forComponent:)方法的包装器(API参考:Foundation-> NSDateComponents

value(forComponent:)
Run Code Online (Sandbox Code Playgroud)

返回给定NSCalendarUnit值的值。

宣言

func value(forComponent unit: NSCalendar.Unit) -> Int
Run Code Online (Sandbox Code Playgroud)

此方法不能返回nil,但是通过全局int变量NSDateComponentUndefined表示没有值的日期分量。

let dateComponents = DateComponents(month: 3)
debugPrint("Enumeration", dateComponents.value(for: .hour))
    // "Enumeration" Optional(9223372036854775807)
debugPrint("NSDateComponentUndefined", NSDateComponentUndefined)
    // "NSDateComponentUndefined" 9223372036854775807
Run Code Online (Sandbox Code Playgroud)

API参考:Foundation-> NSDateComponents中,我们读取:

...

一个NSDateComponents对象不要求定义所有组件字段。NSDateComponents创建的新实例时,日期组件将设置为NSDateComponentUndefined

因此,尝试调用尚未初始化value(...)Calendar.Component成员的返回DateComponents不是乱码,而是默认的未定义值NSDateComponentUndefinedint发生在return /处为的全局属性Int64.max = 9223372036854775807)。


为了更深入地了解这一点,我们可以访问DateComponentsswift-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]))
    }
}
Run Code Online (Sandbox Code Playgroud)

_handle包装上的属性NSDateComponents

internal var _handle: _MutableHandle<NSDateComponents>
Run Code Online (Sandbox Code Playgroud)

_MutableHandleswift-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)
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

value(...)上面的签名ReturnType推断为Int?,并将_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
}
Run Code Online (Sandbox Code Playgroud)

因此,whatToDo在的返回正文中,value(...)可以解读为等效于

NSDateComponents.value(forComponent: NSCalendar.Unit)
Run Code Online (Sandbox Code Playgroud)

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

如果最后返回到的源DateComponents,我们将看到以下私有获取器和设置器处理了NSDateComponentUndefined和之间的“桥接” 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 } }
Run Code Online (Sandbox Code Playgroud)