Crash on Timer Callback in Swift: closure #1 in ViewController.updateTimer() Causes App to Crash

46 views Asked by At

I have a timer in my app that updates the current time on the UI and checks against a set time to trigger an event. However, my app crashes when the timer fires. The crash logs point to ViewController.TimeUp() and closure #1 in ViewController.updateTimer(). Here's a simplified version of the stack trace and related code:

Crashed: com.apple.main-thread 0 EarAlarm 0x3677c AlarmViewController.TimeUp() + 4375193468 (:4375193468) 1 EarAlarm 0x297e8 closure #1 in AlarmViewController.updateCurrentTime() + 971 (AlarmViewController.swift:971) 2 EarAlarm 0x4dd98 thunk for @escaping @callee_guaranteed () -> () + 4375289240 (:4375289240) 3 libdispatch.dylib 0x26a8 + 32 4 libdispatch.dylib 0x4300 + 20 5 libdispatch.dylib 0x12998 + 984 6 libdispatch.dylib 0x125b0 _dispatch_main_queue_callback_4CF + 44 7 CoreFoundation 0x36f9c + 16 8 CoreFoundation 0x33ca8 + 1996 9 CoreFoundation 0x333f8 CFRunLoopRunSpecific + 608 10 GraphicsServices 0x34f8 GSEventRunModal + 164 11 UIKitCore 0x22c8a0 + 888 12 UIKitCore 0x22bedc UIApplicationMain + 340 13 EarAlarm 0x40bfc main + 22 (AppDelegate.swift:22) 14 ??? 0x1aec46dcc (シンボルが不足しています)`

Related code snippet:

import UIKit
import AVFoundation
import MediaPlayer
import AudioToolbox
import UserNotifications
import FirebaseCore
import GoogleMobileAds
import FirebaseAnalytics

class AlarmViewController: UIViewController, UNUserNotificationCenterDelegate {

    let userDefaults = UserDefaults.standard
    var appDelegate:AppDelegate = UIApplication.shared.delegate as! AppDelegate
    
    @IBOutlet weak var bannerView: GADBannerView!
    @IBOutlet weak var bannerViewBig: GADBannerView!
    var heightConstraint : NSLayoutConstraint?
    
    var MPplayer: MPMusicPlayerController!
    var MPplayerState: MPMusicPlaybackState!
    
    var displayTimer: Timer?
    var timer: Timer?
    var snoozeTimer: Timer?
    var vibrationTimer: Timer?
    var SlowlyVolumeTimer: Timer?
    var AudioPlayerSlowlyVolumeTimer: Timer?
    var currentTime: String?
    var df = DateFormatter()
    var hourFormat = DateFormatter()
    var minuteFormat = DateFormatter()
    var nowHourStr : String?
    var nowMinuteStr : String?
    var nowHourInt : Int?
    var nowMinuteInt : Int?
    var setHour : Int?
    var setMinute : Int?
    var residueTime : Int?
    var residueSeconds : Int?
    let audioSession = AVAudioSession.sharedInstance()
    var mpVolumeSlider = UISlider()
    var SaveUserVolume : Float!
    var MPplayerPlayingInt : Int!
    var longPressTimer: Timer?
    var slowlyInt : Int!
    var slowlyFlo : Float!
    var CurrentVolumeFlo : Float!
    var SavedVolumeFlo : Float!
    var CurrentSaveFlo : Float!
    var SlowlyVolumeChangeFlo : Float!
    var audioPlayeSlowlyVolumeChangeFlo : Float!
    var audioPlayer:AVAudioPlayer!
    var IntroductionPlayer:AVAudioPlayer!
    var iPodSongTitle : String? = nil
    var iPodUrl : NSURL? = nil
    var SongTitle : String? = nil
    var snoozeInt : Int!
    var snoozeSetTimeSec : Int!
    var snoozeTimeSec : Int!
    var snoozeDisplaySec : Int!
    var snoozeDisplayMin : Int!
    var vibrationCount = 5
    var introductionStopTimeSecInt : Int!
    var nowIntroductionStopTimeSecInt : Int!
    var notificationContent = UNMutableNotificationContent()
    var alreadyTimeUp : Bool!
    var AlarmTitleKey: String!
    var iPodAlarmTitleKey: String!
    var UseiPodKey: String!
    var AlarmVolumeKey: String!
    var iPodAlarmURLKey: String!
    var slowlyKey: String!
    var SnoozeKey: String!
    var VibKey: String!
    var IntroductionTitleKey: String!
    var iPodIntroductionTitleKey: String!
    var IntroductionUseiPodKey: String!
    var IntroductionVolumeKey: String!
    var IntroductionStopTimeKey: String!
    var iPodIntroductionURLKey: String!
    var iPodIntroductionAlbumArtDataKey: String!
    @IBOutlet var NowTimeLabel: UILabel!
    @IBOutlet var SetTimeLabel: UILabel!
    @IBOutlet var niAlarmLabel: UILabel!
    @IBOutlet var systemVolumeLabel: UILabel!
    @IBOutlet var volumeParentView: UIView!
    @IBOutlet var slider: UISlider!
    @IBOutlet weak var UpMainVolumeBtn: UIButton!
    @IBOutlet weak var DownMainVolumeBtn: UIButton!
    @IBOutlet var SnoozeButton: UIButton!
    @IBOutlet var HaikeiImageView: UIImageView!
    @IBOutlet weak var nativeAdPlaceholder: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        setupVolumeSlider()
    
