我在 Objective-C 中使用
__DATE__
和 __TIME__
来获取我的应用程序的构建日期和时间。 我找不到在 Swift 中获取此信息的方法。 可以吗?
您无需恢复到 Objective-C 即可获取构建日期和时间。 构建应用程序时,捆绑包中的 Info.plist 文件始终是根据项目中的文件创建的。 因此该文件的创建日期与构建日期和时间相匹配。 您始终可以读取应用程序包中的文件并获取它们的属性。 因此,您可以通过访问 Info.plist 文件属性来获取 Swift 中的构建日期:
var buildDate:NSDate
{
if let infoPath = NSBundle.mainBundle().pathForResource("Info.plist", ofType: nil),
let infoAttr = try? NSFileManager.defaultManager().attributesOfItemAtPath(infoPath),
let infoDate = infoAttr["NSFileCreationDate"] as? NSDate
{ return infoDate }
return NSDate()
}
注意:当我最初遇到这个问题时,这篇文章让我使用了桥接头。 从那时起我发现了这个“Swiftier”解决方案,所以我想我会分享它以供将来参考。
[编辑]添加了compileDate变量以获取最新的编译日期,即使不进行完整构建也是如此。 这仅在开发期间才有意义,因为您将必须进行完整的构建才能在应用程序商店上发布应用程序,但它可能仍然有一些用处。 它的工作方式相同,但使用包含实际代码的捆绑文件而不是 Info.plist 文件。
var compileDate:Date
{
let bundleName = Bundle.main.infoDictionary!["CFBundleName"] as? String ?? "Info.plist"
if let infoPath = Bundle.main.path(forResource: bundleName, ofType: nil),
let infoAttr = try? FileManager.default.attributesOfItem(atPath: infoPath),
let infoDate = infoAttr[FileAttributeKey.creationDate] as? Date
{ return infoDate }
return Date()
}
您可以使用
#line
、#column
和 #function
。
原答案:
在项目中创建一个新的 Objective-C 文件,当 Xcode 询问时,选择“是”以创建桥接标头。
在这个新的 Objective-C 文件中,添加以下
.h
文件:
NSString *compileDate();
NSString *compileTime();
并在
.m
中实现这些功能:
NSString *compileDate() {
return [NSString stringWithUTF8String:__DATE__];
}
NSString *compileTime() {
return [NSString stringWithUTF8String:__TIME__];
}
现在转到桥接标头并导入我们创建的
.h
。
现在回到您的任何 Swift 文件:
println(compileDate() + ", " + compileTime())
Alain T 的 Swift 5 版本答案:
var buildDate: Date {
if let infoPath = Bundle.main.path(forResource: "Info", ofType: "plist"),
let infoAttr = try? FileManager.default.attributesOfItem(atPath: infoPath),
let infoDate = infoAttr[.modificationDate] as? Date {
return infoDate
}
return Date()
}
向您的应用程序添加一个新的
Run Script
构建阶段,并确保将其设置为Compile Sources
阶段之前运行。
将此添加为该脚本中的代码:
#!/bin/bash
timestamp=$(date +%s)
echo "import Foundation;let appBuildDate: Date = Date(timeIntervalSince1970: $timestamp)" > ${PROJECT_DIR}/Path/To/Some/BuildTimestamp.swift
在项目中的某个路径创建文件
BuildTimestamp.swift
,然后确保上面脚本中的输出路径与该文件存在的位置(相对于项目的根文件夹)匹配。
在构建设置中,搜索“启用用户脚本沙箱”并将其关闭。 (否则,当脚本尝试运行时,您将收到权限错误。)
您现在拥有一个全局的
appBuildDate
,可以在项目中的任何位置使用。 (在使用变量之前构建一次项目,以便脚本在您指定的文件中创建它。)
可选:如果您希望在增量构建中更新日期,请务必取消选中您创建的运行脚本阶段中的“基于依赖性分析”复选框。
这是自动的。
它不会受到用户更改应用程序包中各种文件的修改/创建日期的影响(macOS 上的一个问题)。
它不需要来自 C 的旧
__TIME__
和 __DATE__
。
它已经是
Date
并且可以按原样使用。
与之前的答案略有不同,而是检查可执行文件的创建日期。这似乎也适用于 macOS(使用 Catalyst 应用程序进行了测试)。
/// Returns the build date of the app.
public static var buildDate: Date
{
if let executablePath = Bundle.main.executablePath,
let attributes = try? FileManager.default.attributesOfItem(atPath: executablePath),
let date = attributes[.creationDate] as? Date
{
return date
}
return Date()
}
这里所有旧的答案都不好,因为它们没有提供稳定可靠的方法来获取实际的构建日期。例如,获取应用程序内文件的文件日期并不好,因为文件日期可能会更改而不会使应用程序的代码签名无效。
官方构建日期由 Xcode 添加到应用程序的 Info.plist - 这就是您应该使用的日期。
例如,使用这段代码(抱歉,它是在 ObjC 中,但将其转录到 Swift 应该不会那么难):
+ (NSDate *)buildDate {
static NSDate *result = nil;
if (result == nil) {
NSDictionary *infoDictionary = NSBundle.mainBundle.infoDictionary;
NSString *s = [infoDictionary valueForKey:@"BuildDateString"];
NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init];
NSDate *d = [formatter dateFromString:s];
result = d;
}
return result;
}
这是您必须从项目的 Build Phases 运行的脚本,以便将
BuildDateString
添加到您的 Info.plist
:
#!/bin/sh
infoplist="$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH"
builddate=`date +%Y-%m-%dT%H:%M:%S%z`
if [[ -n "$builddate" ]]; then
# if BuildDateString doesn't exist, add it
/usr/libexec/PlistBuddy -c "Add :BuildDateString string $builddate" "${infoplist}"
# and if BuildDateString already existed, update it
/usr/libexec/PlistBuddy -c "Set :BuildDateString $builddate" "${infoplist}"
fi
依赖 Info.plist 的创建日期是行不通的。在某些情况下检索的结果可能是应用程序安装到您的计算机时的日期时间戳,这就是我实际发生的情况。
这是我的两个想法:
请使用
contentModificationDateKey
代替。尽管如此,如果将其复制到 FAT 或 NTFS 卷,这可能会不可靠,从而破坏时间戳信息。
找到获取
kseccodeinfotimestamp
的CFDate值的方法。这是不可篡改的。请参阅以下示例:
(如果没有经过 Apple Developer ID 应用程序等签名,它将返回 nil。临时签名也会让它抛出 nil。)
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
let url = URL.init(fileURLWithPath: "/Users/shikisuen/Library/Input Methods/vChewing.app/")
func getCodeSignedDate(bundleURL: URL) -> Date? {
var code: SecStaticCode?
var information: CFDictionary?
let status4Code = SecStaticCodeCreateWithPath(bundleURL as CFURL, SecCSFlags(rawValue: 0), &code)
guard status4Code == 0, let code = code else {
NSLog("Error from getCodeSignedDate(): Failed from retrieving status4Code.")
return nil
}
let status = SecCodeCopySigningInformation(code, SecCSFlags(rawValue: kSecCSSigningInformation), &information)
guard status == noErr else {
NSLog("Error from getCodeSignedDate(): Failed from retrieving code signing intelligence.")
return nil
}
guard let dictionary = information as? [String: NSObject] else { return nil }
guard dictionary[kSecCodeInfoIdentifier as String] != nil else {
NSLog("Error from getCodeSignedDate(): Target not signed.")
return nil
}
guard let infoDate = dictionary[kSecCodeInfoTimestamp as String] as? Date else {
NSLog("Error from getCodeSignedDate(): Target signing timestamp is missing.")
return nil
}
return infoDate as Date
}
if let infoDate = getCodeSignedDate(bundleURL: url) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd.HHmm"
dateFormatter.timeZone = .init(secondsFromGMT: +28800) ?? .current
let strDate = dateFormatter.string(from: infoDate)
print(strDate)
}