属性
Swift中跟实例相关的属性可以分为2大类
存储属性(Strored Property)
类似于成员变量的概念,存储在实例的内存中,结构体、类可以定义存储属性,枚举不可以定义存储属性
计算属性(Computed Property)
本质就是方法(函数)、不占用实例的内存,枚举、结构体、类都可以定义计算属性
struct Circle {
// 存储属性
var radius: Double
// 计算属性
var diameter: Double {
set {
radius = newValue / 2
}
get {
radius * 2
}
}
}
var circle = Circle(radius: 5)
circle.radius // 5.0
circle.diameter // 10.0
MemoryLayout<Circle>.stride // 8
关于存储属性,Swift有个明确的规定,在创建类或者结构体的实例时候,必须为所有的存储属性设置一个合适的初始值,可以再初始化器里为存储属性设置一个初始值,或者也可以分类一个默认的属性值作为属性定义的一部分
set传入的新值默认叫做newValue,也可以自定义
struct Circle {
// 存储属性
var radius: Double
// 计算属性
var diameter: Double {
set(newdiameter) {
radius = newdiameter / 2
}
get {
radius * 2
}
}
}
定义计算属性只能用var,不能用let,计算属性的值是可能发生变化的 ,即使是只读计算属性
只读计算属性:只有get 没有set 。 下面两份代码等价
struct Circle {
var radius: Double
var diameter: Double {
get {
radius * 2
}
}
}
struct Circle {
var radius: Double
var diameter: Double { radius * 2 }
}
枚举的rawValue的本质是:只读计算属性
enum TestEnum: Int {
case test1 = 1, test2 = 2
var rawValue: Int {
switch self {
case .test1:
return 10
case .test2:
return 23
}
}
TestEnum.test2.rawValue
延迟存储属性Lazy Stored Property
使用lazy可以定义一个延迟存储属性,在第一次用到属性的时候才会进行初始化
lazy属性必须是var,延迟属性初始化时需要改变结构体的内存,不能是let,let必须在实例的初始化方法完成之前就拥有值,如果多条线程同时第一次访问lazy属性,无法保证属性只被初始化1次,所以要加锁
class Car {
init() {
print("Car init!")
}
func run() {
print("Car is running")
}
}
class Person {
lazy var car = Car()
init() {
print("Person init!")
}
func goOut() {
car.run()
}
}
let p = Person()
print("-------")
p.goOut()
Person init!
-------
Car init!
Car is running
案例调用
class PhotoView {
lazy var image: Image = {
let var url = "https://www.ffsf.com/df.png"
let data = Data(url: url)
return image(data: data)
}
}
属性观察器 Property Observer
可以为非lazy的var存储属性设置属性观察器
willSet会传递新值,默认叫做newValue,didSet会传递旧值,默认叫做oldValue。在初始化器中设置属性值不会触发willSet和didSet,在属性定义时设置初始值也不会触发willSet和didSet
struct Circle {
var radius: Double {
willSet {
print("willset", newValue)
}
didSet {
print("didset", oldValue, radius)
}
}
init() {
self.radius = 1.0
print("Circle init!")
}
}
var circle = Circle()
circle.radius = 10.4 // 5.0
Circle init!
willset 10.4
didset 1.0 10.4
属性观察器、计算属性的功能,同样可以应用到全局变量、局部变量的身上
var num: Int {
get {
return 10
}
set {
print("setNum", newValue)
}
}
num = 11
func test() {
var age = 10 {
willSet {
print("willset", newValue)
}
didSet {
print("didset", oldValue, age)
}
}
age = 11 // willset 11 didset 10 11
}
test()
类型属性 Type Prooerty
严格来说,属性可以分为
实例属性:只能通过实例去访问。1:存储实例属性:存储在实例中的属性,每个实例中都有一份。 2:计算实例属性
类型属性:只能通过类型去访问。1:存储类型属性:整个程序运行过程中,只有一份内存(类似于全局变量)。2:计算类型属性
可以通过sttic定义类型属性, 如果是类,也可以用关键字class,因为struct只能用static class里可以用class和static
struct Car {
static var count: Int = 0
init() {
Car.count += 1
}
}
let c1 = Car()
let c2 = Car()
let c3 = Car()
print(Car.count) // 3
类型属性细节
不同于存储实例属性,你必须给存储类型属性设置初始值, 因为类型没有像实例那样的init初始化器来初始化存储属性
存储类型属性默认就是lazy,会再第一次使用的时候才初始化,就算被多个线程同时访问,保证只会初始化一次,是线程安全的。存储类型属性可以用let
枚举类型也可以定义类型属性(存储类型属性、计算类型属性)
单例模式
public class FileManager {
public static let shared = FileManager()
private init() {}
}
public class FileManagers {
public static let shared = {
// .....
return FileManagers()
}()
private init() {}
}
inout本质总结
如果实参有物理内存地址,且没有设置属性观察器,则直接将实参的内存地址传入函数(实参进行引用传递)
如果实参是计算属性 或者 设置值了属性观察器 采取了Copy In Copy Out的做法
调用该函数时,先赋值实参的值,产生副本:get 讲副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
函数返回后,再讲副本的值覆盖实参的值:set
总结:inout的本质据实引用传递(地址传递)
方法
枚举、结构体、类都可以定义实例方法、类型方法
实例方法:Instance Method 通过实例调用。 类型方法Type Method 通过类型调用,用static或者class关键字定义
struct Car {
static var count: Int = 0
init() {
Car.count += 1
}
static func getCount() -> Int { count }
}
let c1 = Car()
let c2 = Car()
let c3 = Car()
print(Car.getCount()) // 3
self 在实例方法中代表实例,在类型方法中代表类型
在类型方法static func getCount中 cout等价于self.count、Car.self.count、Car.count
结构体和枚举是值类型,默认情况下,值类型的属性不能被自身的实例方法修改 在func关键字前加mutating可以运行这种修改行为
@discardableResult
在func前面加个@discardableResult,可以消除:函数调用后返回值未被使用的警告⚠️
struct Point {
var x = 0.0, y = 0.0
@discardableResult mutating
func moveBy(delayX: Double) -> Double {
x += delayX
return x
}
}
var p = Point()
p.moveBy(delayX: 10)
@discardableResult
func get() -> Int {
return 10
}
get()
下标subscript
使用subscript可以给任意类型(枚举、结构体、类)增加下标功能,有些地方也翻译为:下标脚本
subscript的预发类似于实例方法、计算属性,本质就是方法(函数)
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
set {
if index == 0 {
x = newValue
} else if index == 1 {
y = newValue
}
}
get {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
}
var p = Point()
p[0] = 11.3
p[1] = 22.2
print(String.init(format: "%.2f", p.x) + "==" + String.init(format: "%.2f", p[0]) + "==" + "11.3")
print(String.init(format: "%.2f", p.y) + "==" + String.init(format: "%.2f", p[1]) + "==" + "22.2")
supscript中定义了返回值类型决定了 get方法的返回值类型,set方法中newValue的类型, subscript可以接受多个参数,并且类型任意
subscript可以没有set方法,但必须要有get方法。
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
get {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
}
如果只有get方法,可以省略get
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
可以设置参数标签
class Point {
var x = 0.0, y = 0.0
subscript(index i: Int) -> Double {
if i == 0 {
return x
} else if i == 1 {
return y
}
return 0
}
}
var p = Point()
p[index: 1]
下标可以是类型方法
class Sum {
static subscript(v1: Int, v2: Int) -> Int {
return v1 + v2
}
}
Sum[10, 33]
多参数的下标
class Grid {
var data = [
[0, 3, 3],
[2, 3, 4],
[2, 4, 4]
]
subscript(row: Int, column: Int) -> Int {
set {
guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
return
}
data[row][column] = newValue
}
get {
guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
return 0
}
return data[row][column]
}
}
}
var gird = Grid()
gird[0, 1] = 223
gird[2, 2] = 13
print(gird.data)
[[0, 223, 3], [2, 3, 4], [2, 4, 13]]
结构体、类作为返回值对比 class的 可以不加set方法
class Point {
var x = 0, y = 0
}
class PointManager {
var point = Point()
subscript(index: Int) -> Point {
point
}
}
var pm = PointManager()
pm[0].x = 2333
pm[0].y = 232222
(lldb) p pm[0]
(MachOSSwift.Point) $R0 = 0x0000000100577130 (x = 2333, y = 232222)
(lldb) p pm.point
(MachOSSwift.Point) $R1 = 0x0000000100577130 (x = 2333, y = 232222)
上面的代码 Point前面改成struct,就立刻报错
![]()
给struct的加上set方法就不报错了
class PointManager {
var point = Point()
subscript(index: Int) -> Point {
set { point = newValue }
get { point }
}
}

