1.单例的实现
class Singleton {
// 静态属性
static let shared = Singleton()
// 私有构造器,防止外部创建新实例
private init() {
}
var someProperty: Int = 0
func someMethod() {
}
}
-
static let shared = Singleton()
: 声明一个静态的常量属性shared
,它是Singleton
类型的单一实例。 -
private init()
: 将构造器设为private
,防止外部直接创建Singleton
类的新实例。 -
在需要使用单例实例的地方,直接通过
Singleton.shared
进行访问和操作。
// 访问单例属性和方法
Singleton.shared.someProperty = 42
Singleton.shared.someMethod()
-
线程安全: 由于
shared
属性是静态的常量,在多线程环境下也能保证只有一个单例实例。 -
延迟初始化:
Singleton
类的实例会在第一次访问shared
属性时才被创建,节省资源。 -
可扩展性: 单例类可以像普通类一样添加属性和方法,满足不同的需求。
-
可测试性: 由于构造器是
private
的,可以通过依赖注入的方式来测试使用单例的类。
2.struct 与 class 的区别
-
值类型 vs 引用类型:
- Struct 是值类型,当它被赋值给一个变量或常量,或者被传递给函数时,会创建一个新的副本。修改副本不会影响原始的 Struct 实例。
- Class 是引用类型,当它被赋值给一个变量或常量,或者被传递给函数时,只会创建一个对该实例的引用。修改引用会影响原始的 Class 实例。
// Struct 示例
struct Person {
var name: String
var age: Int
}
var person1 = Person(name: "Alice", age: 30)
var person2 = person1
person2.name = "Bob"
print(person1.name) // Output: "Alice"
print(person2.name) // Output: "Bob"
// Class 示例
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
let person1 = Person(name: "Alice", age: 30)
let person2 = person1
person2.name = "Bob"
print(person1.name) // Output: "Bob"
print(person2.name) // Output: "Bob"
-
继承:
- Class 支持继承,可以创建子类并继承父类的属性和方法。
- Struct 不支持继承,但可以使用协议来实现类似的功能。
-
初始化:
- class 在初始化时不能直接把 property 放在默认的 constructor 的参数里,而是需要自己创建一个带参数的 constructor。struct 可以把属性放在默认的 constructor 的参数里。
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
let person = Person(name: "Alice", age: 30)
struct Person {
let name: String
let age: Int
}
let person = Person(name: "Alice", age: 30)
-
deinit:
- Class 支持 deinit 方法,可以在实例被销毁时执行清理操作。
- Struct 没有 deinit 方法,因为它们是值类型,在超出作用域时会自动销毁。
// Class deinit 示例
class Person {
var name: String
init(name: String) {
self.name = name
print("Person \(name) was initialized.")
}
deinit {
print("Person \(name) was deinitialized.")
}
}
var person: Person? = Person(name: "Alice")
person = nil // Output: "Person Alice was deinitialized."
// Struct 没有 deinit
struct Person {
var name: String
}
var person = Person(name: "Alice")
// No deinit called
-
类型转换:
- Class 支持类型转换,可以在运行时检查和转换类实例的类型。
- Struct 不支持类型转换,因为它们是值类型,没有运行时类型信息。
// Class 类型转换示例
class Animal {}
class Dog: Animal {}
class Cat: Animal {}
let animal: Animal = Dog()
if let dog = animal as? Dog {
print("It's a dog!")
} else if let cat = animal as? Cat {
print("It's a cat!")
} else {
print("It's an unknown animal.")
}
-
内存管理:
- Class 实例需要手动管理内存,通常使用引用计数或自动引用计数(ARC)机制。
- Struct 实例的内存管理是自动的,它们被分配在栈上,不需要手动管理。
-
struct 的 function 要去改变 property 的值的时候要加上 mutating,而 class 不用。
struct Person {
var name: String
var age: Int
mutating func updateName(to newName: String) {
name = newName
}
}
var person = Person(name: "Alice", age: 30)
person.updateName(to: "Bob")
print(person.name) // Output: "Bob"
-
struct 会自动生成需要的构造方法(constructor),哪个属性没有赋初始值就会生成以哪个属性为参数的构造方法。
struct Person {
let name: String
let age: Int = 30
}
let person = Person(name: "Alice") // Can call the constructor with name parameter
-
Struct 不能被序列化成 NSData 对象,原因是无法归解档。归解档的类必须遵守 NSCoding 协议,struct 不能遵守 NSCoding 协议。
-
当项目的代码是 Swift 和 Objective-C 混合开发时,会发现在 Objective-C 的代码里无法调用 Swift 的 Struct。因为要在 Objective-C 里调用 Swift 代码的话,对象需要继承于 NSObject
总的来说,如果数据是简单的、不需要共享引用的,且不需要复杂的继承结构,使用 Struct 通常是更好的选择。而对于需要共享引用、继承、复杂内存管理的场景,使用 Class 会更合适。
3.如何使用map、filter和reduce
map:
- 用于对集合中的每一个元素执行一个指定的操作,并返回一个新的集合。
- 示例:将一个数组中的所有数字乘以2
let numbers = [1, 2, 3, 4, 5]
let doubledNumbers = numbers.map { $0 * 2 }
print(doubledNumbers) // [2, 4, 6, 8, 10]
filter:
- 用于从集合中过滤出符合指定条件的元素,并返回一个新的集合。
- 示例:从数组中过滤出所有偶数
let numbers = [1, 2, 3, 4, 5]
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers) // [2, 4]
reduce:
- 用于将集合中的所有元素组合成一个单一的值。
- 它接受一个初始值和一个闭包函数,该函数将前一次的结果和当前元素合并成一个新的值。
- 示例:计算数组元素的和
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0) { $0 + $1 }
print(sum) // 15
4.计算属性和存储属性
存储属性:
- 存储属性是与特定类、结构体或枚举相关联的常量或变量。
- 它们直接存储值,可以是任意类型,包括基本数据类型、自定义类型等。
计算属性:
- 计算属性不直接存储值,而是提供一个 getter 和可选的 setter 来间接地访问和修改其他属性或变量的值。
- 计算属性的声明使用
var
关键字,即使属性本身是只读的。
struct Circle {
var radius: Double
var area: Double {
get {
return Double.pi * radius * radius
}
set {
radius = sqrt(newValue / Double.pi)
}
}
}
let myCircle = Circle(radius: 5.0)
print(myCircle.area) // 输出: 78.53981633974483
myCircle.area = 100.0
print(myCircle.radius) // 输出: 5.641895835477563
在这个例子中,area
是一个计算属性,它根据radius
属性计算圆的面积。当设置area
时,它会自动更新radius
的值。
计算属性的优势在于:
- 可以根据其他属性或变量的值动态计算属性值
- 可以提供自定义的 getter 和 setter 方法,控制属性的读写行为
- 可以实现只读或只写属性
5.枚举的其它用法
关联值 (Associated Values):
- 枚举成员可以关联不同类型的值。
- 这些值在每个枚举成员中都可以是不同的。
- 示例:
enum CompassDirection {
case north(direction: String, degrees: Int)
case south(direction: String, degrees: Int)
case east(direction: String, degrees: Int)
case west(direction: String, degrees: Int)
}
let northDirection = CompassDirection.north(direction: "North", degrees: 0)
原始值 (Raw Values):
- 枚举成员可以有预定义的原始值,通常是
Int
、String
、Character
或Bool
类型。 - 原始值在每个枚举成员中必须是唯一的。
- 示例:
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
let earth = Planet(rawValue: 3)
print(earth?.rawValue) // 输出: 3
递归枚举 (Recursive Enumerations):
- 枚举成员可以包含另一个同类型的枚举作为关联值。
- 这种情况下,枚举需要使用
indirect
关键字修饰。 - 示例:
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
枚举的计算属性和方法:
- 枚举可以定义计算属性和方法,就像结构体和类一样。
- 这些属性和方法可以访问枚举成员的关联值或原始值。
- 示例:
enum TrafficLight {
case red, yellow, green
var description: String {
switch self {
case .red:
return "Stop"
case .yellow:
return "Slow down"
case .green:
return "Go"
}
}
func next() -> TrafficLight {
switch self {
case .red:
return .green
case .yellow:
return .red
case .green:
return .yellow
}
}
}
let light = TrafficLight.red
print(light.description) // 输出: "Stop"
let nextLight = light.next()
print(nextLight) // 输出: green
枚举遵循协议:
- 在 Swift 中,枚举可以像类和结构体一样实现协议。
- 这允许我们为枚举添加协议中定义的属性、方法和下标。
- 示例:
protocol Named {
var name: String { get }
}
enum WeekDay: Named {
case monday, tuesday, wednesday, thursday, friday, saturday, sunday
var name: String {
switch self {
case .monday: return "Monday"
case .tuesday: return "Tuesday"
// 其他情况...
}
}
}
在这个例子中,我们定义了一个 Named
协议,然后让 WeekDay
枚举遵循这个协议。这允许我们为枚举成员添加 name
属性的实现。
通过扩展增加功能:
- 我们还可以通过扩展来为枚举增加额外的功能。
- 扩展允许我们在不修改枚举本身的情况下,为其添加新的计算属性、方法和下标。
- 示例:
extension WeekDay {
var isWeekend: Bool {
return self == .saturday || self == .sunday
}
func next() -> WeekDay {
switch self {
case .sunday: return .monday
case .monday: return .tuesday
// 其他情况...
}
}
}
let today = WeekDay.monday
print(today.name) // 输出: "Monday"
print(today.isWeekend) // 输出: false
print(today.next().name) // 输出: "Tuesday"
在这个例子中,我们通过扩展为 WeekDay
枚举添加了 isWeekend
计算属性和 next()
方法,这些功能并没有在枚举的原始定义中出现。
6.?解包
可选绑定 (Optional Binding):
- 可选绑定用于安全地访问可选类型的值。
- 它允许我们在使用可选值之前先检查它是否为
nil
。 - 可选绑定使用
if let
或guard let
语句来实现。 - 示例:
let myString: String? = "Hello, world!"
if let unwrappedString = myString {
print(unwrappedString) // 输出: "Hello, world!"
} else {
print("myString is nil")
}
在这个例子中,我们使用 if let
语句来检查 myString
是否为 nil
。如果不为 nil
,则将其解包并赋值给 unwrappedString
常量,然后在 if
块内使用它。否则,我们会进入 else
块并输出 "myString is nil"。
可选链接 (Optional Chaining):
- 可选链接用于安全地访问可选类型中的属性、方法或下标。
- 它允许我们在可选值可能为
nil
的情况下进行访问,而不会引发运行时错误。 - 可选链接使用
?
运算符来实现。 - 示例:
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms: Int = 1
}
let john = Person()
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("John doesn't have a residence.")
}
在这个例子中,我们使用 john.residence?.numberOfRooms
来访问 Person
实例的 residence
属性和 Residence
实例的 numberOfRooms
属性。如果 residence
属性为 nil
,则整个表达式的值也为 nil
,从而避免了运行时错误。
nil 合并运算符 (??
):
- nil 合并运算符
??
用于安全地获取可选类型的值。 - 它提供了一种简单的方式来指定,如果可选值为
nil
,应该返回一个备用值。 - 示例:
let userName: String? = nil
let greeting = "Hello, \(userName ?? "Guest")"
print(greeting) // 输出: "Hello, Guest"
在这个例子中,如果 userName
为 nil
,则 userName ?? "Guest"
会返回字符串 "Guest"
。
强制解包运算符 (!
):
- 强制解包运算符
!
用于将可选类型强制转换为非可选类型。 - 它会直接访问可选类型中的值,而不进行任何安全检查。
- 如果可选值为
nil
,使用!
会导致运行时错误。 - 示例:
let myString: String? = "Hello, world!"
let unwrappedString = myString! // 如果 myString 为 nil,这会导致运行时错误
print(unwrappedString) // 输出: "Hello, world!"
在这个例子中,我们使用 !
运算符将 myString
强制转换为非可选类型 String
。如果 myString
恰好为 nil
,这种强制转换会导致运行时错误。因此,强制解包运算符 !
应该谨慎使用,最好先确保可选值不为 nil
。