Получить дату и время сборки в Swift
Я использую __DATE__
а также __TIME__
в Objective-C, чтобы получить дату и время сборки моего приложения. Я не могу найти способ получить эту информацию в Swift. Является ли это возможным?
7 ответов
Ты можешь использовать #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())
Вы можете получить дату и время сборки, не возвращаясь к target-C. Когда приложение собрано, файл Info.plist, помещенный в пакет, всегда создается из файла в вашем проекте. Таким образом, дата создания этого файла соответствует дате и времени сборки. Вы всегда можете прочитать файлы в комплекте вашего приложения и получить их атрибуты. Таким образом, вы можете получить дату сборки в Swift, обратившись к его атрибутам файла Info.plist:
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" решение, поэтому я решил поделиться им для дальнейшего использования.
[EDIT] добавил переменную 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()
}
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).
Ему не нужен старый
__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;
}
Опираться на дату создания Info.plist не получится. Полученный результат в некоторых сценариях может быть отметкой даты и времени установки приложения на ваш компьютер, что на самом деле и произошло со мной.
Вот мои две мысли:
Использовать
contentModificationDateKey
вместо. Тем не менее, это может быть ненадежно, если кто-то скопирует его на том FAT или NTFS, испортив информацию о временной метке.Найдите метод получения значения CFDate
kseccodeinfotimestamp
. Это невозможно изменить. См. следующий пример:
(Он вернет ноль, если он не подписан приложением Apple Developer ID и т. д. Специальные подписи также позволят ему выдать ноль.)
// (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)
}