因为struct是值类型,是需要内容拷贝的,pm[0]这个就是一个临时的point,并不是真正的point,这个是无法修改的,所以会报错。 而class可以的原因是因为, get方法里面返回的point是指针变量,不是结构体,而通过指针变量去访问x 是可以访问成功的。所以如果是结构体的话 一定要加上set方法,如果是class则可以不加set方法
继承 Inheritance
值类型(枚举、结构体)不支持继承,只有类支持继承
没有父类的类,成为基类
Swift并没有像OC、Java那样的规定,任何类最终都要继承自某个基类,子类可以重写父类的下标、方法、属性(计算属性),重写必须加上override关键字

重写实例方法和下标。多态:父对象指向子类类型
class Animal {
func speak() {
print("Animal speak")
}
subscript(index: Int) -> Int {
return index
}
}
class Cat: Animal {
override func speak() {
super.speak()
print("Cat speak")
}
override subscript(index: Int) -> Int {
return super[index] + 1
}
}
var anim: Animal
anim = Animal() // Animal speak
anim.speak() // 6
print(anim[6])
anim = Cat()
anim.speak() // Animal speak Cat speak
print(anim[9]) // 10
被class修饰的类型方法、下标,允许被子类重写,被static修饰的类型方法、下标,不允许被子类重写。所以想要重写父类,父类就用class,子类可以用static,它只影响的是它的子类不能被重写
class Animal {
class func speak() {
print("Animal speak")
}
subscript(index: Int) -> Int {
return index
}
}
class Cat: Animal {
override class func speak() {
super.speak()
print("Cat speak")
}
override subscript(index: Int) -> Int {
return super[index] + 1
}
}
重写属性
子类可以将父类的属性(存储、计算)重写为计算属性,子类不可以将父类属性重写为存储属性,只能重写var属性,不能重写let属性。重写时,属性名、类型要一致
子类重写后的属性权限不能小于父类属性的权限,如果父类属性是只读的,那么子类重写后的属性可以是只读的、也可以是可读可写的,如果父类属性是可读写的,那么子类重写后的属性也必须是可读写的。