        do {
            try audioSession.setActive(true)
        } catch {
            do {
                try audioSession.setActive(true)
            } catch {
                do {
                    try audioSession.setActive(true)
                } catch {
                    do {
                        try audioSession.setActive(true)
                    } catch {
                        do {
                            try audioSession.setActive(true)
                        } catch {
                            do {
                                try audioSession.setActive(true)
                            } catch {
                                do {
                                    try audioSession.setActive(true)
                                } catch {
                                    do {
                                        try audioSession.setActive(true)
                                    } catch {
                                        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {//1秒後に処理する
                                            //ここに処理
                                            do {
                                                try self.audioSession.setActive(true)
                                            } catch {
                                                print("audioSession ラストエラー")
                                                self.dismiss(animated: true, completion: nil)
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    
        let KillNotificationCenter = NotificationCenter.default
        KillNotificationCenter.addObserver(
            self,
            selector: #selector(SyuuryouKeikoku),
            name:UIApplication.willTerminateNotification,
            object: nil)
    
        SnoozeButton.isHidden = true
        SnoozeButton.isEnabled = false
    
        niAlarmLabel.text = ""
    
        setTimer()
    
        let longPress5Up = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressUpMainVolume(gesture:)))
        longPress5Up.minimumPressDuration = 0.3
        UpMainVolumeBtn.addGestureRecognizer(longPress5Up)
    
        let longPress1Up = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressDownMainVolume(gesture:)))
        longPress1Up.minimumPressDuration = 0.3
        DownMainVolumeBtn.addGestureRecognizer(longPress1Up)
    
        nativeAdPlaceholder.alpha = 0
    
        prepareIntroductionMuonAudioPlayer()
    
        IntroductionPlayer.play()
    
        admob()
        admobBig()
    }
    func getFileURL(fileName: String) -> URL {
        let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        return docDir.appendingPathComponent(fileName)
    }
    func setTimer() {
        setHour = appDelegate.SettingAlarmTimeHour
        setMinute = appDelegate.SettingAlarmTimeMiunte
        SetTimeLabel.text = "\(String(format: "%02d", setHour!)) : \(String(format: "%02d", setMinute!))"
        self.userDefaults.set(appDelegate.AlarmNumber, forKey: "savedAlarmNumber")
        self.userDefaults.set(setHour, forKey: "savedAlarmHourInsurance")
        self.userDefaults.set(setMinute, forKey: "savedAlarmMinuteInsurance")
        nowIntroductionStopTimeSecInt = 0
        introductionStopTimeSecInt = userDefaults.integer(forKey: IntroductionStopTimeKey)
        self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCurrentTime), userInfo: nil, repeats: true)
        displayNowTime()
        displayTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateNowTimeLabel), userInfo: nil, repeats: true)
        AtoHowManyMinutesLater()
        if residueTime! > 1434
        {
        } else
        {
            setTrigger()
        }
    }
    @objc private func updateCurrentTime(){
        df.locale = Locale(identifier: "en_US_POSIX")
        hourFormat.locale = Locale(identifier: "en_US_POSIX")
        minuteFormat.locale = Locale(identifier: "en_US_POSIX")
        df.dateFormat = "HH:mm:ss"
        hourFormat.dateFormat = "HH"
        minuteFormat.dateFormat = "mm"
        df.timeZone = TimeZone.current
        hourFormat.timeZone = TimeZone.current
        minuteFormat.timeZone = TimeZone.current
        let timezoneDate = df.string(from: Date())
        let timezoneDateH = hourFormat.string(from: Date())
        let timezoneDateM = minuteFormat.string(from: Date())
        currentTime = timezoneDate
        nowHourStr = timezoneDateH
        nowMinuteStr = timezoneDateM
        nowHourInt = Int(nowHourStr!)
        nowMinuteInt = Int(nowMinuteStr!)
    
        NowTimeLabel.text = currentTime
    
        AtoHowManyMinutesLater()
    
        nowIntroductionStopTimeSecInt = nowIntroductionStopTimeSecInt + 1
        if nowIntroductionStopTimeSecInt == introductionStopTimeSecInt {
            IntroductionPlayer.volume = 0
        }
    
        if(nowHourInt == setHour && nowMinuteInt == setMinute){
            if timer!.isValid {
                timer?.invalidate()
            }
            if alreadyTimeUp == true {
            } else {
                DispatchQueue.main.async { [weak self] in
                      guard let self = self else { return }
                      self.TimeUp()
                }
            }
        }
    }
    
    func AtoHowManyMinutesLater(){
        var residueTimeHour = 0
        var residueTimeMinute = 0
        let setAlarmMinute = setHour! * 60  + setMinute!
        let nowTimeMinute = nowHourInt! * 60 + nowMinuteInt!
        residueTime = setAlarmMinute - nowTimeMinute
        if residueTime! > 0
        {
            if residueTime! < 60
            {
                residueTimeMinute = residueTime!
            } else if residueTime! >= 60
            {
                residueTimeHour = residueTime! / 60
                residueTimeMinute = residueTime! % 60
            }
        } else if residueTime! < 0
        {
            residueTime = setAlarmMinute + 1440 - nowTimeMinute
            if residueTime! < 60
            {
            } else if residueTime! >= 60
            {
                residueTimeHour = residueTime! / 60
                residueTimeMinute = residueTime! % 60
            }
        }
        let hourSt = NSLocalizedString("時間", comment: "")
        let minutesSt = NSLocalizedString("分", comment: "")
        let goSt = NSLocalizedString("後", comment: "")
        if residueTimeHour <= 0
        {
            if residueTimeMinute > 1
            {
                niAlarmLabel.text = "\(residueTimeMinute)\(minutesSt)\(goSt)"//分後
            } else if residueTimeMinute <= 1
            {
                niAlarmLabel.text = NSLocalizedString("1分以内", comment: "")
            }
        }
        else if residueTimeHour > 0
        {
            niAlarmLabel.text = "\(residueTimeHour)\(hourSt)\(residueTimeMinute)\(minutesSt)\(goSt)"//時間 分後
        }
    }
    
    @objc private func updateNowTimeLabel(){
        displayNowTime()
    
    }
    
    func displayNowTime() {
        df.locale = Locale(identifier: "en_US_POSIX")
        hourFormat.locale = Locale(identifier: "en_US_POSIX")
        minuteFormat.locale = Locale(identifier: "en_US_POSIX")
        df.dateFormat = "HH:mm:ss"
        hourFormat.dateFormat = "HH"
        minuteFormat.dateFormat = "mm"
        df.timeZone = TimeZone.current
        hourFormat.timeZone = TimeZone.current
        minuteFormat.timeZone = TimeZone.current
        let timezoneDate = df.string(from: Date())
        let timezoneDateH = hourFormat.string(from: Date())
        let timezoneDateM = minuteFormat.string(from: Date())
        currentTime = timezoneDate
        NowTimeLabel.text = currentTime
        nowHourStr = timezoneDateH
        nowMinuteStr = timezoneDateM
        nowHourInt = Int(nowHourStr!)
        nowMinuteInt = Int(nowMinuteStr!)
    }
    func TimeUp() {
        if Thread.isMainThread {
            print("MainThreadです TimeUp")
        } else {
            print("MainThreadではない TimeUp")
        }
        alreadyTimeUp = true
        UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
        UNUserNotificationCenter.current().removeAllDeliveredNotifications()
        niAlarmLabel.text = ""
        if IntroductionPlayer == nil {
        } else {
            if(IntroductionPlayer.isPlaying) {
            IntroductionPlayer.stop()
            }
        }
        prepareAudioPlayer()
        SaveUserVolume = mpVolumeSlider.value
        let useiPod = userDefaults.integer(forKey: UseiPodKey)
        if useiPod == 2 {
        } else {
            MPplayer = MPMusicPlayerController.systemMusicPlayer
            MPplayerState = MPplayer.playbackState
            if MPplayerState == MPMusicPlaybackState.playing{
                MPplayer.pause()
                MPplayerPlayingInt = 1
            }
            if AVAudioSession.sharedInstance().isOtherAudioPlaying {
            }
        }
        let AlarmVolumeFlo = userDefaults.float(forKey: AlarmVolumeKey)
        slowlyInt = userDefaults.integer(forKey: slowlyKey)
        audioPlayer.volume = 0
        if slowlyInt == 0 {
            audioPlayer.volume = 1
            mpVolumeSlider.value = AlarmVolumeFlo
            slider?.value = self.mpVolumeSlider.value
        } else {
            slowlyFlo = Float(slowlyInt)
            audioPlayeSlowlyVolumeChangeFlo = 0
            if AlarmVolumeFlo > SaveUserVolume{
                CurrentVolumeFlo = SaveUserVolume
                SavedVolumeFlo = AlarmVolumeFlo
                CurrentSaveFlo = SavedVolumeFlo - CurrentVolumeFlo
                SlowlyVolumeChangeFlo = CurrentVolumeFlo
                SlowlyVolumeTimer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(SlowlyVolume), userInfo: nil, repeats: true)
            } else {
    
                mpVolumeSlider.value = AlarmVolumeFlo
    
                slider?.value = self.mpVolumeSlider.value
            }
            AudioPlayerSlowlyVolumeTimer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(AudioPlayerSlowlyVolume), userInfo: nil, repeats: true)
        }
    
