- 作者: Liwx
- 郵箱: 1032282633@qq.com
- 源碼: 需要
源碼的同學(xué), 可以在評論區(qū)留下您的郵箱
iOS Swift 語法
底層原理與內(nèi)存管理分析 專題:【iOS Swift5語法】00 - 匯編
01 - 基礎(chǔ)語法
02 - 流程控制
03 - 函數(shù)
04 - 枚舉
05 - 可選項
06 - 結(jié)構(gòu)體和類
07 - 閉包
08 - 屬性
09 - 方法
10 - 下標(biāo)
11 - 繼承
12 - 初始化器init
13 - 可選項
目錄
- 01-屬性
- 02-存儲屬性
- 03-計算屬性
- 04-枚舉rawValue原理
- 05-延遲存儲屬性(Lazy Stored Property)
- 06-延遲存儲屬性注意點
- 07-屬性觀察器(Property Observer)
- 08-全局變量、局部變量
- 09-inout的再次研究
- 10-inout的本質(zhì)總結(jié)
- 11-類型屬性(Type Property)
- 12-類型屬性細節(jié)
- 13-單例模式
01-屬性
- Swift中跟實例相關(guān)的屬性可以分為
2大類型:存儲屬性(Stored Property),計算屬性(Computed Property)
- 存儲屬性
- 類似于
成員變量這個概念 存儲在實例的內(nèi)存中-
結(jié)構(gòu)體、類可以定義存儲屬性 -
枚舉不可以定義存儲屬性
- 類似于
- 計算屬性
-
本質(zhì)就是方法(函數(shù)) 不占用實例的內(nèi)存-
枚舉、結(jié)構(gòu)體、類都可以定義計算屬性
-
struct Circle {
// 存儲屬性 占用內(nèi)存
var radius: Double
// 計算屬性 不占用內(nèi)存
var diameter: Double {
set {
radius = newValue / 2
}
get {
radius * 2 // 如果只有單一表達式,可以省略return
}
/*
也可以這樣寫
set(newDiameter) {
radius = newDiameter / 2
}
*/
}
}
var circle = Circle(radius: 5)
print(circle.radius) // 5.0
print(circle.diameter) // 10.0
circle.diameter = 12
print(circle.radius) // 6.0
print(circle.diameter) // 12.0
print(MemoryLayout<Circle>.stride) // 8
02-存儲屬性
- 關(guān)于存儲屬性, Swift有個明確的規(guī)定
- 在創(chuàng)建
類或者結(jié)構(gòu)體的實例時,必須為所有的存儲屬性設(shè)置一個合適的初始值- 可以在
初始化器里為存儲屬性設(shè)置初始值 - 可以分配一個
默認(rèn)的屬性值作為屬性定義的一部分
- 可以在
- 在初始化器里為存儲屬性設(shè)置一個初始值
struct Point {
var x: Int
var y: Int
init() {
x = 11
y = 22
}
}
- 分配一個默認(rèn)的屬性值作為屬性定義的一部分
struct Point {
var x: Int = 11
var y: Int = 22
}
var p = Point()
03-計算屬性
-
set傳入的新增默認(rèn)叫做newValue,也可以自定義
struct Circle {
var radius: Double
var diameter: Double {
// set(newDiameter) { // 自定義
// radius = newDiameter / 2
// }
set {
radius = newValue / 2
}
get {
radius * 2
}
}
}
- 定義
計算屬性只能用var, 不能用let-
let代表常量:值是一成不變的 - 計算屬性的值是可能
發(fā)生變化的(即使是只讀計算屬性)
-
-
只讀計算屬性: 只有get, 沒有set
struct Circle {
var radius: Double
var diameter: Double {
get {
radius * 2
}
}
}
- 只讀計算屬性, 只有
get方法,可以省略get
struct Circle {
var radius: Double
var diameter: Double { radius * 2 }
}
- 注意:
不能只有set方法, 沒有g(shù)et方法
04-枚舉rawValue原理
-
枚舉原始值rawValue的本質(zhì)是:只讀計算屬性
enum TestEnum : Int {
case test1 = 1, test2 = 2, test3 = 3
var rawValue: Int {
switch self {
case .test1:
return 10
case .test2:
return 11
case .test3:
return 12
}
}
}
print(TestEnum.test3.rawValue) // 12
05-延遲存儲屬性(Lazy Stored Property)
- 使用
lazy可以定義一個延遲存儲屬性, 在第一次用到屬性的時候才會進行初始化 -
lazy屬性必須是var, 不能是let-
let必須在實例的初始化方法完成之前就擁有值
-
- 如果
多條線程同時第一次訪問lazy屬性- 無法保證屬性只被初始化1次 (
不是線程安全)
- 無法保證屬性只被初始化1次 (
class Car {
init() {
print("Car init!")
}
func run() {
print("Car is running!")
}
}
class Person {
lazy var car = Car()
// lazy let car1 = Car() // 延遲存儲屬性必須用var, error: 'lazy' cannot be used on a let
init() {
print("Person init!")
}
func goOut() {
car.run()
}
}
let p = Person() // 還沒用到car延遲屬性,因此此次不會創(chuàng)建car屬性
//Person init!
print("------")
p.goOut() // 用到car延遲屬性,此時才創(chuàng)建Car實例對象,調(diào)用Car init 方法
//Car init!
//Car is running
- 應(yīng)用場景
class PhotoView {
lazy var image: Image = {
let url = "https://upload-images.jianshu.io/upload_images/1253159-f30fb4cd197ff37d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"
let data = Data(url: url)
return Image(data: data)
}()
}
06-延遲存儲屬性注意點
- 當(dāng)
結(jié)構(gòu)體包含一個延遲存儲屬性時, 只有var才能訪問延遲存儲屬性- 因為
延遲存儲屬性初始化時需要改變結(jié)構(gòu)體的內(nèi)存
- 因為
struct Point {
var x = 0
var y = 0
lazy var z = 0
}
/*
let p = Point() // 此處用let,不能訪問延遲存儲屬性p.z
print(p.z) // error: cannot use mutating getter on immutable value: 'p' is a 'let' constant
*/
var p = Point() // 此處需用var, 才能正常訪問p.z
print(p.z)
07-屬性觀察器(Property Observer)
-
willSet會傳遞新值,默認(rèn)叫newValue -
didSet會傳遞舊值, 默認(rèn)叫oldValue - 在
初始化器中設(shè)置屬性值不會觸發(fā)willSet和didSet - 在
屬性定義時設(shè)置初始值也不會觸發(fā)willSet和didSet
- 可以為非
lazy的var 存儲屬性設(shè)置屬性觀察器
struct Circle {
var radius: Double {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, radius)
}
}
init() {
self.radius = 1.0
print("Circle init!")
}
}
// Circle init!
var circle = Circle()
// willSet 10.5
// didSet 1.0 10.5
circle.radius = 10.5
// 10.5
print(circle.radius)
08-全局變量、局部變量
屬性觀察器、計算屬性的功能,同樣可以應(yīng)用在全局變量、局部變量身上全局變量 計算屬性
// 全局變量 計算屬性 屬性觀察器應(yīng)用
var num: Int {
get {
return 10
}
set {
print("setNum", newValue)
}
}
num = 11 // setNum 11
print(num) // 10
- 局部變量 存儲屬性 屬性觀察器應(yīng)用
// 局部變量 存儲屬性 屬性觀察器應(yīng)用
func test() {
var age = 10 {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, age)
}
}
age = 11
// willSet 11
// didSet 10 11
}
test()
09-inout的再次研究
struct Shape {
var width: Int
var side: Int {
willSet {
print("willSetSide", newValue)
}
didSet {
print("didSetSide", oldValue, side)
}
}
var girth: Int {
set {
width = newValue / side
print("setGirth", newValue)
}
get {
print("getGirth")
return width * side
}
}
func show() {
print("width=\(width), side=\(side), girth=\(girth)")
}
}
func test(_ num: inout Int) {
num = 20
}
var s = Shape(width: 10, side: 4)
test(&s.width)
s.show()
print("---------")
test(&s.side)
s.show()
print("---------")
test(&s.girth)
s.show()
// 運行結(jié)果:
getGirth
width=20, side=4, girth=80
---------
willSetSide 20
didSetSide 4 20
getGirth
width=20, side=20, girth=400
---------
getGirth
setGirth 20
getGirth
width=1, side=20, girth=20

