4

(这可能需要一个更好的标题......)

我想要一组可以在代码中使用的访问器来快速表达持续时间。例如:

42.seconds
3.14.minutes
0.5.hours
13.days

这篇文章说明了你不能只用一个简单的新协议、扩展和强制来做到这一点IntegerTypeFloatingPointType采用它。所以我想我会走更冗余的路线,直接扩展IntegerType(然后重复代码FloatingPointType)。

extension IntegerType {
    var seconds:NSTimeInterval {
        return NSTimeInterval(self)
    }
}

产生的错误令人困惑:

Playground execution failed: /var/folders/2k/6y8rslzn1m95gjpg534j7v8jzr03tz/T/./lldb/9325/playground131.swift:31:10: error: cannot invoke initializer for type 'NSTimeInterval' with an argument list of type '(Self)'
                return NSTimeInterval(self)
                       ^
/var/folders/2k/6y8rslzn1m95gjpg534j7v8jzr03tz/T/./lldb/9325/playground131.swift:31:10: note: overloads for 'NSTimeInterval' exist with these partially matching parameter lists: (Double), (UInt8), (Int8), (UInt16), (Int16), (UInt32), (Int32), (UInt64), (Int64), (UInt), (Int), (Float), (Float80), (String), (CGFloat), (NSNumber)
                return NSTimeInterval(self)

让我困惑的是,它似乎说我不能用 (Self) 做一个 NSTimeInterval() 初始化程序,但是 Self 表示的所有内容都列在下一行,它显示了 NSTimeInterval 的所有可能的初始化程序。我在这里想念什么?

旁白:如果有一篇写得很好的关于 Swift 类型系统和做这些事情的教程,我会很高兴的。Apple 稀疏的 Swift 文档没有很好地涵盖中级/高级的内容

更新/澄清

我想要的是能够评估上述任何表达式:

42.seconds   --> 42
3.14.minutes --> 188.4
0.5.hours    --> 1800
13.days      --> 1123200

此外,我希望这些的返回类型为 NSTimeInterval(Double 的类型别名),例如:

42.seconds is NSTimeInterval   --> true
3.14.minutes is NSTimeInterval --> true
0.5.hours is NSTimeInterval    --> true
13.days is NSTimeInterval      --> true

我知道我可以通过简单地扩展来实现这一点,Double因此Int

extension Int {
    var seconds:NSTimeInterval {
        return NSTimeInterval(self)
    }

    var minutes:NSTimeInterval {
        return NSTimeInterval(self * 60)
    }

    var hours:NSTimeInterval {
        return NSTimeInterval(self * 3600)
    }

    var days:NSTimeInterval {
        return NSTimeInterval(self * 3600 * 24)
    }
}

extension Double {
    var seconds:NSTimeInterval {
        return NSTimeInterval(self)
    }

    var minutes:NSTimeInterval {
        return NSTimeInterval(self * 60)
    }

    var hours:NSTimeInterval {
        return NSTimeInterval(self * 3600)
    }

    var days:NSTimeInterval {
        return NSTimeInterval(self * 3600 * 24)
    }
}

但我也希望以下表达式起作用:

let foo:Uint = 4242
foo.minutes                   --> 254520
foo.minutes is NSTimeInterval --> true

不过这行不通,因为我只扩展了Int,而不是UInt。我可以冗余地扩展Uint,然后UInt16,然后Int16,等等......

我想概括 to 的扩展,IntIntegerType原始清单所示,这样我就可以获得所有整数类型的一般转换。然后对FloatingPointType而不是专门做同样的事情Double。但是,这会产生原始错误。我想知道为什么我不能像通常所示的那样扩展 IntegerType。除了列表中显示的之外,是否还有其他IntegerType采用者使得 NSTimeInterval() 初始化程序无法解析?

4

3 回答 3

4

我可以冗余地扩展 Uint,然后是 UInt16,然后是 Int16,等等......

正确的。这就是今天在 Swift 中的做法。您不能声明一个协议符合扩展中的另一个协议。(“为什么?”“因为编译器不允许这样做。”)

但这并不意味着您必须重写所有实现。您目前必须实现它三次(如果您愿意,可以实现四次Float80,但这在这里似乎没有用)。首先,您声明您的协议。

import Foundation

// Declare the protocol
protocol TimeIntervalConvertible {
    func toTimeInterval() -> NSTimeInterval
}

// Add all the helpers you wanted
extension TimeIntervalConvertible {
    var seconds:NSTimeInterval {
        return NSTimeInterval(self.toTimeInterval())
    }

    var minutes:NSTimeInterval {
        return NSTimeInterval(self.toTimeInterval() * 60)
    }

    var hours:NSTimeInterval {
        return NSTimeInterval(self.toTimeInterval() * 3600)
    }

    var days:NSTimeInterval {
        return NSTimeInterval(self.toTimeInterval() * 3600 * 24)
    }
}

// Provide the implementations. FloatingPointType doesn't have an equivalent to
// toIntMax(). There's no toDouble() or toFloatMax(). Converting a Float to
// a Double injects data noise in a way that converting Int8 to IntMax does not.
extension Double {
    func toTimeInterval() -> NSTimeInterval { return NSTimeInterval(self) }
}

extension Float {
    func toTimeInterval() -> NSTimeInterval { return NSTimeInterval(self) }
}

extension IntegerType {
    func toTimeInterval() -> NSTimeInterval { return NSTimeInterval(self.toIntMax()) }
}