        audioPlayer.play()
    
        let vibInt = userDefaults.integer(forKey: VibKey)
    
        if vibInt == 0 {
            vibrationTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(VibrationTimerAction), userInfo: nil, repeats: true)
        }
    
        push()
    
        snoozeSwitch()
    }

}

2024/3/31 update I previously asked a question about my app crashing when a timer fires, pointing to ViewController.TimeUp() and a closure in ViewController.updateTimer(). I received some helpful suggestions, which I've implemented, but my app still crashes. Below are the changes I made based on the recommendations:

Original code:

// Inside setTimer, lines 841 to 846
// Retrieving the set alarm time
setHour = appDelegate.SettingAlarmTimeHour
setMinute = appDelegate.SettingAlarmTimeMiunte
        
SetTimeLabel.text = "\(String(format: "%02d", setHour!)) : \(String(format: "%02d", setMinute!))"

Updated code:

// Invalidate any existing timer before creating a new one
timer?.invalidate()
guard let setHour = appDelegate.SettingAlarmTimeHour,
      let setMinute = appDelegate.SettingAlarmTimeMiunte else {
    return
}
self.setHour = setHour
self.setMinute = setMinute
SetTimeLabel.text = "\(String(format: "%02d", setHour)) : \(String(format: "%02d", setMinute))"

Original Timer scheduling:

// Inside setTimer
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCurrentTime), userInfo: nil, repeats: true)

Updated Timer scheduling:

// Using a closure-based timer to avoid strong reference cycles
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
    self?.updateCurrentTime()
}

Despite these changes, the app continues to crash with similar crash logs pointing to the same methods as before. I made sure to invalidate any previous timers before setting a new one and used [weak self] in closures to prevent strong reference cycles. However, the issue persists.

Questions:

Are there any additional steps I should consider to properly manage the timer and avoid crashes? Could there be another underlying issue with my implementation that I might be overlooking? I appreciate any further insight or suggestions that could help resolve this crash issue. Thank you in advance for your help.

0

There are 0 answers