1. string拼接
- swift中拼接字符串有兩種方式
1.使用+號進(jìn)行兩個(gè)字符串的相加否副,但是要都是非可選類型的字符串,如:let str = "hello" + "world"
2.使用以下方法進(jìn)行拼接崎坊,但是如果拼接的字符串是可選類型备禀,會被默認(rèn)添加Optional
let str1 = "hello "
let str2:String? = "world"
print("\(str1)\(str2)")
//打印結(jié)果:hello Optional("world")
如果不希望被添加Optional,只要讓str2是非可選類型就可以了奈揍。
let str1 = "hello "
let str2:String? = "world"
//guard let str2 = str2 else { return } 這樣寫就不需要加 ?? "default"
print("\(str1)\(str2 ?? "default")")
//打印結(jié)果:hello world
- ?? : 是當(dāng)前面的值為nil時(shí)曲尸,則使用后面的默認(rèn)值。相當(dāng)于: str2 ?? "default" -> str2 != nil ? str2 : "default"
這篇文章有講?? 的實(shí)現(xiàn) @AUTOCLOSURE 和 ??
2.字典
- 在類中聲明初始化字典時(shí)男翰,盡量不包含中文,不要聲明過長另患。
- iOS瀏覽器的項(xiàng)目中有幾個(gè)地方出現(xiàn)過在初始化字典時(shí)出現(xiàn)崩潰。
- 像這種初始化有默認(rèn)值的字典時(shí)蛾绎,可以考慮用plist文件存昆箕,然后懶加載plist文件中的數(shù)據(jù)。
3.dispatch_once
- swift3中dispatch_once方法被廢棄了租冠,所以在swift中推薦的單例的寫法:
static let shared = MyClass()
fileprivate init() { }
- 或者自己對DispatchQueue進(jìn)行拓展dispatch_once方法,比如:
public extension DispatchQueue {
private static var _onceTracker = [String]()
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token: String, block:()->Void) {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
if _onceTracker.contains(token) {
return
}
_onceTracker.append(token)
block()
}
}
4. @discardableResult
- 如果一個(gè)方法是返回值鹏倘,但是你調(diào)用的時(shí)候沒有使用其返回值編譯時(shí)會報(bào)警告,
可以在方法前添加@discardableResult對方法進(jìn)行修飾則可以消除警告顽爹。
5.@escaping逃逸閉包
- 在之前的swift版本中纤泵,一個(gè)函數(shù)的參數(shù)的閉包的捕捉策略默認(rèn)就是@escape,所以如果要明確這個(gè)閉包是一個(gè)非逃逸閉包需要顯示的添加聲明@noescape镜粤。簡單的說就是如果這個(gè)閉包是在這個(gè)函數(shù)結(jié)束前就會被調(diào)用捏题,就是非逃逸的玻褪,即noescape。
- 如果這個(gè)閉包是在函數(shù)執(zhí)行完后才被調(diào)用涉馅,調(diào)用的地方超過了這函數(shù)的范圍归园,所以叫逃逸閉包黄虱。
- swift3已經(jīng)將@noescape廢棄了稚矿,改成了函數(shù)的參數(shù)的閉包的捕捉策略默認(rèn)就是@noescape,如果這個(gè)閉包是逃逸的則需要在閉包參數(shù)前添加@escape捻浦。
- 需要添加逃逸@escaping的情況有:
var myBlock: ()->()?
func test(_ block: @escaping ()->()) {
self.myBlock = block
DispatchQueue.global().async {
block()
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(0.1 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) {
block()
}
}
-
注意點(diǎn):關(guān)于 @escaping 最后還有一點(diǎn)想要說明晤揣。如果你在協(xié)議或者父類中定義了一個(gè)接受 @escaping 為參數(shù)方法,那么在實(shí)現(xiàn)協(xié)議和類型或者是這個(gè)父類的子類中朱灿,對應(yīng)的方法也必須被聲明為 @escaping昧识,否則兩個(gè)方法會被認(rèn)為擁有不同的函數(shù)
6.defer延時(shí)調(diào)用
- 被defer包裹的代碼塊會在其作用域結(jié)束時(shí)被調(diào)用,可以用在確保退出時(shí)盗扒,將某些操作結(jié)束跪楞,常見的是:打開數(shù)據(jù)庫后,將關(guān)閉數(shù)據(jù)庫的代碼寫在defer中侣灶,這樣可以確保不管什么情況下數(shù)據(jù)庫都能被關(guān)閉甸祭。
大家可以看看下面這段代碼,打印的順序是什么樣的褥影?
class A: NSObject {
class func doSomething() {
defer {
print("firest defer!")
}
defer {
print("second defer!")
}
if true {
defer {
print("third defer!")
}
}
print("will return")
return test()
}
class func test() {
print("test func")
}
}
A.doSomething()
//打印結(jié)果:
third defer!
will return
test func
second defer!
firest defer!
從上面的打印可以看出池户,defer代碼塊會被壓入棧中,待函數(shù)結(jié)束時(shí)彈出棧運(yùn)行凡怎,并且是在return代碼執(zhí)行后執(zhí)行的校焦。
7.lazy
swift3.0除了給屬性進(jìn)行懶加載外,還可以配合像map或者filter這類接受閉包并進(jìn)行運(yùn)行的方法一起, 讓整個(gè)行為變成延時(shí)進(jìn)行的. 在某些情況下這么做也對性能會有不小的幫助. 例如, 直接使用map時(shí):
let data = [1,2,3]
let result = data.map {
(i: Int) -> Int in
print("正在處理\(i)")
return i * 2
}
print("準(zhǔn)備訪問結(jié)果")
for i in result {
print("操作后結(jié)果為\(i)")
}
print("操作完畢")
這么做的輸出為:
//正在處理1
//正在處理2
//正在處理3
//準(zhǔn)備訪問結(jié)果
//操作后結(jié)果為2
//操作后結(jié)果為4
//操作后結(jié)果為6
//操作完畢
如果添加上lazy的話:
let data = [1,2,3]
let result = data.lazy.map {
(i: Int) -> Int in
print("正在處理\(i)")
return i * 2
}
print("準(zhǔn)備訪問結(jié)果")
for i in result {
print("操作后結(jié)果為\(i)")
}
print("操作完畢")
/*
此時(shí)的運(yùn)行結(jié)果
//準(zhǔn)備訪問結(jié)果
//正在處理1
//操作后結(jié)果為2
//正在處理2
//操作后結(jié)果為4
//正在處理3
//操作后結(jié)果為6
//操作完畢
如果只取result中其中某一個(gè)值统倒,那么只會會要取的值進(jìn)行處理寨典。
對于那些不需要完全運(yùn)行, 可能提前退出的情況, 使用lazy來進(jìn)行性能優(yōu)化效果會非常有效.
8.class 與 static
這里直接看這篇文章去查看其中的區(qū)別。 STATIC 和 CLASS
- 另外補(bǔ)充一點(diǎn)就是:
-static修飾的不能被子類重寫房匆,class修飾的才能被子類重寫耸成,所以除了當(dāng)你需要被子類重寫的時(shí)候外,大多數(shù)情況都可以直接使用static
9.mutating
- 默認(rèn)情況下坛缕,(struct墓猎、enum)的實(shí)例方法中是不可以修改值類型(struct、enum)的屬性赚楚。
- Swift 的 protocol 不僅可以被 class 類型實(shí)現(xiàn)毙沾,也適用于 struct 和 enum
- 因此在協(xié)議中定義接口的時(shí)候最好都加上mutating進(jìn)行修飾,如果你沒在接口方法里寫 mutating 的話宠页,別人如果用 struct 或者 enum 來實(shí)現(xiàn)這個(gè)接口的話左胞,就不能在方法里改變自己的變量了寇仓。
- 在使用 class 來實(shí)現(xiàn)帶有 mutating 的方法的接口時(shí),具體實(shí)現(xiàn)的前面是不需要加 mutating 修飾的烤宙,因?yàn)?class 可以隨意更改自己的成員變量遍烦。所以說在接口里用 mutating 修飾方法,對于 class 的實(shí)現(xiàn)是完全透明躺枕,可以當(dāng)作不存在的服猪。
值類型的實(shí)例方法中,也可以直接修改self屬性值拐云。
struct Point {
var x: Int
var y: Int
func moveToPoint(point: Point) {
self.x = point.x // 報(bào)錯(cuò):不能對值類型的屬性進(jìn)行修改
self.y = point.y // 報(bào)錯(cuò):不能對值類型的屬性進(jìn)行修改
}
mutating func moveToPoint2(point: Point) {
self.x = point.x // 編譯通過
self.y = point.y // 編譯通過
}
//可變方法還可以對self進(jìn)行修改罢猪,這個(gè)方法和moveToPoint2效果相同
mutating func moveToPoint3(x deltaX: Int, y deltaY: Int) {
self = Point(x:deltaX, y:deltaY)
}
}
10.subscript
- swift 還可以給class、struct叉瘩、enum添加類似字典和數(shù)組的自定義下標(biāo)訪問方法膳帕。
- subscript例子看這里。
11.內(nèi)聯(lián)序列函數(shù)sequence
- 這里也直接看這個(gè)網(wǎng)址Swift - 內(nèi)聯(lián)序列函數(shù)sequence介紹(附樣例)
12.weak和unowned
不管在什么語言里, 內(nèi)存管理的內(nèi)容都很重要薇缅。
Swift是自動管理內(nèi)存的, 這也就是說, 我們不再需要操心內(nèi)存的申請和分配. 當(dāng)我們通過初始化創(chuàng)建一個(gè)對象時(shí), Swift會替我們管理和分配內(nèi)存. 而釋放的原則遵循了自動引用計(jì)數(shù)(ARC)的規(guī)則: 當(dāng)一個(gè)對象沒有引用的時(shí)候, 其內(nèi)存將會被自動回收. 這套機(jī)制從很大程度上簡化了我們的編碼, 我們只需要保證在合適的時(shí)候?qū)⒁弥每?比如超過作用域, 或者手動設(shè)為nil等, 就可以確保內(nèi)存使用不出現(xiàn)問題.)
但是, 所有的自動引用計(jì)數(shù)機(jī)制都有一個(gè)從理論上無法繞過的限制, 那就是循環(huán)引用(retain cycle)的情況.
什么是循環(huán)引用
雖然我覺得循環(huán)引用這樣的概念介紹不太因該出現(xiàn)在這本書中, 但是為了更清晰地解釋Swift中的循環(huán)引用的一般情況, 這里還是簡單進(jìn)行說明. 假設(shè)我們有兩個(gè)類A和B, 它們之中分別有一個(gè)存儲屬性持有對方:
class A: NSObject {
let b: B
override init() {
b = B()
super.init()
b.a = self
}
deinit {
print("A deinit")
}
}
class B: NSObject {
var a: A? = nil
deinit {
print("B deinit")
}
}
在A的初始化方法中, 我們生成了一個(gè)B的實(shí)例并將其存儲在屬性中. 然后我們又將A的實(shí)例復(fù)制給了b.a. 這樣a.b和b.a將在初始化的時(shí)候形成一個(gè)引用循環(huán). 現(xiàn)在當(dāng)有第三方的調(diào)用初始化了A, 然后及時(shí)立即將其釋放, A和B兩個(gè)類實(shí)例的deinit方法也不會被調(diào)用, 說明它們并沒有被釋放.
var obj: A? = A()
obj = nil
// 內(nèi)存沒有釋放
因?yàn)榧磿r(shí)obj不再持有A的這個(gè)對象, b中的b.a依然引用著這個(gè)對象, 導(dǎo)致它無法釋放. 而進(jìn)一步, a中也持有著b, 導(dǎo)致b也無法釋放. 在將obj設(shè)為nil之后, 我們在代碼里再也拿不到對于這個(gè)對象的引用了, 所以除非是殺掉整個(gè)進(jìn)程, 我們已經(jīng)永遠(yuǎn)也無法將它釋放了. 多么悲傷的故事啊..
在Swift里防止循環(huán)引用
為了防止這種人神共憤的悲劇發(fā)生, 我們必須給編譯器一點(diǎn)提示, 表明我們不希望它們互相持有. 一般來說我們習(xí)慣希望"被動"的一方不要去持有"主動"的一方. 在這里b.a里對A的實(shí)例的持有是由A的方法設(shè)定的, 我們在之后直接使用的也是A的實(shí)例, 因此認(rèn)為b是被動的一方. 可以將上面的class B的聲明改為:
class B: NSObject {
weak var a: A? = nil
deinit {
print("B deinit")
}
}
在var a前面加上了weak, 向編譯器說明我們不希望持有a. 這時(shí), 當(dāng)obj指向nil時(shí), 整個(gè)環(huán)境中就沒有對A的這個(gè)實(shí)例的持有了, 于是這個(gè)實(shí)例可以得到釋放. 接著, 這個(gè)被釋放的實(shí)例上對b的引用a.b也隨著這次釋放結(jié)束了作用域, 所以b的引用也將歸零, 得到釋放. 添加weak后的輸出:
A deinit
B deinit
可能有心的朋友已經(jīng)注意到, 在Swift中除了weak以外, 還有另一個(gè)沖著編譯器叫喊著類似的"不要引用我"的標(biāo)識符, 那就是unowned. 它們的區(qū)別在哪里呢? 如果您一直寫Objective-C過來的, 那么從表面的行為上來說unowned更像以前的unsafe_unretained, 而weak就是以前的weak. 用通俗的話說, 就是unowned設(shè)置以后即使它原來引用的內(nèi)容已經(jīng)被釋放了, 它仍然會保持對被已經(jīng)釋放了的對象的一個(gè)"無效的"引用, 它不能是Optional值, 也不會被指向nil. 如果你嘗試調(diào)用這個(gè)引用的方法或者訪問成員屬性的話, 程序就會崩潰. 而weak則友好一些, 在引用的內(nèi)容被釋放后, 標(biāo)記為weak的成員將自動地變成nil(因此被標(biāo)記為@weak的變量一定需要時(shí)Optional值). 關(guān)于兩者使用的選擇, Apple給我們的建議是如果能夠確定在訪問時(shí)不會已被釋放的話, 盡量使用unowned, 如果存在被釋放的可能, 那就選擇用weak.
我們結(jié)合實(shí)際編碼中的使用來看看選擇吧. 日常工作中一般使用弱引用的最常見的場景有兩個(gè):
設(shè)置delegate時(shí)
在self屬性存儲為閉包時(shí), 其中擁有對self引用時(shí)
前者是Cocoa框架的常見設(shè)計(jì)模式, 比如我們有一個(gè)負(fù)責(zé)網(wǎng)絡(luò)請求的類, 它實(shí)現(xiàn)了發(fā)送請求以及接受請求結(jié)果的任務(wù), 其中這個(gè)結(jié)果是通過實(shí)現(xiàn)請求類的protocol的方式來實(shí)現(xiàn)的, 這種時(shí)候我們一般設(shè)置delegate為weak:
// RequestManager.swift
class RequestManager: RequestHandler {
@objc func requestFinished() {
print("請求完成")
}
func sendRequest() {
let req = Request()
req.delegate = self
req.send()
}
}
// Request.swift
@objc protocol RequestHandler {
@objc optional func requestFinished()
}
class Request {
weak var delegate: RequestHandler!
func send() {
//發(fā)送請求
//一般來說會將req的引用傳遞給網(wǎng)絡(luò)框架
}
func gotResponse() {
//請求返回
delegate?.requestFinished?()
}
}
req中以weak的方式持有了delegate, 因?yàn)榫W(wǎng)絡(luò)請求是一個(gè)異步過程, 很可能會遇到用戶不愿意等待而選擇放棄的情況. 這種情況下一般都會講RequestManager進(jìn)行清理, 所以我們其實(shí)是無法保證在拿到返回時(shí)作為delegate的RequestManager對象是一定存在的. 因此我們使用了weak而非unowned, 并在調(diào)用前進(jìn)行了判斷.
閉包和循環(huán)引用
另一種閉包的情況稍微復(fù)雜一些: 我們首先要知道, 閉包中對任何其他元素的引用都是會被閉包自動是有的. 如果我們在閉包中寫了self這樣的東西話, 那我們其實(shí)也就在閉包內(nèi)持有了當(dāng)前的對象. 這里就出現(xiàn)了一個(gè)在實(shí)際開發(fā)中比較隱蔽的陷阱: 如果當(dāng)前的實(shí)例直接或者間接地對這個(gè)閉包又有引用的話, 就形成了一個(gè)self->閉包->self的循環(huán)引用. 最簡單的例子是, 我們聲明了一個(gè)閉包用來以特定的形式打印self中的一個(gè)字符串:
class Person {
let name: String
lazy var printName: () -> () = {
print("The name is /(self.name)")
}
init(personName: String) {
name = personName
}
deinit {
print("Person deinit /(self.name)")
}
}
var xiaoMing: Person? = Person(personName: "XiaoMing")
xiaoMing!.printName()
xiaoMing = nil
// 輸出:
// The name is XiaoMing. 沒有被釋放
printName是self的屬性, 會被self持有, 而它本身又在閉包內(nèi)持有self, 這導(dǎo)致了xiaoMing的deinit在自身超過作用域后還是沒有被調(diào)用, 也就是沒有被釋放. 為了解決這種閉包內(nèi)的循環(huán)引用, 我們需要在閉包開始的時(shí)候添加一個(gè)標(biāo)注, 來表示這個(gè)閉包內(nèi)的某些要素應(yīng)該以何種特定的方式來使用. 可以將printName修改為這樣:
lazy var printName: () -> () = {
[weak self] in
if let strongSelf = self {
print("The name is /(strongSelf.name)")
}
}
現(xiàn)在內(nèi)存釋放就正確了:
// 輸出:
// The name is XiaoMing
// Person deinit XiaoMing
如果我們可以確定在整個(gè)過程中self不會被釋放的話, 我們可以將上面的weak改為unowned, 這樣就不再需要strongSelf的判斷. 但是如果在過程中self被釋放了而printName這個(gè)閉包沒有被釋放的話(比如 生成Person后, 某個(gè)外部變量持有了printName, 隨后這個(gè)Person 對象被釋放了, 但是printName已然存在并可能被調(diào)用), 使用unowned將造成崩潰. 在這里我們需要根據(jù)實(shí)際的需求來決定是使用weak還是unowned.
這種在閉包參數(shù)的位置進(jìn)行標(biāo)注的語法結(jié)構(gòu)是將要標(biāo)注的內(nèi)容放在原來參數(shù)的前面, 并使用中括號括起來. 如果有多個(gè)需要標(biāo)注的元素的話, 在同一個(gè)中括號內(nèi)用逗號隔開, 舉個(gè)例子:
//標(biāo)注前
{ (number: Int) -> Bool in
//...
return true
}
//標(biāo)注后
{ [unowned self, weak someObject] (number: Int) -> Bool in
//...
return true
}
13.curry特性
其他內(nèi)容
- 蘋果專門給swift開的博客
- swift版本升級的改動 apple/swift-evolution
- 想看更多swift相關(guān)的知識點(diǎn)可以在- Swifter-Swift 必備tips 這里看看危彩,文章中很多都能在這里面找到。
- 還有Swift的編寫風(fēng)格可以看這里:Swift Style Guide