Swift 类与结构体:特性、内存管理及循环引用处理
1. @objc 属性与协议
使用
@objc
属性时,只有类可以采用标记有该属性的协议,结构体则无法采用这些协议。下面是使用
optional
关键字定义可选属性和方法的示例:
@objc protocol Phone {
var phoneNumber: String {get set}
optional var emailAddress: String {get set}
func dialNumber()
optional func getEmail()
}
在上述
Phone
协议中,定义了一个必需属性
phoneNumber
和一个可选属性
emailAddress
,同时定义了一个必需函数
dialNumber()
和一个可选函数
getEmail()
。这意味着采用
Phone
协议的类必须提供
phoneNumber
属性和
dialNumber()
方法,而
emailAddress
属性和
getEmail()
方法则是可选的。
2. 扩展(Extensions)
当需要为现有类或结构体添加额外功能时,可以使用扩展。通过扩展,能够添加新的属性、方法、初始化器和下标,或者使现有类或结构体符合某个协议。不过,扩展不能覆盖现有功能。
以下是扩展
String
类的示例:
extension String {
var firstLetter: Character {
get {
return self.characters.first
}
}
func reverse() -> String {
var reverse = ""
for letter in self.characters {
reverse = "\(letter)" + reverse
}
return reverse
}
}
扩展现有类或结构体时,定义属性、方法、初始化器、下标和协议的方式与在标准类或结构体中定义的方式相同。扩展对于为外部框架(包括 Apple 框架)中的类和结构体添加额外功能非常有用,并且相较于子类化,更推荐使用扩展,因为这样可以在整个代码中使用框架提供的类。
3. 内存管理基础:值类型与引用类型
结构体是值类型,类是引用类型。当在应用程序中传递结构体实例(如作为方法的参数)时,会在内存中创建该结构体的新实例。这个新实例仅在创建它的作用域内有效,一旦超出作用域,该实例就会被销毁,内存也会被释放,因此结构体的内存管理相对简单。
而类是引用类型,在首次创建类的实例时,仅分配一次内存。当在应用程序中传递类的实例(作为函数参数或赋值给变量)时,实际上传递的是该实例在内存中的引用。由于类的实例可能在多个作用域中被引用,因此不能自动销毁,即使超出某个作用域,如果在其他作用域中仍有引用,内存也不会被释放。所以,Swift 使用自动引用计数(ARC)来跟踪和管理类实例的内存使用。
4. 引用类型与值类型传递示例
以下是一个示例,展示了引用类型(类的实例)和值类型(结构体或变量的实例)如何传递给函数:
class MyClass {
var name = ""
}
struct MyStruct {
var name = ""
}
func showPass(myc: MyClass, var mys: MyStruct) {
print("Received Class: \(myc.name) Struct: \(mys.name)")
myc.name = "Set in function - class"
mys.name = "Set in function - struct"
print("Set Class: \(myc.name) Struct: \(mys.name)")
}
var mci = MyClass()
mci.name = "set in main - class"
var msi = MyStruct()
msi.name = "set in main - struct"
print("Main Class: \(mci.name) Struct: \(msi.name)")
showPass(mci, msi)
print("Main Class: \(mci.name) Struct: \(msi.name)")
运行上述代码,输出结果如下:
Received Class: set in main - class Struct: set in main - struct
Set Class: Set in function - class Struct: Set in function - struct
Main Class: Set in function - class Struct: set in main – struct
从输出结果可以看出,当传递引用类型(类的实例)给函数时,传递的是对原始类的引用,函数中所做的更改在函数退出后仍然保留;而传递值类型(结构体或变量的实例)给函数时,传递的是实例的值(副本),函数中对副本所做的更改在函数退出后会丢失。
5. ARC 的工作原理
ARC 会自动跟踪类实例的引用计数。当创建新的类实例时,ARC 会分配存储该类所需的内存,并锁定该内存以防止被覆盖。当类实例不再需要时(即引用计数为 0),ARC 会自动销毁该实例并释放内存。
以下是一个展示 ARC 如何工作的示例:
class MyClass {
var name = ""
init(name: String) {
self.name = name
print("Initializing class with name \(self.name)")
}
deinit {
print("Releasing class with name \(self.name)")
}
}
var class1ref1: MyClass? = MyClass(name: "One")
var class2ref1: MyClass? = MyClass(name: "Two")
var class2ref2: MyClass? = class2ref1
print("Setting class1ref1 to nil")
class1ref1 = nil
print("Setting class2ref1 to nil")
class1ref1 = nil
print("Setting class2ref2 to nil")
class2ref2 = nil
在上述示例中,创建了两个
MyClass
类的实例
class1ref1
和
class2ref1
,并为
class2ref1
创建了第二个引用
class2ref2
。通过将这些引用设置为
nil
,可以观察到 ARC 如何销毁实例并释放内存。
这个过程可以用如下 mermaid 流程图表示:
graph TD;
A[创建类实例] --> B[ARC分配内存];
B --> C[引用计数增加];
D[引用置为nil] --> E[引用计数减少];
E --> F{引用计数是否为0};
F -- 是 --> G[ARC释放内存];
F -- 否 --> H[继续使用];
6. 强引用循环问题
强引用循环是指两个类的实例相互持有对方的强引用,导致 ARC 无法释放任何一个实例。以下是一个强引用循环的示例:
class MyClass1 {
var name = ""
var class2: MyClass2?
init(name: String) {
self.name = name
print("Initializing class with name \(self.name)")
}
deinit {
print("Releaseing class with name \(self.name)")
}
}
class MyClass2 {
var name = ""
var class1: MyClass1?
init(name: String) {
self.name = name
print("Initializing class2 with name \(self.name)")
}
deinit {
print("Releaseing class2 with name \(self.name)")
}
}
var class1: MyClass1? = MyClass1(name: "Class1")
var class2: MyClass2? = MyClass2(name: "Class2")
class1?.class2 = class2
class2?.class1 = class1
print("Setting classes to nil")
class2 = nil
class1 = nil
在上述示例中,
MyClass1
和
MyClass2
相互引用,导致引用计数永远不会达到 0,从而造成内存泄漏。内存泄漏会导致应用程序持续使用内存但无法正确释放,最终可能导致应用程序崩溃。
7. 解决强引用循环:无主引用(Unowned Reference)
为了解决强引用循环问题,可以使用无主引用或弱引用。无主引用所引用的实例不能为
nil
,因此在使用无主引用时,属性不能是可选类型。以下是使用无主引用解决强引用循环的示例:
class MyClass3 {
var name = ""
unowned let class4: MyClass4
init(name: String, class4: MyClass4) {
self.name = name
self.class4 = class4
print("Initializing class3 with name \(self.name)")
}
deinit {
print("Releasing class3 with name \(self.name)")
}
}
class MyClass4 {
var name = ""
var class3: MyClass3?
init(name: String) {
self.name = name
print("Initializing class4 with name \(self.name)")
}
deinit {
print("Releasing class4 with name \(self.name)")
}
}
var class4 = MyClass4(name: "Class4")
var class3: MyClass3? = MyClass3(name: "class3", class4: class4)
class4.class3 = class3
print("Classes going out of scope")
在上述示例中,
MyClass3
的
class4
属性被定义为无主引用,这意味着它不会对
MyClass4
实例保持强引用,从而允许 ARC 在实例不再需要时释放内存。
8. 解决强引用循环:弱引用(Weak Reference)
弱引用所引用的实例可以为
nil
,因此使用弱引用时,属性必须是可选类型。以下是使用弱引用解决强引用循环的示例:
class MyClass5 {
var name = ""
var class6: MyClass6?
init(name: String) {
self.name = name
print("Initializing class5 with name \(self.name)")
}
deinit {
print("Releasing class5 with name \(self.name)")
}
}
class MyClass6 {
var name = ""
weak var class5: MyClass5?
init(name: String) {
self.name = name
print("Initializing class6 with name \(self.name)")
}
deinit {
print("Releasing class6 with name \(self.name)")
}
}
在上述示例中,
MyClass6
的
class5
属性被定义为弱引用,从而打破了强引用循环,允许 ARC 正确释放内存。
综上所述,在 Swift 开发中,理解类和结构体的特性、内存管理机制以及如何处理强引用循环是非常重要的。通过合理使用扩展、无主引用和弱引用,可以编写出高效、稳定的代码。
下面是一个总结表格,对比无主引用和弱引用:
| 引用类型 | 引用实例是否可为 nil | 属性是否为可选类型 | 适用场景 |
| ---- | ---- | ---- | ---- |
| 无主引用 | 否 | 否 | 引用实例确定不会为 nil 的情况 |
| 弱引用 | 是 | 是 | 引用实例可能为 nil 的情况 |
通过以上内容,我们对 Swift 中类和结构体的相关特性、内存管理以及强引用循环的解决方法有了更深入的了解。在实际开发中,应根据具体需求合理运用这些知识,以确保代码的性能和稳定性。
Swift 类与结构体:特性、内存管理及循环引用处理
9. 无主引用与弱引用的使用场景分析
在实际开发中,需要根据具体情况选择使用无主引用或弱引用。以下是一些使用场景的分析:
-
无主引用的使用场景
:
- 当一个对象的生命周期明显比另一个对象长,并且被引用的对象在引用它的对象的整个生命周期内都存在时,适合使用无主引用。例如,在一个数据模型中,一个父对象持有对其子对象的引用,而子对象的生命周期完全依赖于父对象,此时可以使用无主引用。
- 当引用的对象不会为
nil
时,使用无主引用可以避免可选类型的繁琐操作,使代码更加简洁。
-
弱引用的使用场景
:
- 当两个对象之间存在相互引用,但其中一个对象的生命周期可能会先结束时,使用弱引用可以避免强引用循环。例如,在一个视图控制器和其持有的代理对象之间的关系中,视图控制器可能会在代理对象之前被销毁,此时可以使用弱引用。
- 当引用的对象可能会变为
nil
时,使用弱引用可以确保在对象被销毁后,引用会自动置为
nil
,避免出现悬空引用的问题。
10. 扩展的更多应用场景
扩展不仅可以用于为现有类和结构体添加新的功能,还可以用于以下场景:
-
为协议添加默认实现
:通过扩展协议,可以为协议中的方法和属性提供默认实现。这样,采用该协议的类或结构体可以选择是否重写这些默认实现。
protocol Printable {
func printInfo()
}
extension Printable {
func printInfo() {
print("Default print info")
}
}
class MyClass: Printable {
// 可以选择不重写 printInfo 方法,使用默认实现
}
let obj = MyClass()
obj.printInfo() // 输出: Default print info
-
对系统类型进行功能扩展
:可以使用扩展为 Swift 的系统类型(如
Int、Array等)添加新的方法和属性,以满足特定的需求。
extension Int {
func squared() -> Int {
return self * self
}
}
let num = 5
let squaredNum = num.squared() // 输出: 25
11. 内存管理的注意事项
在使用 ARC 进行内存管理时,需要注意以下几点:
-
避免不必要的强引用
:在编写代码时,要尽量避免创建不必要的强引用,特别是在可能会导致强引用循环的情况下。可以通过使用无主引用或弱引用代替强引用。
-
及时释放不再使用的引用
:当一个对象不再需要时,要及时将其引用置为
nil
,以便 ARC 能够及时释放内存。
-
注意闭包中的强引用循环
:闭包也可能会导致强引用循环。当闭包捕获了一个对象的引用,并且该对象又持有对闭包的引用时,就会形成强引用循环。可以使用捕获列表来解决闭包中的强引用循环问题。
class MyClass {
var name = ""
lazy var closure: () -> Void = { [weak self] in
if let strongSelf = self {
print("Closure called with name: \(strongSelf.name)")
}
}
init(name: String) {
self.name = name
}
}
let obj = MyClass(name: "Test")
obj.closure() // 输出: Closure called with name: Test
12. 总结与建议
为了更好地理解和运用 Swift 中的类、结构体、扩展和内存管理知识,以下是一些总结和建议:
-
理解值类型和引用类型的区别
:在选择使用类还是结构体时,要考虑它们的特性。值类型适合用于表示简单的数据,而引用类型适合用于表示复杂的对象和需要共享状态的情况。
-
合理使用扩展
:扩展是一种强大的工具,可以为现有类和结构体添加新的功能。在使用扩展时,要确保扩展的功能与原类型的功能相关,并且不会导致代码的混乱。
-
掌握内存管理机制
:了解 ARC 的工作原理,以及如何避免强引用循环。在实际开发中,要根据具体情况选择使用无主引用或弱引用,以确保内存的正确管理。
下面是一个 mermaid 流程图,展示了在开发中如何选择使用无主引用和弱引用:
graph TD;
A[存在强引用循环风险?] -->|是| B{引用实例是否可能为 nil?};
B -- 是 --> C[使用弱引用];
B -- 否 --> D[使用无主引用];
A -->|否| E[使用强引用];
通过以上内容的学习,我们对 Swift 中类和结构体的特性、扩展的应用以及内存管理机制有了更全面的认识。在实际开发中,要根据具体需求灵活运用这些知识,以提高代码的质量和性能。
以下是一个总结列表,概括了本文的重点内容:
1.
@objc
属性下,只有类能采用标记协议,结构体不行。
2. 扩展可给现有类和结构体添加新功能,不能覆盖现有功能。
3. 结构体是值类型,内存管理简单;类是引用类型,使用 ARC 管理内存。
4. 引用类型传递的是引用,值类型传递的是副本。
5. ARC 通过引用计数管理类实例的内存。
6. 强引用循环会导致内存泄漏,可使用无主引用或弱引用解决。
7. 无主引用适用于引用实例确定不为
nil
的情况,弱引用适用于引用实例可能为
nil
的情况。
8. 扩展可用于为协议添加默认实现和对系统类型进行功能扩展。
9. 内存管理要避免不必要的强引用,及时释放不再使用的引用,注意闭包中的强引用循环。
希望这些内容能帮助你更好地掌握 Swift 开发中的相关知识,编写出更加高效、稳定的代码。
637

被折叠的 条评论
为什么被折叠?



