ios Swift 管理内存

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27958012/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 04:21:55  来源:igfitidea点击:

Swift Managing Memory

iosswiftmemory-managementautomatic-ref-counting

提问by R Menke

This question was cleaned up and the important info moved to the answer below.

此问题已清理,重要信息移至下面的答案。



I have some questions about memory management.

我有一些关于内存管理的问题。

I am building a photo editing app. So keeping memory usage low is important. Also I am not going to post code because I do not have a big memory leak when doing one specific thing. I just lose a couple of KB's/MB's with everything that happens. And going over tens of thousands of lines of code to find kilobytes is no fun ;)

我正在构建一个照片编辑应用程序。所以保持低内存使用率很重要。此外,我不打算发布代码,因为在做一件特定的事情时我没有大的内存泄漏。我只是因为发生的一切而丢失了几个 KB/MB。遍历数万行代码来查找千字节并不好玩;)

my app uses core data, lots of cifilter stuff, location, and the basics.

我的应用程序使用核心数据、大量 cifilter 内容、位置和基础知识。

My first view is just a tableview which costs me about 5mb of memory. Then you take some photos, apply some filters, this gets saved to core data and then you go back to that first view.

我的第一个视图只是一个 tableview,它花费了我大约 5mb 的内存。然后你拍一些照片,应用一些过滤器,这会被保存到核心数据中,然后你回到第一个视图。

Is it possible to truly get rid of everything in memory except for the data needed to drive that first view. (that very save and awesome 5mb)

除了驱动第一个视图所需的数据外,是否有可能真正摆脱内存中的所有内容。(非常节省和很棒的 5mb)

Or will there always be something left behind, even if you set everything to nil?

或者即使您将所有内容都设置为零,总会留下一些东西?



Bonus Question:is there a difference in file size / cpu load between UIImageJPEGRepresentationand UIImagePNGRepresentation? I know you can set a compression quality with the JPEG method (harder on the cpu/gpu?).

额外问题:UIImageJPEGRepresentation和 之间的文件大小/cpu 负载是否存在差异UIImagePNGRepresentation?我知道您可以使用 JPEG 方法设置压缩质量(在 cpu/gpu 上更难?)。

Just trying to reduce memory pressure by all means possible.

只是想尽一切可能减少内存压力。



Update:

更新:

It was pointed out to me that the question might be too vague.

有人向我指出,这个问题可能太含糊了。

The problems I was having at some point or another were the following:

我在某些时候遇到的问题如下:

  • At some points peak memory usage is too high
  • Navigating to a second viewcontroller and back causes a leak
  • Editing an image causes a memory leak.
  • Applying a filter to more than 4-5 images causes a crash because of low memory, there were no more memory leaks at this point. (verified in instruments)
  • 在某些时候峰值内存使用率过高
  • 导航到第二个视图控制器并返回导致泄漏
  • 编辑图像会导致内存泄漏。
  • 由于内存不足,对超过 4-5 个图像应用过滤器会导致崩溃,此时没有更多的内存泄漏。(在仪器中验证)


P.s this was all tested on an iPhone 4s , not the simulator.

Ps 这都是在 iPhone 4s 上测试的,而不是模拟器。

There was a meme here to lighten the mood on this site a bit.

这里有一个模因可以稍微缓解这个网站的情绪。

回答by R Menke

This question has been open long enough and I now feel confident enough to answer it.

这个问题已经提出了足够长的时间,我现在有足够的信心来回答它。



Different Levels of MM:

不同级别的MM:

Hardware Memory

硬件内存

In Swift with ARCwe have no way to clean up the actual hardware ram. We can only make it possible for the OS to do that for us. One part is using the right code (optionalsand weak) the other part is creating time for the OS to do it's job.

在带有ARC 的Swift 中,我们无法清理实际的硬件内存。我们只能让操作系统为我们做这件事。一部分是使用正确的代码(optionalsweak),另一部分是为操作系统创造时间来完成它的工作。

