我对 Swift 和 iOS 开发还很陌生,在构建应用程序时遇到了问题。 在 ViewController 中,我有一个 ScrollView,嵌套在 UIView 中,其中两个 UIImageView 子级堆叠在一起。我使用背景 imageview 显示包含幻想地图的 jpg 图像,同时顶部 imageview 显示 png 图像(具有透明背景),其中绘制了代表某些角色旅程的路径。 在 ViewController 内部有一个垂直的 StackView,里面有一个 Label 和一个 UISlider。使用此滑块,用户可以选择一个日期,并且顶部图像视图应使用代表角色截至该日期的路径的正确图像进行更新。
以下是ViewController的类
`import UIKit
class MapViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var imagesView: UIView!
@IBOutlet weak var backgroundImageView: UIImageView!
@IBOutlet weak var foregroundImageView: UIImageView!
@IBOutlet weak var menuStackView: UIStackView!
@IBOutlet weak var coloredSwitch: UISwitch!
@IBOutlet weak var dateLabel: UILabel!
@IBOutlet weak var dateSlider: UISlider!
let mapPins = MapPins()
override func viewDidLoad() {
super.viewDidLoad()
menuStackView.layer.cornerRadius = 11.0
menuStackView.layer.masksToBounds = true
dateSlider.maximumValue = Float(mapPins.pinsLabelTexts.count)
scrollView.delegate = self
updateBackgroundMap(isColored: true)
}
func updateBackgroundMap(isColored: Bool) {
var backImage: UIImage? = nil
if isColored {
backImage = UIImage(named: "Mappa Antico Mondo - colori.jpg")
} else {
backImage = UIImage(named: "Mappa Antico Mondo - bn.jpg")
}
backgroundImageView.image = backImage
backgroundImageView.contentMode = .scaleAspectFit
backgroundImageView.frame.size = backImage!.size
}
func updateTopImage(imageName: String?) {
if imageName == nil {
foregroundImageView.image = nil
} else {
let image = UIImage(named: imageName!)
foregroundImageView.image = image
}
}
func updateCharactersPins() {
var index = Int(dateSlider.value)
if index > mapPins.pinsLabelTexts.count-1 {
index = mapPins.pinsLabelTexts.count-1
}
dateLabel.text = mapPins.pinsLabelTexts[index-1]
updateTopImage(imageName: mapPins.getPinsImageName(for: index-1))
}
@IBAction func toggleColors(_ sender: UISwitch) {
updateBackgroundMap(isColored: sender.isOn)
}
@IBAction func dateSliderChanged(_ sender: UISlider) {
updateCharactersPins()
}
}
extension MapViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imagesView
}
}`
有 104 个可能的“mapPins”(顶部图像视图的 png 图像),当我测试代码时,一切都工作“正常”,但我可以在调试器中看到,每次更改滑块时,内存使用量都会增加,并且在结束应用程序崩溃,因为它使用了太多内存。如果我在 Xcode 模拟器上使用该应用程序,则不会发生同样的事情。 此外,我尝试删除滑块并在 viewDidLoad() 方法中调用函数 updateCharactersPins() 104 次,以便重新创建与滑块一样多的函数调用(如果用户只是增加它),我发现内存使用情况仍然很好。因此,我的滑块处理函数调用的 @IBAction 似乎存在问题(可能在我的代码中)。 难道每个 UIImage 对象都保留在内存中吗?
首先,文件大小与内存使用量关系不大。
加载到内存中时,4096 x 3072 图像将使用 48 MB:
width * height * 4-bytes-per-pixel
4096.0 * 3072.0 * 4.0 / 1048576.0 == 48.0 MB
为了进行比较,我创建了三个 4096 x 3072 图像,另存为 PNG:
57 KB
61 KB
18.9 MB
这是山景图像的微型版本:
每个图像在加载到内存中时,都会使用
48 MB
其次,当使用
let img = UIImage(named: "sample")
加载图像时,iOS 缓存 内存中的图像,假设您想再次使用它们。
如果我们运行此代码(“r”==红色图像,“t”==透明图像,“m”==山图像):
let imgNames: [String] = [
"r4096x3072",
"t4096x3072",
"m4096x3072",
]
var i: Int = 0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let nm: String = imgNames[i % imgNames.count]
if let img = UIImage(named: nm) {
print("img:", nm, "size:", img.size)
self.imgView.image = img
}
i += 1
}
内存使用情况如下所示:
前三次点击加载下一张图像并将其设置为
imgView.image
——正如我们所见,内存不断增加。
我继续点击,重新加载图像...并且内存停止增加,因为iOS / UIKit在内存中缓存了所有三个图像。
正如我在评论中提到的,一般来说缓存足够智能,不会使内存过载...但是,如果有 104 个非常大的图像,并且快速加载它们,那么...我们会发生内存崩溃。
我们可以通过使用UIImage(contentsOfFile: path)
来防止
缓存。但是,图像需要作为资源存储在您的应用程序包中 - 我们无法从资产目录中以这种方式加载它们。所以项目文件可能如下所示:我们可以像这样改变加载:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let nm: String = imgNames[i % imgNames.count]
if let path = Bundle.main.path(forResource: nm, ofType: "png"),
let img = UIImage(contentsOfFile: path) {
print("img:", nm, "size:", img.size)
self.imgView.image = img
}
i += 1
}
现在,我们得到内存使用情况:我们只得到两次内存跳转,而不是三次(因为图像视图也保存一个副本)。
我使用 100 张
400 x 600
图像整理了一个简单的示例(这样我们就不会出现内存崩溃)。有一个灰色的“地图图像视图”和一个覆盖的图像视图,用于保存大部分透明的图像。它会自动运行,每 1/10 秒更新一次图像,以模拟您的“滑块拖动”。
看起来像这样(缩小了):
这是
UIImge(named:...)
的内存使用情况:并与
UIImage(contentsOfFile:...)
: