开始进行Swift编程,第7部分-初始化和反初始化,覆盖和引用计数

在上一篇文章中,我们讨论了结构,类,属性和方法。

开始进行Swift编程第6部分-结构,类,属性和方法

在上一篇文章中,我们介绍了函数,枚举和范围。

medium.com

它帮助我们弄清楚如何创建对象以及将类似的属性和方法组合在一起。 本系列的内容不会教您Apple提供的更具体的类,例如UIButton或更实际的URLSession。 它会教给您足够的知识,因此当您在自己的程序中看到这些类时,您将对如何开始使用它们有所了解。 因此,让我们深入研究这一课。

从最基本的意义上讲,类和结构的初始化是为类和结构提供值。 有时,我们希望类或结构在创建时具有默认值,而其他时候,我们想告诉类或结构其默认值是什么。

初始化Class和初始化Struct之间的区别是:Swift中的Struct有自己的默认初始化器 ,而Classes没有。 这是什么意思? 这意味着当我们创建结构时,我们不需要在Struct中包含init()方法,它会自动为您创建。 仅当创建类时未实例化该类的属性时,类才必须具有init()方法。 让我们看看我在说什么。

myFullyInitializedClass我们可以使用
var myFirstClass = myFullyInitializedClass() 。 如果要使用myFirstClass.myFunction()调用函数,则它将返回整数1因为我们为该类设置了初始值。

myNonInitializedClass ,我们需要传入给定的givenNumber来创建它,以便初始化该类的所有属性。 在这种情况下,我们只有一个属性,但是如果有更多属性,则必须传递所有默认值。 只需将init()方法想像为目前没有func关键字的函数即可。 其唯一目的是为类中的属性分配值。 当我们调用方法myFunction ,它将返回值2

myStruct示例中,我们没有初始化程序,因为在后台为我们创建了默认初始化程序。 创建新结构时,我们使用var myThirdStruct = myStruct(firstNumber: 3) 。 不,我们没有创建3个结构,但我以这种方式命名,因此您可以看到类和结构的总体顺序。 我们可以调用myThirdStruct.myFunction ,该方法将返回3

如果我们要创建一个myStruct的新实例,而不传递第一个数字的默认值,则会收到错误消息。 因此,对于结构,您必须设置默认值或在调用它时传递一个默认值。

可以在类和结构中使用Optionals以满足初始化程序的要求。 假设您创建的类并不总是需要其所有属性。 我们可以将不需要的属性设置为立即可用。

通过使用可选值,我们不必包括初始化程序,但是,如进一步所示,我们需要强制展开可选值,以删除设置了我们值的Optional()包装器。 我知道我告诉过您不要使用! ,强制解包,操作员。 但是如果没有更多工具可供使用,那么现在就必须这样做。 不用担心那些新工具即将推出。 如果您确实解开了nil可选项,则程序将崩溃,并且Found nil while unwrapping optional 会收到一个讨厌的错误消息: Found nil while unwrapping optional

有几种类型的初始化程序,我们刚刚介绍过的默认初始化程序,必需的初始化程序,便捷初始化程序和失败的初始化程序。

必需的初始化器只是意味着,如果您从当前正在创建的类中继承子类,则该子类必须从父级或超类中调用初始化器。

如上所示,通过将Vehicle的初始化程序设置为required ,我们强制Car为超类提供fuelType。 我们使用super.init来访问超类的初始化程序。 如果这没有意义,请不要担心,我将在一分钟内重新编写它以演示其工作原理。 通过调用Carinit方法,我们需要包含VehiclefuelType属性,因此,当我们从init方法中调用super.init时,我们可以将fuelType值传递回Vehicle类。 让我们看看它现在如何工作。

因此,这实际上与从来没有出现过Vehicle类的情况相同。 “为什么不使用这种方式呢?”答案很简单,可扩展。 从第一个子类化示例开始,让我们对其进行扩展。

在此示例中,我们提供了拥有其自身属性bedSizeTruck类。 然后,我们创建一个SemiTruck类,该类也具有一个初始化程序,但仅接受hasSleeper 。 我们将fuelType设置为fuelType ,因为在此示例中,所有的Semi- .diesel都使用柴油运行。 这是允许的。

这将我们带到了方便的初始化器。 便利初始化器的工作方式与上一个示例相同,但是当您不需要自己设置值时,它们用于设置默认值。

这使我们无需设置值即可调用Person() ,并且获得默认name "unknown person" 。 这些对于在整个类中使用可选选项可能是有用的替代方法。

失败的初始化程序可以防止您将类的属性设置为错误的值,并且如果无法初始化该类,则允许您返回nil。

在这里,我们使用init?设置了可失败的初始化程序init? ,这使我们可以取消初始化,并返回nil到变量。 使用故障初始化程序还会使保存该类的变量成为可选变量。 如果不输入名称,则返回nil,否则,返回可选的myFailableClass实例。

完成类后,去初始化就是清除所有松散的结局。 很快我们将讨论引用计数,但是只要您创建一个类,便会创建对该引用的引用 ,如果您有另一个使用该类的对象,我们将使用反初始化来帮助其他对象“忘记”该类。 对于此示例,我将讨论Swift Timer类。 计时器为使用它们的类创建了一个强大的引用 。完成类后,我们想从类中删除计时器,让我们看看它是如何完成的。

从内存中删除我们所在的类之前,将deinit调用deinit 。 当我们使用myCounter = nil ,该类准备将自身从内存中删除。 它调用deinit并运行其中的任何代码,无论是将数据复制出来以将其保存到某个地方,还是在我们的示例中,检查计时器是否仍然有效,如果无效,则将其无效。

使计时器无效会使计时器释放对拥有它的类的引用,并执行使其从内存中删除所需的其他任何清理任务。 计时器销毁后,所属类便完成deinit并将其从内存中删除。

