詳解Swift的內(nèi)存管理
內(nèi)存管理
和OC一樣, 在Swift中也是采用基于引用計(jì)數(shù)的ARC內(nèi)存管理方案(針對(duì)堆空間的內(nèi)存管理)
在Swift的ARC中有三種引用
- 強(qiáng)引用(strong reference):默認(rèn)情況下,代碼中涉及到的引用都是強(qiáng)引用
- 弱引用(weak reference):通過(guò)weak定義弱引用
- 無(wú)主引用(unowned reference):通過(guò)unowned定義無(wú)主引用
weak
弱引用(weak reference):通過(guò)weak定義弱引用必須是可選類型的var,因?yàn)閷?shí)例銷毀后,ARC會(huì)自動(dòng)將弱引用設(shè)置為nilARC自動(dòng)給弱引用設(shè)置nil時(shí),不會(huì)觸發(fā)屬性觀察
在介紹weak弱引用之前, 先看一下下面一段代碼
class Animal {
deinit {
print("Animal deinit")
}
}
func test() {
let animal = Animal()
}
print("will deinit")
test()
print("did deinit")
上面這段代碼中在test函數(shù)調(diào)用結(jié)束之后, 該作用的內(nèi)存就會(huì)被回收,animal對(duì)象自然就會(huì)被銷毀, 毫無(wú)疑問(wèn)上面的輸出結(jié)果應(yīng)該是
will deinit
Animal deinit
did deinit
同樣下面這段代碼, 同樣也是在a1對(duì)象被置為nil的時(shí)候內(nèi)存會(huì)被回收, 對(duì)象就會(huì)被銷毀
var a1: Animal? = Animal()
print("will deinit")
a1 = nil
print("did deinit")
下面是一個(gè)被weak修飾的弱引用對(duì)象,
我們都知道, 被weak修飾的弱引用對(duì)象, 在對(duì)象銷毀的時(shí)候, 會(huì)被自動(dòng)置為nil
所以被weak修飾的弱引用對(duì)象必須是可選類型的var, 兩個(gè)條件缺一不可
weak var a2: Animal? = Animal() // 以下兩種方式都會(huì)報(bào)錯(cuò)的 weak var a2: Animal = Animal() weak let a2: Animal? = Animal()
unowned無(wú)主引用(unowned reference):通過(guò)unowned定義無(wú)主引用
不會(huì)產(chǎn)生強(qiáng)引用,實(shí)例銷毀后仍然存儲(chǔ)著實(shí)例的內(nèi)存地址(類似于OC中的unsafe_unretained)
試圖在實(shí)例銷毀后訪問(wèn)無(wú)主引用,會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤(如下野指針)
Fatal error: Attempted to read an unowned reference but object 0x0 was already deallocate
需要注意的是
weak、unowned只能用在類實(shí)例上面, 如下所示
// 該協(xié)議表示只能被類遵守, AnyObject代表所有的類實(shí)例
protocol Liveable: AnyObject {}
class Person {}
weak var p0: Person?
weak var p1: AnyObject?
// 所有能遵循Liveable協(xié)議的肯定都是類
weak var p2: Liveable?
unowned var p10: Person?
unowned var p11: AnyObject?
unowned var p12: Liveable?
循環(huán)引用
- weak、unowned都能解決循環(huán)引用的問(wèn)題,unowned要比weak少一些性能消耗
- 在生命周期中可能會(huì)變?yōu)閚il的使用weak
- 初始化賦值后再也不會(huì)變?yōu)閚il的使用unowne
- 說(shuō)道循環(huán)引用就自然想到了閉包
閉包的循環(huán)引用
閉包表達(dá)式默認(rèn)會(huì)對(duì)用到的外層對(duì)象產(chǎn)生額外的強(qiáng)引用(對(duì)外層對(duì)象進(jìn)行了retain操作), 看一下下面的代碼中deinit會(huì)被調(diào)用嗎?
class Person {
var fn: (() -> ())?
func run() { print("run") }
deinit { print("deinit") }
}
func test() {
let p = Person()
p.fn = {
p.run()
}
}
test()
上面代碼中,p對(duì)象強(qiáng)引用著fn閉包,fn閉包也強(qiáng)引用著p對(duì)象, 自然就造成了循環(huán)引用問(wèn)題
最后沒(méi)有任何輸出結(jié)果, 我們看一下上述代碼的匯編執(zhí)行過(guò)程