重写类型属性
被class修饰的计算类型属性,可以被子类重写,被static修饰的类型属性(存储、计算),不可以被子类重写

可以在子类中为父类属性(除了只读计算属性、let属性)增加属性观察器


类的类属性

多态的原理:oc:runtime, c++:虚表(虚函数表)

初始化
类、结构体、枚举都可以定义初始化器
类有2中初始化器:指定初始化器(designated initializer)、便捷初始化器(convenience initializer).
init(paramters) { // 指定初始化器
statements
}
convenience init(paramters) { // 便捷初始化器
statements
}
认为主要的都设置为指定初始化器,其他的都可以设置为便捷初始化器。
每个类至少有一个指定初始化器,指定初始化器是主要初始化器,默认初始化器总是类的指定初始化器,类偏向于少量指定初始化器,一个类通常只有一个指定初始化器
初始化器的相互调用规则:指定初始化器必须从它的直系父类调用指定初始化器,便捷初始化器必须从相同的类里调用另一个初始化器,便捷初始化器最终必须调用一个指定初始化器。
初始化器的相互调用如图

这就是两段式初始化
Swift在编码安全方面是煞费苦心,为了保证初始化过程的安全,设定了两段式初始化、安全检查
两段式初始化
第一阶段:初始化所有存储属性
1:外层调用指定/便捷初始化器
2:分配内存给实例,但未初始化
3:指定初始化器确保当前类定义的存储属性都初始化
4:指定初始化器调用父类的初始化器,不断向上调用,形成初始化器链
第二阶段:设置新的存储属性值
1:从顶部初始化器往下,链中的每一个指定初始化器都有机会进一步指定实例
2:初始化器现在能够使用self(访问、修改它的属性,调用它的实例方法等等)
3:最终,链中任何便捷初始化器都有机会定制实例以及使用self
总结:第一阶段结束代表:属性都能得到初始化,内存得到分配,内存得到初始化,从下往上走的。第二阶段个性化定制从上往下走
安全检查
指定初始化器必须保证在调用父类初始化器之前,其所在类定义的所有存储属性都要初始化完成
指定初始化器必须先调用父类初始化器,然后才能为继承的属性设置新值
便捷初始化器必须先调用同类中的其他初始化器,然后再为任意属性设置新值
初始化器在第一阶段初始化完成之前,不能调用任何实例方法,不能读取任何实例属性的值,也不能引用self,直到第一阶段结束,实例才算完全合法
重写
当重写父类的指定初始化器时,必须加上override(即便子类的实现是便捷初始化器)
如果子类写了一个匹配父类便捷初始化器的初始化器,不用加上override,因为父类的便捷初始化器永远不会通过子类直接调用,因此,严格来说,子类无法重写父类的便捷初始化器
自动继承
1:如果子类没有自定义任何指定初始化器,它会自动继承父类所有的指定初始化器。
2:如果子类提供了父类所有指定初始化器的实现(要么通过方式1继承,要么重写)
子类自动继承所有的父类便捷初始化器。
3:就算子类添加了更多的便捷初始化器,这些规则仍然适用
4:子类以便捷初始化器的形式重写父类的指定初始化器,也可以作为满足规则2的一部分
requeired
用required修饰初始化器,表明其所有子类都必须实现该初始化器(通过继承或者重写实现)
如果子类重写了required初始化器时 也必须加上required,不用加override
class Person {
required init() {}
init(age: Int) {}
}
class Student: Person {
required init() {
super.init()
}
}
属性观察器
父类的属性在它自己的初始化器重赋值不会触发属性观察器,但在子类的初始化器中赋值会触发属性观察器
class Person {
var age: Int {
willSet {
print("willSet", newValue)
}
didSet {
print("didSelt", oldValue, age)
}
}
init() {
self.age = 0
}
}
class Student: Person {
override init() {
super.init()
self.age = 1
}
}
var stu = Student() // willSet 1 didSelt 0 1
可失败初始化器
类、结构体、枚举都可以使用init?来定义可失败初始化器
class Person {
var name: String
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
}
不允许同时定义参数标签、参数个数、参数类型相同的可失败初始化器和非可失败初始化器
可以用init!定义隐式解包的可失败初始化器
可失败初始化器可以调用非可失败初始化器,非可失败初始化器调用可失败初始化器需要进行解包
如果初始化器调用一个可失败初始化器导致初始化失败,那么整个初始化过程都失败,并且之后的代码都停止执行
可以用一个非可失败初始化器重写一个可失败初始化器,但是反过来是不行的
给一个view的初始化可做参考,后续可再重新赋值
class ScrollItem: UIView {
let textLabel : UILabel = UILabel()
convenience init() {
self.init(frame: CGRect.zero)
}
init(frame: CGRect) {
super.init(frame: frame)
layoutUI()
textLabel.backgroundColor = UIColor.cyan
addSubview(textLabel)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
layoutUI()
}
fileprivate func layoutUI() {
textLabel.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height)
}
}
反初始化器 deinit
deinit叫做反初始化器,类似于C++的析构函数,OC中的dealloc方法
当类的实例对象被释放内存时,就会滴啊用实例对象的deinit方法
class Person {
deinit {
print("person对象被销毁了")
}
}
deinit不接受任何参数,不能写小括号,不能自定调用
父类的deinit不能被子类继承,子类的deinit实现执行完毕后会调用父类的deinit
可选链Optional Chaining


193

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