重写允许您子类化并更改超类的默认初始化器。

在这里,我们有Ball的超类,它带有一个默认的初始化器init() ,因为我们要从Basketball调用一个具有相同名称的初始化器,所以我们使用了override关键字。 我们仍然需要调用super.init以便超类可以完成其工作,但是最后我们将Basketball类设置为默认值3

我们还在具有相同名称且存在于父类中的方法上使用override关键字。 在这里,我们重写方法bouceHeight因为它具有与父类相同的方法签名 。 方法签名只是另一种说法,它看起来完全相同,应该以相同的方式调用。 唯一的区别是与其返回
Double(size) * 0.5我们返回Double(size) * 0.75 。 从一种类型转换为另一种类型的过程称为强制转换 。 我们将在本系列的下一部分中讨论这一点。

不必担心何时需要重写,当您需要使用override关键字时,Xcode会生成一条不错的错误消息,甚至在您单击错误时也会自动为您完成它。

每当在Swift中创建类时,都会创建对对象的引用。 创建此引用后,系统将同时创建一个计数器。 该计数器直接引用指向它的对象(包括变量)的数量。

这里我们有两个引用,A和B都指向Data,因此我们更新计数器说有两个引用。 如果将A设置为nil那么我们只有一个对Data的引用。

如果A和B都不再引用数据会怎样? 根据内存定律,数据将无限期地坐在那里。 在关闭计算机(清除内存)之前或偶然之间,我们碰巧访问了包含数据的整个内存块,并将其设置为nil或者更有可能在使用的数据之上写入了新值成为。

多亏有了操作系统,我们才不必为此而惊慌。 在Swift中,更准确地说,在clang编译器中 ,我们有一个称为ARC(自动引用计数)的功能,该功能会在不同时间出现,检查内存中是否有不再引用它的数据,并将其从内存中取消分配。 在过去的Objective-C时代,这曾经被称为垃圾收集,而垃圾收集仍然被许多语言所使用。 这并不意味着一个要比另一个好或坏,这只是从内存中释放数据的不同过程。

ARC挂起的地方是当我们有很强的参考资料时。 使用上面的计数器类,让我们说明一下它的外观。

因为Counter拥有Timer,并且Timer创建了对Counter的引用,所以它们每个的引用计数都为1。如果我们在不删除此强引用的情况下释放Counter,则Timer会以引用计数1单独存在,直到计算机被删除为止。关闭。 ARC无法处理这种情况。 这就是所谓的内存泄漏。 这只是说一个对象位于内存中并且永远不会消失的一种方式。

任何大小的内存泄漏都不是一件好事,我们都应该努力做到没有,但是当您第一次开始时,它就会发生。 这并不意味着您会立即用尽内存,而是意味着当您拥有该泄漏内存时,您将无法将该内存用于其他任何用途。 关闭计算机大约15秒钟是最简单的修复方法。 “啊,这就是有线电视公司告诉我关闭路由器15秒钟的原因。”确实,如果您的内存损坏,关闭设备将清除损坏的内存,并且在将正确的数据重新写回内存后它可以正常工作。

因此,如果我们释放Timer,我们仍然可以控制该类的释放,并且这样做时一切都很好。

您可以使用这种强引用循环来帮助的另一种方法是用weakunowned关键字标记将引起强引用的类。

Ilea Cristian在他对Stack Overflow的回答中说得最好:

weak引用使它的可能性变为nil (当释放被引用的对象时,这会自动发生),因此属性的类型必须是可选的–因此,作为程序员,您必须在使用它之前进行检查(基本上,编译器会尽力迫使您编写安全的代码)。

一个unowned引用假定它在生命周期内永远不会变为nil 。 初始化期间必须设置一个无主引用–这意味着该引用将被定义为非可选类型,可以不经检查即可安全使用。 如果以某种方式释放了被引用的对象,则当使用无主引用时,应用程序将崩溃。

这就是帮助我最终理解“ unownedweak ”概念的原因。

我们了解了初始化,如何执行初始化以及为什么它在类中如此重要。 在Swift中,大多数时候您都会编写结构,偶尔需要使用一个类。 经验法则是从结构开始,如果遇到限制,请移至类。

然后,我们了解了取消初始化以及何时需要进行初始化。 不需要在每个类中都包含它,但是请确保在可能会遇到强大的参考周期时使用它。

我们了解了一些关于覆盖以及为什么需要覆盖的知识,并完成了使用引用类型编写程序时需要意识到的最重要的主题之一:引用计数。 知道自己最终可能会导致内存泄漏并加以阻止,这是您可以直接控制应用程序中的错误的时间。 它不会导致您的应用程序崩溃,但是由于内存不足而使用户的计算机崩溃,可能会同样糟糕。

建议阅读:

  • 使用Swift编程语言通读函数,枚举,类和结构,属性,方法,继承,初始化,取消初始化和自动引用计数。

函数— Swift编程语言(Swift 4.2)

函数是执行特定任务的独立代码块。 您给函数命名以标识……

developer.apple.com

我知道这听起来像是很多信息,但是自从上一本书阅读以来,我给您提供了入门知识,您应该能够快速浏览它,并获得有关您可能正在苦苦挣扎的主题的更深入的信息。用。

接下来是Type强制转换,Safely Unwrapping Optionals和Access Control。 它应该很快就可以完成,它将为您的工具集提供很大的灵活性。

与往常一样,继续练习您的新技能,我们已经完成了一半以上。

Swift编程第8部分开始-类型转换,安全解压缩可选项和访问控制

在上一篇文章中,我们讨论了初始化,反初始化,覆盖和引用计数。 在这一部分中,我们…

medium.com