Question:
What is the standard, most reliable way to manage temporary files associated with a URLSessionDownloadTask that has been terminated abnormally due to a network error or other issues?
Details
Hello,
I'm currently developing a feature to download multiple files concurrently on iOS using URLSessionDownloadTask, and I have a question regarding the lifecycle of the temporary files created during this process.
As I understand it, URLSessionDownloadTask stores incoming data in a temporary file within the tmp directory, typically with a name like CFNetworkDownload_*.tmp.
In my testing, temporary files are managed correctly in the normal scenario. For instance, when I call the cancel() method on an active downloadTask and then release all references to it, the corresponding temporary file is automatically cleaned up from the tmp directory shortly after.
However, the problem occurs when a download is interrupted abnormally due to external factors, such as a lost network connection. In this situation, the urlSession(_:task:didCompleteWithError:) delegate method is called, but the associated temporary file is not deleted and remains in the tmp directory.
I've observed a particularly interesting behavior related to this. Immediately after the error occurs, if I check my app's storage usage in the iOS Settings app, the data size appears to have decreased momentarily. However, the tmp file has not actually been deleted, and after a short while, the storage usage is recalculated to include the size of this orphaned temporary file.
Since my app does not support resuming interrupted downloads, these leftover files become orphaned and unnecessarily consume storage. Therefore, I want to ensure they are all reliably deleted.
With this context, I'd like to ask the community:
What is the standard, most reliable way to manage temporary files associated with a URLSessionDownloadTask that has been terminated abnormally due to a network error or other issues?
I am wondering if there is an official guide or a framework-level API to handle these orphaned files.
I would appreciate any advice from those with experience in this area. Thank you.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Hello everyone,
I've encountered a fascinating and perplexing rendering anomaly when using UIBezierPath(roundedRect:cornerRadius:) to create a CGPath.
Summary of the Issue:
When the shortest side of the rectangle (min(width, height)) is just under a certain multiple of the cornerRadius (empirically, around 3x), the algorithm for generating the path seems to change entirely. This results in a path with visually different (and larger) corners than when the side is slightly longer, even with the same cornerRadius parameter.
How to Reproduce:
The issue is most clearly observed with a fixed cornerRadius while slightly adjusting the rectangle's height or width across a specific threshold.
Create a UIView (contentView) and another UIView (shadowView) behind it.
Set the shadowView.layer.shadowPath using UIBezierPath(roundedRect: contentView.bounds, cornerRadius: 16).cgPath.
Adjust the height of the contentView.
Observe the shadowPath at height 48 vs. height 49
Minimal Reproducible Example:
Here is a simple UIViewController to demonstrate the issue. You can drop this into a project. Tapping the "Toggle Height" button will switch between the two states and print the resulting CGPath to the console.
import UIKit
class PathTestViewController: UIViewController {
private let contentView = UIView()
private let shadowView = UIView()
private var heightConstraint: NSLayoutConstraint!
private let cornerRadius: CGFloat = 16.0
private let normalHeight: CGFloat = 49
private let anomalyHeight: CGFloat = 48
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray5
setupViews()
setupButton()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
updateShadowPath()
}
private func updateShadowPath() {
let newPath = UIBezierPath(roundedRect: contentView.bounds, cornerRadius: cornerRadius).cgPath
shadowView.layer.shadowPath = newPath
}
private func setupViews() {
// ContentView (the visible rect)
contentView.backgroundColor = .systemBlue
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.isHidden = true
// ShadowView (to render the path)
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOpacity = 1
shadowView.layer.shadowRadius = 2
shadowView.layer.shadowOffset = .zero
shadowView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(shadowView)
view.addSubview(contentView)
heightConstraint = contentView.heightAnchor.constraint(equalToConstant: normalHeight)
NSLayoutConstraint.activate([
contentView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
contentView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
contentView.widthAnchor.constraint(equalToConstant: 300),
heightConstraint,
shadowView.topAnchor.constraint(equalTo: contentView.topAnchor),
shadowView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
shadowView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
shadowView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])
}
private func setupButton() {
let button = UIButton(type: .system, primaryAction: UIAction(title: "Toggle Height", handler: { [unowned self] _ in
let newHeight = self.heightConstraint.constant == self.normalHeight ? self.anomalyHeight : self.normalHeight
self.heightConstraint.constant = newHeight
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}))
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20)
])
}
}
Evidence: CGPath Analysis
Note: The CGPath data below is from my initial observation. At that time, height 48.7 produced a path with straight edges. Now, this "correct" path is only produced at height 49.0 or greater. The inconsistency now occurs at 48.7.*
The key difference lies in the raw CGPath data.
Path for Height = 48.7 (Expected Behavior)
The path is constructed with lineto commands for the straight edges between the curved corners.
// Path for Height 48.7
Path 0x60000300a0a0:
moveto (24.4586, 0)
lineto (24.5414, 0) // <-- Straight line on top edge
curveto (31.5841, 0) (35.1055, 0) (38.8961, 1.19858)
...
Path for Height = 48.6 (Anomalous Behavior)
The lineto commands for the short edges disappear. The path is composed of continuous curveto commands, as if the two corners have merged into a single, larger curve. This creates the visual discrepancy.
// Path for Height 48.6
Path 0x600003028630:
moveto (24.1667, 0)
lineto (24.1667, 0) // <-- Zero-length line
curveto (24.1667, 0) (24.1667, 0) (24.1667, 0)
lineto (25.375, 1.44329e-15)
curveto (34.8362, -2.77556e-16) (43.2871, 5.9174) (46.523, 14.808) // <-- First curve
curveto (48.3333, 20.5334) (48.3333, 25.8521) (48.3333, 36.4896) // <-- Second curve, no straight line in between
...
min.length == 48
min.length == 49
My Questions:
Is this change in the path-generation algorithm at this specific size/radius threshold an intended behavior, or is it a bug?
Is this behavior documented anywhere? The threshold doesn't seem to be a clean side/radius == 2.0, so it's hard to predict.
Is there a recommended workaround to ensure consistent corner rendering across these small size thresholds?
Any insight would be greatly appreciated. Thank you!
Environment:
Xcode: 16.4
iOS: 16.5.1(iPad), 18.4(iphone simulator)
When using the continuation API, we're required to call resume exactly once. While withCheckedContinuation helps catch runtime issues during debugging, I'm looking for ways to catch such errors at compile time or through tools like Instruments.
Is there any tool or technique that can help enforce or detect this requirement more strictly than runtime checks? Or would creating custom abstractions around Continuation be the only option to ensure safety? Any suggestions or best practices are appreciated.