從上面匯編代碼可以看出, 整個(gè)過(guò)程經(jīng)歷了
一次init引用計(jì)數(shù)為: 1
一次retain引用計(jì)數(shù)會(huì)加(1), 結(jié)果為: 2
一次release引用計(jì)數(shù)會(huì)減(1), 結(jié)果為: 1
那么最后的引用計(jì)數(shù)就是1, 所以p對(duì)象肯定沒(méi)有被釋放
下面是使用解決循環(huán)引用的情況
在閉包表達(dá)式的捕獲列表里, 聲明weak或unowned引用,用以解決循環(huán)引用問(wèn)題
// 使用weak
func test() {
let p = Person()
p.fn = { [weak p] in
p?.run()
}
}
// 使用unowned
func test() {
let p = Person()
p.fn = { [unowned p] in
p.run()
}
}
上述兩種方式都可以解決循環(huán)引用的問(wèn)題, 運(yùn)行后就發(fā)現(xiàn)Person對(duì)象調(diào)用了deinit這里我們?cè)倏匆幌聟R編代碼如下, 從下面匯編代碼中可以很明顯看到, 引用計(jì)數(shù)最后為0, 對(duì)象被釋放

下面這段代碼其實(shí)是等價(jià)的
func test() {
let p = Person()
p.fn = { [unowned p] in
p.run()
}
}
// 和上面等價(jià)代碼
func test() {
let p = Person()
p.fn = { [unowned ownedP = p, weak weakP = p] in
ownedP.run()
// weakP?.run()
}
}
特別注意點(diǎn)
這里要區(qū)分捕獲列表和參數(shù)列表, 下面看看fn有參數(shù)的情況下
class Person {
var fn: ((Int) -> ())?
func run() { print("run") }
deinit { print("deinit") }
}
func test() {
let p = Person()
p.fn = {
(num) in
print("num = \(num)")
}
}
那么閉包的參數(shù)列表和捕獲列表同時(shí)存在的情況如下代碼所示
func test() {
let p = Person()
p.fn = {
[weak p](num) in
print("num = \(num)")
p?.run()
}
}
self的循環(huán)引用
如果想在引用閉包的同時(shí)引用self, 這個(gè)閉包必須是lazy的
因?yàn)閷?shí)例在初始化完畢之后才能引用self
class Person {
lazy var fn: (() -> ()) = {
self.run()
}
func run() { print("run") }
deinit { print("deinit") }
}
func test() {
let p = Person()
p.fn()
}
test()
上面代碼中如果fn閉包去掉lazy, 編譯器會(huì)直接報(bào)錯(cuò)在Swift中, 為了保證初始化的安全, 設(shè)定了兩段式初始化, 在所有的存儲(chǔ)屬性被初始化完成之后, 初始化器才能夠使用self而且在上述fn閉包中, 如果fn內(nèi)部用到了實(shí)例成員(屬性和方法), 則編譯器會(huì)強(qiáng)制要求明確寫出selflazy既保證只有在使用的時(shí)候才會(huì)被初始化一次但是上述代碼同樣存在循環(huán)引用的問(wèn)題,Person對(duì)象強(qiáng)引用著fn閉包,fn閉包也強(qiáng)引用著self同樣使用weak和unowned解決循環(huán)引用的問(wèn)題
// weak解決循環(huán)引用
lazy var fn: (() -> ()) = {
[weak self] in
self?.run()
}
// unowned解決循環(huán)引用
lazy var fn: (() -> ()) = {
[unowned self] in
self.run()
}
另外再看看下面這種情況, 是都存在循環(huán)引用的問(wèn)題
class Student {
var age: Int = 2
lazy var getAge: Int = {
self.age
}()
deinit { print("deinit") }
}
func test() {
let p = Student()
print(p.getAge)
}
test()
/* 輸出結(jié)果
2
deinit
*/
通過(guò)輸出結(jié)果看一看出調(diào)用了deinit, 說(shuō)明對(duì)象最終被釋放, 并未出現(xiàn)循環(huán)引用的問(wèn)題, 下面比較一下
// 存在循環(huán)引用
class Person {
lazy var fn: (() -> ()) = {
self.run()
}
func run() { print("run") }
deinit { print("deinit") }
}
// 不存在循環(huán)引用
class Student {
var age: Int = 2
lazy var getAge: Int = {
self.age
}()
deinit { print("deinit") }
}
上述兩種寫法的區(qū)別, 本質(zhì)上說(shuō)Person對(duì)象中的fn閉包屬于閉包賦值,Student對(duì)象那個(gè)中的getAge屬于閉包調(diào)用(類似函數(shù)調(diào)用),相當(dāng)于在在Student對(duì)象調(diào)用getAge結(jié)束之后, 作用域內(nèi)的變量就會(huì)被釋放
// getAge也可以寫成如下形式
lazy var getAge: Int = {
return self.age
}()
// 也可以理解為
lazy var getAge: Int = self.age
內(nèi)存訪問(wèn)沖突
在Swift中的內(nèi)存訪問(wèn)沖突主要在兩個(gè)訪問(wèn)滿足下列條件時(shí)發(fā)生
- 至少一個(gè)是寫入操作
- 它們?cè)L問(wèn)的是同一塊內(nèi)存
- 它們的訪問(wèn)時(shí)間重疊(比如在同一個(gè)函數(shù)內(nèi))
- 對(duì)比看看以下兩個(gè)函數(shù)操作
// 不存在內(nèi)存訪問(wèn)沖突
var number = 1
func plus(_ num: inout Int) -> Int {
return num + 1
}
number = plus(&number)
// 存在內(nèi)存訪問(wèn)沖突
var step = 1
func increment(_ num: inout Int) {
num += step
}
increment(&step)
上面第二部分代碼就是同時(shí)對(duì)step變量執(zhí)行讀寫操作, 運(yùn)行時(shí)會(huì)報(bào)出如下錯(cuò)誤
Simultaneous accesses to 0x100002028, but modification requires exclusive access.
再看下面對(duì)于結(jié)構(gòu)體和元組的使用, 這里先定義一個(gè)全局函數(shù)和一個(gè)結(jié)構(gòu)體
// 改變兩個(gè)傳入?yún)?shù)的值, 讀取并修改傳入?yún)?shù)的值
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
// 定義Player結(jié)構(gòu)體
struct Player {
var name: String
var health: Int
var energy: Int
mutating func shareHealth(with teammate: inout Player) {
balance(&teammate.health, &health)
}
}
再看下面的使用示例, 兩者都會(huì)有一個(gè)內(nèi)存訪問(wèn)沖突的錯(cuò)誤
// 這里讀寫的是同一個(gè)maria var maria = Player(name: "Maria", health: 50, energy: 10) balance(&maria.health, &maria.energy) // 這里讀寫的是同一個(gè)tuple var tuple = (health: 10, energy: 20) balance(&tuple.health, &tuple.energy)
但是有時(shí)候的確會(huì)有上面這種訪問(wèn)同一塊內(nèi)存的需求, 如果下面的條件滿足, 就說(shuō)明重疊訪問(wèn)結(jié)構(gòu)體的屬性是安全的
- 訪問(wèn)的是實(shí)例存儲(chǔ)屬性, 不是計(jì)算屬性或者類屬性
- 結(jié)構(gòu)體是局部變量而非全局變量
- 結(jié)構(gòu)體要么沒(méi)有被閉包捕獲要么只被非逃逸閉包捕獲
// 這里可以在局部作用域內(nèi)定義成局部變量, 就不會(huì)有問(wèn)題了
func test() {
var maria = Player(name: "Maria", health: 50, energy: 10)
var tuple = (health: 10, energy: 20)
balance(&tuple.health, &tuple.energy)
balance(&maria.health, &maria.energy)
}
指針
class Person {}
var person = Person()
- 在Swift中class聲明的類(Person)是引用類型, 初始化的person對(duì)象其本質(zhì)上就是一個(gè)指針變量
- 而person里面存儲(chǔ)的就是這個(gè)指針變量的地址值, 也就可以根據(jù)這個(gè)地址值去訪問(wèn)被分配的內(nèi)存空間
- 指針在某種意義上被定性為不安全的, 舉個(gè)例子:當(dāng)前指針變量的地址值對(duì)應(yīng)的空間只有32個(gè)字節(jié), 但有可能訪問(wèn)的是超過(guò)32個(gè)字節(jié)的空間, 這樣就可能會(huì)出問(wèn)題的
指針?lè)诸?/h3>
在Swift中也有專門的指針類型,這些都被定性為Unsafe(不安全的),常見(jiàn)的有以下4種類型
- UnsafePointer<Pointee>, 類似于C語(yǔ)言中的constPointee *, 只能訪問(wèn)內(nèi)存不能修改內(nèi)存, 這里的Pointee是指泛型
- UnsafeMutablePointer<Pointee>類似于C語(yǔ)言中的Pointee *, 可以訪問(wèn)和修改內(nèi)存, 這里的Pointee是指泛型
- UnsafeRawPointer類似于constvoid *, 不支持泛型
- UnsafeMutableRawPointer類似于void, 不支持泛型
下面看一下具體的使用示例
var age = 10
func sum1(_ ptr: UnsafeMutablePointer<Int>) {
// 通過(guò)訪問(wèn)pointee屬性, 獲取ptr指針的內(nèi)存地址所存儲(chǔ)的值
// UnsafeMutablePointer的pointee屬性是可讀可寫的
ptr.pointee += 10
}
func sum2(_ ptr: UnsafePointer<Int>) {
// UnsafePointer的pointee屬性是只讀的
// ptr.pointee += 10
print(ptr.pointee)
}
func sum3(_ num: inout Int) {
//
num += 10
}
// 和inout輸入輸出參數(shù)一樣接受變量的地址值
sum1(&age)
sum2(&age)
sum3(&age)
print(age)
func sum4(_ ptr: UnsafeMutableRawPointer) {
// 可讀可寫, 取值
print("age = ", ptr.load(as: Int.self))
// 可讀可寫, 賦值
ptr.storeBytes(of: 50, as: Int.self)
}
func sum5(_ ptr: UnsafeRawPointer) {
// 只讀, 取值
print("age = ", ptr.load(as: Int.self))
}
sum4(&age)
sum5(&age)
獲得變量的指針
Swift中有可以直接獲取變量的指針的方法
// 獲取可變的變量指針, value參數(shù)接受變量地址 @inlinable public func withUnsafeMutablePointer<T, Result>(to value: inout T, _ body: (UnsafeMutablePointer<T>) throws -> Result) rethrows -> Result // 獲取不可變的變量指針, value參數(shù)接受變量 @inlinable public func withUnsafePointer<T, Result>(to value: T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result // 獲取不可變的變量指針, value參數(shù)接受變量地址 @inlinable public func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
上述方法中返回值默認(rèn)是變量的指針地址, 也可以是其他的數(shù)據(jù)類型, 主要取決于body閉包的返回值, 返回值類型由閉包中的Result泛型決定
var age = 10
var ptr1 = withUnsafeMutablePointer(to: &age) { $0 } // UnsafeMutablePointer<Int>
var ptr2 = withUnsafePointer(to: &age) { $0 } // UnsafePointer<Int>
ptr1.pointee = 22
print(ptr2.pointee) // 22
print(ptr2) // 0x0000000100008310
var ptr3 = withUnsafeMutablePointer(to: &age) { UnsafeMutableRawPointer($0) } // UnsafeMutableRawPointer
var ptr4 = withUnsafePointer(to: &age) { UnsafeRawPointer($0) } // UnsafeRawPointer
// as參數(shù)是需要存儲(chǔ)什么類型的數(shù)據(jù)
ptr3.storeBytes(of: 33, as: Int.self)
print(ptr4.load(as: Int.self)) // 33
print(ptr4) // 0x0000000100008310
創(chuàng)建指針
- 之前獲取到的指針都是根據(jù)已經(jīng)存在的內(nèi)存獲取的
- 這里就看看重新分配一塊內(nèi)存指向堆空間
malloc
Swift提供了malloc直接分配內(nèi)存創(chuàng)建指針的方式
// 根據(jù)需要分配的內(nèi)存大小創(chuàng)建一個(gè)指針 public func malloc(_ __size: Int) -> UnsafeMutableRawPointer! // 釋放內(nèi)存 public func free(_: UnsafeMutableRawPointer!) // 下面這兩個(gè)函數(shù), 是賦值和取值的函數(shù), 之前簡(jiǎn)單介紹過(guò) // 參數(shù)一: 需要存儲(chǔ)的值 // 參數(shù)二: 偏移量, 從第幾個(gè)字節(jié)開(kāi)始存儲(chǔ), 默認(rèn)從第一個(gè) // 參數(shù)三: 需要存儲(chǔ)的值的類型 @inlinable public func storeBytes<T>(of value: T, toByteOffset offset: Int = 0, as: T.Type) // 參數(shù)一: 偏移量, 從第幾個(gè)字節(jié)開(kāi)始存儲(chǔ), 默認(rèn)從第一個(gè) // 參數(shù)二: 需要存儲(chǔ)的值的類型 @inlinable public func load<T>(fromByteOffset offset: Int = 0, as type: T.Type) -> T
代碼示例如下
// 創(chuàng)建指針 var ptr = malloc(16) // 存儲(chǔ)值 ptr?.storeBytes(of: 10, as: Int.self) // 這里toByteOffset參數(shù)如果傳0, 就會(huì)覆蓋前8個(gè)字節(jié)的數(shù)據(jù) ptr?.storeBytes(of: 12, toByteOffset: 8, as: Int.self) // 取值 print(ptr?.load(as: Int.self) ?? 0) print(ptr?.load(fromByteOffset: 8, as: Int.self) ?? 0) // 銷毀, 釋放內(nèi)存 free(ptr)
allocate
使用allocate方式創(chuàng)建指針, 代碼示例如下
// byteCount: 需要申請(qǐng)的字節(jié)數(shù), alignment: 對(duì)其字節(jié)數(shù) var ptr2 = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1) // 存儲(chǔ) ptr2.storeBytes(of: 9, as: Int.self) // 根據(jù)字節(jié)偏移存儲(chǔ) // 這里的ptr3是ptr2偏移8個(gè)字節(jié)的新的指針地址 var ptr3 = ptr2.advanced(by: 8) // UnsafeMutableRawPointer ptr3.storeBytes(of: 12, as: Int.self) // 上面這種方式等價(jià)于 ptr2.storeBytes(of: 12, toByteOffset: 8, as: Int.self) // 取值同樣 print(ptr2.load(as: Int.self)) // 下面這兩種取值方式也是一樣的 print(ptr2.advanced(by: 8).load(as: Int.self)) print(ptr2.load(fromByteOffset: 8, as: Int.self)) // 釋放內(nèi)存 ptr2.deallocate()
這里需要注意的地方
- 只有UnsafeMutableRawPointer才有allocate分配方法,UnsafeRawPointer是沒(méi)有這個(gè)方法的
- 下面說(shuō)到的UnsafeMutablePointer<T>類型也是,UnsafePointer<T>沒(méi)有allocate分配方法
// capacity: 容量, 即可以存儲(chǔ)3個(gè)Int類型的數(shù)據(jù), 也就是24個(gè)字節(jié) var ptr = UnsafeMutablePointer<Int>.allocate(capacity: 3) // 初始化內(nèi)存, 用10初始化錢8個(gè)字節(jié) ptr.initialize(to: 10) // 用10初始化前兩個(gè)容量的內(nèi)存, 即16個(gè)字節(jié) ptr.initialize(repeating: 10, count: 2) // 使用successor獲取下一個(gè)存儲(chǔ)位, 也就是下一個(gè)Int的位置 var ptr1 = ptr.successor() // UnsafeMutablePointer<Int> ptr1.initialize(to: 20) // 存儲(chǔ)第三個(gè)Int值 ptr.successor().successor().initialize(to: 30) // 取值的兩種方式 print(ptr.pointee) // 第一個(gè)值 print((ptr + 1).pointee) // 第二個(gè)值 print((ptr + 2).pointee) // 第三個(gè)值 // 下面這種方式和上面等價(jià) print(ptr[0]) print(ptr[1]) print(ptr[2]) // 前面如果使用了initialize, 則必須調(diào)用反初始化 // 而且count要和上面allocate(capacity: 3)的capacity一致, 否則會(huì)造成內(nèi)存泄露的問(wèn)題 ptr.deinitialize(count: 3) ptr.deallocate()
指針之間的轉(zhuǎn)換
前面提到過(guò)Swift中的指針類型有四種
- UnsafePointer<Pointee>類似于const Pointee *
- UnsafeMutablePointer<Pointee>類似于Pointee *
- UnsafeRawPointer類似于const void *
- UnsafeMutableRawPointer類似于void *
那么上面的類型, 能否通過(guò)其中的一種創(chuàng)建另外一種指針呢, 下面我們來(lái)看一下
init
UnsafeMutableRawPointer中有一個(gè)初始化方法可以根據(jù)UnsafeMutablePointer創(chuàng)建自身
public init<T>(_ other: UnsafeMutablePointer<T>) var ptr = UnsafeMutablePointer<Int>.allocate(capacity: 3) var ptr1 = UnsafeMutableRawPointer(ptr)
assumingMemoryBound
反過(guò)來(lái),UnsafeMutableRawPointer也提供了一個(gè)方法用于創(chuàng)建UnsafePointer
public func assumingMemoryBound<T>(to: T.Type) -> UnsafePointer<T> var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1) var ptr1 = ptr.assumingMemoryBound(to: Int.self) // 初始化前8個(gè)字節(jié) ptr1.pointee = 11 // 初始化后8個(gè)字節(jié) // 特別注意, 這里的(ptr + 8)是指ptr向后偏移8個(gè)字節(jié), 要和之前的區(qū)分開(kāi) (ptr + 8).assumingMemoryBound(to: Int.self).pointee = 12 ptr.deallocate()
unsafeBitCast
unsafeBitCast是忽略數(shù)據(jù)類型的強(qiáng)制轉(zhuǎn)換,不會(huì)因?yàn)閿?shù)據(jù)類型的變化而改變?cè)瓉?lái)的內(nèi)存數(shù)
// 把第一個(gè)參數(shù)類型轉(zhuǎn)成第二個(gè)參數(shù)類型 @inlinable public func unsafeBitCast<T, U>(_ x: T, to type: U.Type) -> U var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1) unsafeBitCast(ptr, to: UnsafeMutablePointer<Int>.self).pointee = 13 // 注意, 這里的(ptr + 8)是指ptr向后偏移8個(gè)字節(jié), 要和之前的區(qū)分開(kāi) unsafeBitCast(ptr + 8, to: UnsafeMutablePointer<Double>.self).pointee = 14.23 ptr.deallocate()
以上就是詳解Swift的內(nèi)存管理的詳細(xì)內(nèi)容,更多關(guān)于Swift內(nèi)存管理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
swift3.0 創(chuàng)建sqlite數(shù)據(jù)庫(kù)步驟方法
本篇文章主要介紹了swift3.0 創(chuàng)建sqlite數(shù)據(jù)庫(kù)步驟方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06
SpringBoot3.0集成Redis緩存的實(shí)現(xiàn)示例
緩存就是一個(gè)存儲(chǔ)器,常用 Redis作為緩存數(shù)據(jù)庫(kù),本文主要介紹了SpringBoot3.0集成Redis緩存的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03
詳解Swift語(yǔ)言中的類與結(jié)構(gòu)體
這篇文章主要介紹了Swift語(yǔ)言中的類與結(jié)構(gòu)體,是Swift入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-11-11
Swift使用enum抹平數(shù)組元素差異實(shí)例詳解
這篇文章主要為大家介紹了Swift使用enum抹平數(shù)組元素差異實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
Swift?Sequence?Collection使用示例學(xué)習(xí)
這篇文章主要為大家介紹了Swift?Sequence?Collection使用示例學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
本地推送通知UserNotifications在Swift中的實(shí)現(xiàn)方式
這篇文章主要介紹了本地推送通知UserNotifications在Swift中的實(shí)現(xiàn)方式,想了解消息推送的同學(xué),一定要看一下2021-04-04
Swift實(shí)現(xiàn)“或”操作符的3種方法示例
這篇文章主要給大家介紹了關(guān)于Swift實(shí)現(xiàn)“或”操作符的3種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
Swift實(shí)現(xiàn)監(jiān)聽(tīng)鍵盤通知及一些處理詳解
這篇文章主要給大家介紹了關(guān)于Swift實(shí)現(xiàn)監(jiān)聽(tīng)鍵盤通知及一些處理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01
Swift語(yǔ)言中字符串相關(guān)的基本概念解析
這篇文章主要介紹了Swift語(yǔ)言中字符串相關(guān)的基本概念解析,是Swift入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-11-11