image.png
10-inout的本質(zhì)總結(jié)
- 如果
實參有物理內(nèi)存地址,且沒有設(shè)置屬性觀察器- 直接將
實參的內(nèi)存地址傳入函數(shù)(實參進行引用傳遞)
- 直接將
- 如果實參是
計算屬性或者 設(shè)置了屬性觀察器- 采取了
Copy In Copy Out的做法- 調(diào)用該函數(shù)是,先復(fù)制實參的值,產(chǎn)生
副本[get] - 將
副本的內(nèi)存地址傳入函數(shù)(副本進行引用傳遞),在函數(shù)內(nèi)部可以修改副本的值 - 函數(shù)返回后,再將副本的值覆蓋實參的值
[set]
- 調(diào)用該函數(shù)是,先復(fù)制實參的值,產(chǎn)生
- 采取了
- 總結(jié): inout的本質(zhì)就是引用傳遞(地址傳遞)
11-類型屬性(Type Property)
- 嚴(yán)格來說, 屬性可以分為
-
實例屬性(Instance Property): 只能通過實例去訪問-
存儲實例屬性(Stored Instance Property): 存儲在實例的內(nèi)存中,每個實例都有1份 -
計算實例屬性(Computed Instance Property)
-
- 類型屬性(Type Property): 只能通過類型去訪問
-
存儲類型屬性(Stored Type Property): 整個程序運行過程中,就只有1份內(nèi)存(類似于全局變量) -
計算類型屬性(Computed Type Property)
-
- 可以通過
static定義類型屬性- 如果是
類,也可以用關(guān)鍵字class
- 如果是
-
struct Car {
static var count: Int = 0
init() {
Car.count += 1
// 構(gòu)造器,類似實例方法,實例方法不能調(diào)用使用 count 或者self.count 調(diào)用類型屬性
// count += 1 // Static member 'count' cannot be used on instance of type 'Car'
}
}
let c1 = Car()
let c2 = Car()
let c3 = Car()
print(Car.count) // 3

