События SwiftUI или Combine Clock / Timer - PullRequest
1 голос
/ 12 января 2020

Используя SwiftUI (или Combine), как мне настроить серию из одного или нескольких событий, которые запускаются (системными) часами. Примеры могут включать:

  • Каждую ночь в полночь,
  • В час,
  • Каждые пятнадцать минут в четверть часа,
  • Наконец, на немного другой ноте: 29 февраля 2020 года в 12: 15.

Приближение легко достигается установкой события таймера, которое срабатывает каждую секунду, а затем проверкой часов / минут / секунд и др. c. но это кажется очень неэффективным для событий, которые могут длиться много часов или дней.

Я ищу что-то, что близко синхронизировано с фактическими системными часами и запускает одно событие в нужное время, а не запускает множество событий, и каждый спрашивает: «Мы уже там?».

Ответы [ 2 ]

1 голос
/ 13 апреля 2020

Мне также пришлось реализовать эту функцию с помощью Combine / SwiftUI: таймер, который будет запускаться при запуске, а затем каждый день, час или минуты (для тестирования), вот мое решение, если оно может быть полезным или улучшено:)

class PeriodicPublisher {

    var periodicFormat: PeriodicFormat = .daily

    init(_ format: PeriodicFormat = .daily) {
        self.periodicFormat = format
    }

    // Must have an equatable for removeDuplicate
    struct OutputDate: Equatable {

        let compared: String
        let original: String

        init(_ comparedDatePart: String, _ originalDate: String) {
            self.compared = comparedDatePart
            self.original = originalDate
        }

        static func ==(lhs: OutputDate, rhs: OutputDate) -> Bool {
            return lhs.compared == rhs.compared
        }
    }

    enum PeriodicFormat {
        case daily
        case hourly
        case minutely

        func toComparableDate() -> String {
            switch self {
                case .daily:
                    return "yyyy-MM-dd"
                case .hourly:
                    return "HH"
                case .minutely:
                    return "mm"
            }
        }
    }

    func getPublisher() -> AnyPublisher<OutputDate, Never> {

        let compareDateFormatter = DateFormatter()
        compareDateFormatter.dateFormat = self.periodicFormat.toComparableDate()

        let originalTimerDateFormatter = DateFormatter()
        originalTimerDateFormatter.dateFormat = "yyyy-MM-dd HH:mm"

        var nowDate: Just<OutputDate> {

            let comparedDate = compareDateFormatter.string(from: Date())
            let originalDate = originalTimerDateFormatter.string(from: Date())

            return Just(OutputDate(comparedDate, originalDate))
        }

        let timerDate = Timer.publish(every: 2.0, tolerance: 1.0, on: .main, in: .default, options: nil)
            .autoconnect()
            .map { dateString -> OutputDate in
                return OutputDate(compareDateFormatter.string(from: dateString), originalTimerDateFormatter.string(from: dateString))
        }
        .eraseToAnyPublisher()

        return Publishers.Merge(nowDate, timerDate)
            .map { $0 }
            .removeDuplicates()
            .eraseToAnyPublisher()
    }
}

Как это работает?

Каждые 2 секунды планировщик выпускает текущую дату (с Timer.publi sh ()), эта дата используется для создания «OutputDate» содержит два свойства: одну «сопоставимую» часть, используемую для сравнения, если что-то изменилось, и одну «оригинальную» часть, поэтому она может быть полезна для потребителя.

Свойство Comparable - это дата таймера, отформатированная с toComparableDate с учетом предоставленной конфигурации (.daily, .hourly, .minutely). Использование «removeDuplicates» в этом свойстве позволяет публиковать sh «OutputDate» только при изменении этого значения. Каждый день, час или минуту.

Publishers.Merge используется для публикации sh значения сразу после создания экземпляра, в противном случае ничего не происходит до первого Timer.publi sh (каждый). Здесь 2 секунды.

Как это использовать?

Вы бы использовали это с Combine следующим образом:

PeriodicPublisher(.daily).getPublisher().sink { date in 
    print("Day has changed \(date.original)") 
}
1 голос
/ 13 января 2020

Я бы предложил следующее:

DispatchQueue.global(qos: .background).async {
    let isoDate = "2020-01-13T16:58:30+0000"

    let dateFormatter = ISO8601DateFormatter()
    let date = dateFormatter.date(from:isoDate)!
    let t = Timer(fire: date, interval: 2, repeats: true) { timer in
        print("fired")
    }
    let runLoop = RunLoop.current
    runLoop.add(t, forMode: .default)
    runLoop.run()
}

преобразование строки в дату Я использовал этот ответ для правильного форматирования времени.

Пример приведен в GMT.

документация apple вы можете посмотреть timer tolerance, который можно настроить, если вам нужно, чтобы таймер был очень точным.

interval в секундах, так что это решение не будет точнее, чем секунды

Возможно, вы захотите включить возможность фоновых режимов на go для очень длительных таймеров. Никогда не делал этого, поэтому я не могу помочь.

Все ваши примеры должны работать. Надеюсь, это поможет!

...