Imagine we have a function that runs on all threads indefinitely. It does one thing, load an image, convert to black/white and save. All images max at a couple of mb's and the function creates no Software Memory Leaks. Because images don't have a set size and might have different compression they don't have the same footprint. This function will always crash your app.

想象一下,我们有一个无限期地在所有线程上运行的函数。它只做一件事,加载图像,转换为黑/白并保存。所有图像最大为几个 mb,并且该功能不会造成软件内存泄漏。因为图像没有固定大小并且可能有不同的压缩,所以它们没有相同的占用空间。此功能将始终使您的应用程序崩溃。

This “Hardware” Memory Leak is caused by the function always taking the next available slot of memory.

这种“硬件”内存泄漏是由于该函数总是占用下一个可用内存插槽造成的。

The OS does not step in to “actually clean the memory” because there is no idle time. Putting a delay between each pass completely fixes this.

操作系统不会介入“实际清理内存”,因为没有空闲时间。在每次通过之间设置延迟完全解决了这个问题。



Language specific MM

特定语言 MM

Casting

铸件

Some operations don't have an impact on memory, others do:

一些操作对内存没有影响,其他操作会:

let myInt : Int = 1
Float(myInt) // this creates a new instance

Try casting instead:

尝试投射:

(myInt as Float) // this will not create a new instance.


Reference Types vs Value Types | Classes vs Structs

引用类型与值类型 | 类与结构

Both have their advantages and their dangers.

两者都有其优点和危险。

Structsare memory intensive because they are Value Types. This means they copytheir values when assigned to another instance, effectively doubling memory usage. There is no fix / work around for this. It is what makes Structs Structs.

结构是内存密集型的,因为它们是值类型。这意味着它们在分配给另一个实例时会复制它们的值,从而有效地加倍内存使用量。对此没有修复/解决方法。这就是使 Structs 结构化的原因。

Classesdon't have this behaviour because they are Reference Types. They don't copy when assigned. Instead they create another referenceto the same object. ARCor Automatic Reference Countingis what keeps track of these references. Every Object has a reference counter. Each time you assign it, it goes up by one. Each time you set a reference to nil, the enclosing function ends, or the enclosing Object deinits, the counter goes down.

没有这种行为,因为它们是引用类型。他们在分配时不会复制。相反,它们创建了对同一对象的另一个引用ARC自动引用计数用于跟踪这些引用。每个对象都有一个引用计数器。每次分配它时,它都会增加一。每次将引用设置为 nil 时,封闭函数结束,或者封闭对象 deinits,计数器下降。

When the counter hits 0 the object is deinitialised.

当计数器达到 0 时,对象被取消初始化。

There is a way to prevent an instance from deinitialising, and thus creating a leak. This is called a Strong Reference Cycle.

有一种方法可以防止实例取消初始化,从而造成泄漏。这称为强引用循环

Good explanation of Weak

弱的很好的解释

class MyClass {

    var otherClass : MyOtherClass?

    deinit {
        print("deinit") // never gets called
    }
}

class MyOtherClass {

    var myclass : MyClass?

    deinit {
        print("deinit") // never gets called
    }
}

var classA : MyClass? = MyClass()

// sorry about the force unwrapping, don't do it like this
classA!.otherClass = MyOtherClass()
classA!.otherClass!.myclass = classA // this looks silly but in some form this happens a lot

classA = nil
// neither the MyClass nor the MyOtherClass deinitialised and we no longer have a reference we can acces. Immortalitiy reached they have.

set one reference to weak

设置一个引用 weak

class MyOtherClass {

    weak var myclass : MyClass?

    deinit {
        print("deinit") // gets called
    }
}


inout

进出

Functions capture the values passed to them. But it is also possible to mark those values as inout. This allows you to change a Struct passed to a function without copying the Struct. This might save memory, depending on what you pass and what you do in the function.

函数捕获传递给它们的值。但也可以将这些值标记为 inout。这允许您更改传递给函数的 Struct,而无需复制该 Struct。这可能会节省内存,具体取决于您传递的内容以及您在函数中执行的操作。

It is also a nice way of having multiple return values without using tuples.

这也是一种在不使用元组的情况下获得多个返回值的好方法。