// And then we tell it that all the int types can get his implementation
extension Int: TimeIntervalConvertible {}
extension Int8: TimeIntervalConvertible {}
extension Int16: TimeIntervalConvertible {}
extension Int32: TimeIntervalConvertible {}
extension Int64: TimeIntervalConvertible {}
extension UInt: TimeIntervalConvertible {}
extension UInt8: TimeIntervalConvertible {}
extension UInt16: TimeIntervalConvertible {}
extension UInt32: TimeIntervalConvertible {}
extension UInt64: TimeIntervalConvertible {}

这就是目前 Swift 中数字类型的处理方式。查看标准库。你会看到很多东西,比如:

extension Double {
    public init(_ v: UInt8)
    public init(_ v: Int8)
    public init(_ v: UInt16)
    public init(_ v: Int16)
    public init(_ v: UInt32)
    public init(_ v: Int32)
    public init(_ v: UInt64)
    public init(_ v: Int64)
    public init(_ v: UInt)
    public init(_ v: Int)
}

在某些情况下谈论“类似数字的东西”会很好吗?当然。你今天不能在 Swift 中使用。

“为什么?”

因为编译器没有实现它。有朝一日可能会。在此之前,为您想要的每种类型创建扩展。一年前,这需要更多的代码。

请注意,虽然有些其中是“Swift 还没有那个功能”,有些也是故意的。Swift 有意要求数字类型之间的显式转换。数字类型之间的转换通常会导致信息丢失或注入噪音,并且在历史上一直是棘手错误的来源。每次转换数字时都应该考虑这一点。例如,从 Int64 到 Int8 或从 UInt8 到 Int8 的明显情况可能会丢失信息。但是从 Int64 到 Double 也会丢失信息。并非所有 64 位整数都可以表示为 Double。这是一个微妙的事实,在处理非常大的数字时经常会惹恼人们,Swift 鼓励你处理它。即使将 Float 转换为 Double 也会在数据中注入噪音。以浮点数表示的 1/10 与以双精度数表示的 1/10 是不同的值。当您将 Float 转换为 Double 时,您的意思是要扩展重复数字吗?根据您选择的不同,您将引入不同类型的错误,因此您需要选择。

另请注意,您.days可以根据确切的问题域引入细微的错误。一天并不总是 24 小时。根据 DST 的变化,它可以是 23 小时或 25 小时。有时这很重要。有时它不会。但是有一个理由要非常小心地将“天”视为特定的秒数。通常如果你想在几天内工作,你应该使用NSDate,而不是NSTimeInterval。我会非常怀疑那个特定的。

顺便说一句,您可能对我对这个想法的旧实​​现感兴趣。而不是使用语法:

1.seconds

我使用了以下语法:

1 * Second

然后重载乘法以返回 struct 而不是Double. 以这种方式返回结构可以提供更好的类型安全性。例如,我可以键入检查“时间 * 频率 == 周期”和“周期 / 时间 == 频率”,这是 Double 无法做到的。不幸的是 NSTimeInterval 不是一个单独的类型。它只是 Double 的另一个名称。因此,您在 NSTimeInterval 上放置的任何方法都会应用于每个 Double (这有时很奇怪)。

就个人而言,我可能会以这种方式解决整个问题:

let Second: NSTimeInterval = 1
let Seconds = Second
let Minute = 60 * Seconds
let Minutes = Minute
let Hour = 60 * Minutes
let Hours = Hour

let x = 100*Seconds

您甚至不需要重载运算符。它已经为你完成了。

于 2015-11-11T18:39:09.873 回答
1

NSTimeInterval只是Double. 您可以通过定义自己的结构轻松实现您想要的,如下所示:

struct MyTimeInterval {
    var totalSecs: NSTimeInterval

    var totalHours: NSTimeInterval {
        get {
            return self.totalSecs / 3600.0
        }
        set {
            self.totalSecs = newValue * 3600.0
        }
    }

    init(_ secs: NSTimeInterval) {
        self.totalSecs = secs
    }

    func totalHourString() -> String {
        return String(format: "%.2f hours", arguments: [self.totalHours])
    }
}

var t = MyTimeInterval(5400)
print(t.totalHourString())
于 2015-11-10T00:58:57.300 回答
0
extension Int {
    var seconds: Int {
            return self
        }
    var minutes: Int {
        get {
            return self * 60
        }
    }
    var hours: Int {
        get {
            return minutes * 60
        }
    }
    var timeString: String {
        get {
            let seconds = abs(self) % 60
            let minutes = ((abs(self) - seconds) / 60) % 60
            let hours = ((abs(self) - seconds) / 3660)
            let sign = self < 0 ? "-" :" "
            let str = String(format: "\(sign)%2d:%02d:%02d", arguments: [Int(hours),Int(minutes),Int(seconds)])
            return str
        }
    }
}

6.minutes == 360.seconds                    // true

let t1 = 1.hours + 22.minutes + 12.seconds
let t2 = 12.hours - 15.minutes
let time = t1 - t2

print("t1    =",t1.timeString,t1)             // t1    =   1:22:12 4932
print("t2    =",t2.timeString,t2)             // t2    =  11:45:00 42300
print("t1-t2 =",time.timeString,time)         // t1-t2 = -10:22:48 -37368

(12.hours / 30.minutes) == 24                 // true
于 2015-11-10T22:32:19.943 回答