iOS开发弱和强烈的舞蹈:它如何打破周期参考?
#ios #swift #objectivec

iOS development weak and strong dance cover

本文将尝试描述捕获时的周期参考和闭合的行为。一旦我们理解了这些事情,我们将继续如何打破导致内存泄漏的周期参考。

本文将使用简单的语言来描述事物,并且不会引入复杂的单词。目的是了解正在发生的事情,而不是对深度的技术复杂性和术语进行漂移。

我们不会在此处使用逃避关闭。您可以推断出它在逃脱关闭方面的工作方式。确定使用弱舞蹈的需求取决于您的上下文,并且本文不会涵盖。如果您有兴趣,可以阅读You don’t (always) need [weak self] | by Besher Al Maleh | Medium

什么是周期参考?

是一个实例A拥有实例B,而实例B拥有实例A。因此,创建周期参考。

为什么周期参考不好?

因为当持有实例的变量强烈变为零时,实例A仍然活着。虽然它仍然活在记忆中,但我们无法访问或销毁它。如果我们不断创建它的实例,它们最终将堆叠在内存中,从而导致内存泄漏。

这是一个示例:

class Person {
  var dog: Dog?
  func adoptAdog(_ dog: Dog) {
    self.dog = dog
    dog.owner = self // cycle reference!
  }
}
class Dog {
  var owner: Person?
}

func simulateAdoptingAdog() {
  let person = Person()
  let dog = Dog()
  person.adoptAdog(dog)
}

simulateAdoptingAdog() // Instances in memory: Person=1, Dog=1
simulateAdoptingAdog() // Instances in memory: Person=2, Dog=2
simulateAdoptingAdog() // Instances in memory: Person=3, Dog=3

您注意到,在 simulateadoptingadog 方法中,我们创建了一个人和狗实例,并使用了引起周期参考的 indemadog 方法。这两个实例互相参考。

我们可以使用弱属性避免这种情况:

class Dog {
  weak var owner: Person?
}

现在,一旦分配了狗的实例,就不会强烈持有实例。

在本文中,我们将讨论封闭引入的周期参考。在人和狗课上,我们可以清楚地看到有关如何引入周期参考的流程。但是,封闭是隐式的。通过了解关闭的行为,我们将知道如何避免循环参考。

什么是软弱而强壮的舞蹈?

虚弱而强壮的舞蹈获得了名字,因为开发人员经常使用它。如果您不想考虑封闭中的复杂自行车参考的复杂性,则开发人员只使用它。这是一个实例被封闭薄弱捕获的情况,并且将强烈持有关闭实施的局部变量。有时,您可以在Objective-C代码上看到它们,例如 strontify 宏。在Swift中,我们这样做:

let someClosure = { [weak self] in
  guard let self = self else { return }
  // code here
}

使用弱舞的具体原因是什么?

至少有两个原因:

  • 打破周期参考。

  • 保持实例的活力,直到实现封闭完成为止。

我们如何判断关闭的实施是否已经完成?

实施可能结束的原因有很多。我只会在这里提及三个基本原因:

  • 最后一行的代码已经完成

  • 返回关键字称为

  • 投掷关键字称为

let someClosure = { [weak self] in
  guard let self = self else { 
    return // will end the implmentation
  }

  guard !self.name.isEmpty else {
    throw SomeError.nameEmpty // will end the implementation
  }

  print(self.name)
  ... // some lines of code here
  print(self.name) // after this line is executed, the implementation is considered finished
}

我们如何判断关闭是否引入了周期参考?

是关闭内部使用的所有者,从而捕获所有者的实例并强烈地保持。

class Person {
  lazy var goToWorkClosure = {
    print("pay the bus")
    self.rideTheBus() // cycle reference!
  }
  func rideTheBus() {
    print("ride the bus")
  }
}

var person: Person? = Person()
person?.goToWorkClosure()  // [1]
person = nil // [2]
  • [1]â执行此代码线后,将创建闭合实例。 GotoWorkcluse的实例属性强烈引用了关闭。因此,封闭实例归人实例所有。而人实例也归封闭。从而创建一个参考周期。

  • [2]人的实例不会被破坏。如果没有被摧毁,封闭也不会被销毁。

为什么关闭会强烈捕捉实例?

因为我们通过调用 self.rideabus 。。。

一个简单的解释是因为默认情况下关闭,请确保其实现内部使用的外部变量实例已经存在。因此,一旦执行,它仍然可以使用这些实例。因此,它强烈捕获了这些实例。

如何修改此默认行为?

我们可以通过明确定义捕获列表来修改此行为。通常,闭合隐式捕获清单,并具有很强的参考。

使用捕获列表,我们可以告诉它易于捕获某个变量:

class Person {
  lazy var goToWorkClosure = { [weak self]
    print("pay the bus")
    self?.rideTheBus()
  }
  func rideTheBus() {
    print("ride the bus")
  }
}

var person: Person? = Person()
person?.goToWorkClosure()  // [1]
person = nil // [2]
  • [1] - 调用此方法时不会引入参考周期

  • [2] - 人实例将与其保留的实例一起销毁(在这种情况下,它仅容纳闭合实例)

这将打破周期参考,类似于我与人类和狗课引入的第一个示例。

您会注意到我们已经使用了 self ?当调用 ridebuss 方法时。这是因为 self 可以随时无所作为。如果当时自我为零,则ridethebus不会执行。

尽管 self?.ridethebus()代码线将永远不会发生的地点,因为一旦自我被交易或销毁,这种封闭就会立即被销毁。

但是,如果您在另一个线程上分发了闭合,使其异步运行,则该线程将拥有该线程,直到实现完成为止。如果闭合包含[弱自我],它将执行 self?.ridethebus()。我的意思是执行此代码行是在调用 ridethebus 方法之前首先检查是否存在。如果没有,该方法将不会执行。

使用 [弱自我] ,闭合将弱捕获自我。但是,这是安全的,使用自我的实施中的其他代码不能保证它们都会运行。曾经有一段时间,您的代码的前半部分使用自我完美地运行,但是下半场不会。为什么?因为当时自我被摧毁了。

在执行实施时,我们如何确保自我还活着?

我们使用弱强大的舞蹈!闭合捕获自我微弱,而实施的局部变量则坚强地保持自我。

class Person {
  lazy var goToWorkClosure = { [weak self]
    guard let self = self else { return }
    print("pay the bus")
    self?.rideTheBus()
  }
  func rideTheBus() {
    print("ride the bus")
  }
}

var person: Person? = Person()
person?.goToWorkClosure()
person = nil

请注意, self 请参阅实例。实现是指关闭的 {} 内部的代码。实施完成后,所有本地变量将被破坏。因此,在实例上释放了保留。

结论

了解软弱和自我舞蹈的行为可能是一个挑战。您必须将自动参考计数(ARC)视为先决条件和封闭行为。由于这里没有提及ARC,因此我试图解释初学者会理解的弱舞。

如果您不是初学者,并且已经了解弧线,那么您也可以在这里学到一些东西。您的想法可能会有一些疑问,您现在已经忽略了很多时间。阅读本文可能会有所帮助,或者可能会导致您提出更多问题。

随后,我可能会在接下来的几周内创建另一个,并提供更详细的示例和解释。