12-類型屬性細節(jié)
-
存儲類型屬性默認(rèn)就是lazy, 會在第一次使用的時候才初始化- 就算被多個線程同時訪問,保證
只會初始化一次(線程安全) -
存儲類型屬性可以是let
- 就算被多個線程同時訪問,保證
-
枚舉類型也可以定義類型屬性(存儲類型屬性、計算類型屬性)
enum TestEnum : Int {
static var count: Int = 0 // 存儲類型屬性、計算類
case test1 = 1, test2 = 2
func plus() {
TestEnum.count += 1
}
static func getCount() -> Int { // 計算類型屬性
return count
}
}
var t1 = TestEnum.test1
t1.plus()
print(TestEnum.count)
t1.plus()
print(TestEnum.count)
t1.plus()
print(TestEnum.count)
print(TestEnum.getCount())
13-單例模式
public class FileManager {
public static let shared = FileManager() // 默認(rèn)是lazy 就算被多個線程同時訪問,保證`只會初始化一次`(線程安全)
private init() { }
}
public class FileManager {
public static let shared = { // 默認(rèn)是lazy 就算被多個線程同時訪問,保證`只會初始化一次`(線程安全)
// do something
return FileManager()
}()
private init() { }
}
let manager = FileManager.shared
iOS Swift 語法
底層原理與內(nèi)存管理分析 專題:【iOS Swift5語法】