var myInt : Int = 0

// return with inout
func inoutTest(inout number: Int) {

    number += 5

}

inoutTest(&myInt)
print(myInt) // prints 5

// basic function with return creates a new instance which takes up it's own memory space
func addTest(number:Int) -> Int {

    return number + 5

}


Functional Programming

函数式编程

State is Value over Time

状态是随时间变化的价值

Functional programming is the counter part of Object Oriented programming. Functional programming uses Immutable state.

函数式编程是面向对象编程的对应部分。函数式编程使用不可变状态。

More on this here

更多关于这里

Object Oriented programming uses Objects that have changing/mutating states. Instead of creating a new value, the old values are updated.

面向对象编程使用具有变化/变异状态的对象。不是创建新值,而是更新旧值。

Functional Programming can use more memory.

函数式编程可以使用更多内存。

example on FP

FP 上的例子



Optionals

可选

Optionals allow you to set thing to nil. This will lower the reference count of Classes or deinitialise Structs. Setting things to nil is the easiest way to clean up memory. This goes hand in hand with ARC. Once you have set all references of a Class to nil it will deinit and free up memory.

Optionals 允许您将事物设置为 nil。这将降低类的引用计数或取消初始化结构。将东西设置为 nil 是清理内存的最简单方法。这与ARC密切相关。一旦您将类的所有引用设置为 nil,它将取消初始化并释放内存。

If you don't create an instance as an optional, the data will remain in memory until the enclosing function ends or the enclosing class deinits. You might not know when this will happen. Optionals give you control over what stays alive for how long.

如果您不创建一个可选的实例,数据将保留在内存中,直到封闭函数结束或封闭类 deinits。你可能不知道这什么时候会发生。Optionals 让你可以控制什么东西存活多久。



API MM

API MM

Many "memory leaks" are cause by Frameworksthat have a “clean up” function that you might not have called. A good example is UIGraphicsEndImageContext()The Context will stay in memory until this function is called. It does not clean up when the function that created the context ends, or when the image involved is set to nil.

许多“内存泄漏”是由具有您可能没有调用过的“清理”功能的框架引起的。一个很好的例子是UIGraphicsEndImageContext()上下文将保留在内存中,直到调用此函数。当创建上下文的函数结束时,或者当涉及的图像设置为 nil 时,它不会清理。

Another good example is dismissing ViewControllers. It might make sense to segue to one VC and then segue back, but the segue actually creates a VC. A segue back does not destroy a VC. Call dismissViewControllerAnimated()to remove it from memory.

另一个很好的例子是关闭 ViewControllers。转至一个 VC 然后转回可能是有意义的,但转场实际上创建了一个 VC。segue back 不会破坏 VC。调用dismissViewControllerAnimated()以将其从内存中删除。

Read the Class References and double check there are no “clean up” functions.

阅读类参考并仔细检查没有“清理”功能。



If you do need Instruments to find a leak, check out the other answer on this question.

如果您确实需要 Instruments 来查找泄漏,请查看此问题的其他答案。

回答by MoralCode

enter image description here

在此处输入图片说明

click on your apps name in the top-right corner of Xcode.

单击 Xcode 右上角的应用名称。

enter image description here

在此处输入图片说明

click on 'edit scheme' in the menu that pops up.

在弹出的菜单中单击“编辑方案”。

enter image description here

在此处输入图片说明

make sure 'RUN' is selected on the left side, then click the diagnostics tab near the top of the window.

确保在左侧选择了“运行”,然后单击窗口顶部附近的诊断选项卡。

under the 'memory management' header check the 'enable Guard Malloc'

在“内存管理”标题下检查“启用 Guard Malloc”

you may also want to try checking 'distributed objects' and 'malloc stack' under the 'logging' header

您可能还想尝试检查“日志记录”标头下的“分布式对象”和“malloc 堆栈”

more info on guard malloc, guard edges and scribble can be found here.

可以在此处找到有关守卫 malloc、守卫边缘和涂鸦的更多信息。



hope this helps!



希望这可以帮助!