Swift中如何持续更新常量值?加载器无限循环问题求解
在Swift里,用let声明的常量是天生不可变的——一旦完成初始化,就再也没法修改它的值。如果你的需求是需要一个能持续更新、但又想限制外部修改权限的“类常量”,可以试试这些替代方案:
- 私有变量+只读计算属性:把可变的逻辑藏在内部,只给外部暴露只读的接口,既满足内部更新需求,又能避免外部随意修改:
class ImageLoader { private var _loadingCount: Int = 0 // 外部只能读,不能改 var isLoading: Bool { return _loadingCount > 0 } func imageStartedLoading() { _loadingCount += 1 } func imageFinishedLoading() { _loadingCount -= 1 } } - 属性包装器(Property Wrapper):如果需要更复杂的更新逻辑(比如更新时触发通知),可以自定义属性包装器,把“可更新但受控制”的逻辑封装起来,用起来像常量但内部能修改。
划重点:Swift的let设计就是为了保证值的不可变性,没有直接修改常量的“黑科技”,别硬钻牛角尖,用var配合逻辑控制才是正道。
你的代码问题太明显了——loadingImages()里无条件递归调用自己!不管conditions是true还是false,只要conditions不是nil,就会再次调用loadingImages(),直接把UI线程卡死,根本没机会检测图片是否真的加载完成。
给你两个靠谱的解决方案,按需选:
方案一:定时轮询检测(简单易上手)
用GCD定时器每隔一小段时间检查一次条件,避免同步递归阻塞线程:
var checkTimer: Timer? func startLoadingCheck() { loader.startLoader(view: view) // 每隔0.1秒检查一次图片状态,时间间隔可以根据需求调整 checkTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] timer in guard let self = self else { timer.invalidate() return } // 检查所有按钮的图片是否加载完成 let allImagesLoaded = [self.button1, self.button2, self.button3, self.button4, self.button5, self.button6, self.button7, self.button8, self.button9] .allSatisfy { $0.currentImage != nil } if allImagesLoaded { // 所有图片加载完成,停止Loader和定时器 self.loader.stop() timer.invalidate() self.checkTimer = nil } } }
调用的时候直接用startLoadingCheck()代替原来的loadingImages()就行,这样不会卡UI,每次定时器触发都会检查状态,一旦全部加载完成就立即停止Loader。
方案二:监听图片加载回调(更高效)
如果你的图片是异步加载的(比如网络图片),别用轮询,直接给每个图片的加载任务加完成回调,等最后一张加载完就停Loader:
func loadAllImages() { loader.startLoader(view: view) var loadedImageCount = 0 let totalImages = 9 // 对应你的9个按钮 // 检查是否全部加载完成的逻辑 func checkCompletion() { if loadedImageCount == totalImages { loader.stop() } } // 封装图片加载方法,带完成回调 func loadImage(for button: UIButton, with url: URL) { URLSession.shared.dataTask(with: url) { data, _, error in DispatchQueue.main.async { if let data = data, let image = UIImage(data: data) { button.setImage(image, for: .normal) } // 不管成功失败都计数,避免无限等待 loadedImageCount += 1 checkCompletion() } }.resume() } // 加载每个按钮的图片 loadImage(for: button1, with: yourImageURL1) loadImage(for: button2, with: yourImageURL2) // ... 剩下的按钮依次调用 }
这种方式不需要轮询,图片加载完成后主动通知,响应更快,也不浪费资源,是最优解。
为啥你的原代码会无限循环?
原代码里,if let cond = conditions只要conditions不是nil就会执行,而你每次调用loadingImages()都会给conditions赋值(不管是true还是false),所以永远会进入这个分支,递归调用自己,根本走不到else分支去停止Loader,直接导致栈溢出或者UI卡死。
内容的提问来源于stack exchange,提问作者Cristian

