如下內容均是筆者學習過程當中收集的知識點,順序比較跳躍,初衷是爲了方便查閱,順便加深記憶。內容會不斷更新,若是有什麼問題或者有好的 Swift 方面的語法糖或者知識點也能夠提出來,我會挑選斟酌後收錄,歡迎你們關注~git
環境:github
Swift 4.0
Xcode 9.1
Objective-C 的 runtime 裏的 Associated Object 容許咱們在使用 Category 擴展示有的類的功能的時候,直接添加實例變量。在 Swift 中 extension 不能添加存儲屬性,咱們能夠利用 Associated Object 來實現,好比下面的 title
「實際上」是一個存儲屬性:shell
// MyClass.swift class MyClass {} // MyClassExtension.swift private var key: Void? extension MyClass { var title: String? { get { return swift_getAssociatedObject(self, &key) as? String } set { swift_setAssociatedObject(self, &key, newValue, .swift_ASSOCIATION_RETAIN_NONATOMIC) } } }
// 測試 func printTitle(_ input: MyClass) { if let title = input.title { print("Title: \(title)") } else { print("沒有設置") } } let a = MyClass() printTitle(a) a.title = "Swifter.tips" printTitle(a) // 輸出: // 沒有設置 // Title: Swifter.tips」
Swift 中 Delegate 須要被聲明成 weak
,來避免訪問到已被回收的內存而致使崩潰,若是咱們像下面這樣,是編譯不過的:express
protocol MyClassDelegate { func method() } class MyClass { weak var delegate: MyClassDelegate? } class ViewController: UIViewController, MyClassDelegate { // ... var someInstance: MyClass! override func viewDidLoad() { super.viewDidLoad() someInstance = MyClass() someInstance.delegate = self } func method() { print("Do something") } //... } // 編譯失敗 // 'weak' may only be applied to class and class-bound protocol types, not 'MyClassDelegate'
這是由於 Swift 的 protocol 是能夠被除了 class 之外的其餘類型遵照的,而對於像 struct
或是 enum
這樣的類型,自己就不經過引用計數來管理內存,因此也不可能用 weak
這樣的 ARC 的概念來進行修飾。json
想要在 Swift 中使用 weak delegate,咱們就須要將 protocol 限制在 class 內:swift
@objc
關鍵字來達到,Objective-C 的 protocol 都只有類能實現,所以使用 weak 來修飾就合理了:@objc protocol MyClassDelegate { func method() }
class
,這能夠爲編譯器顯式地指明這個 protocol 只能由 class
來實現,避免了過多的沒必要要的 Objective-C 兼容:protocol MyClassDelegate: class { func method() }
Objective-C 中的 protocol 裏存在 @optional
關鍵字,被這個關鍵字修飾的方法並不是必需要被實現,原生的 Swift protocol 裏沒有可選項,全部定義的方法都是必須實現的,若是不是實現是沒法編譯的:數組
class ViewController: UIViewController,MyProtocol { } // 編譯失敗 // Type 'ViewController' does not conform to protocol 'MyProtocol'
若是咱們想要像 Objective-C 裏那樣定義可選的協議方法,就須要將協議自己和可選方法都定義爲 Objective-C 的,也即在 protocol 定義以前加上 @objc
,方法以前加上 @objc optional
:安全
@objc protocol MyProtocol { @objc optional func myMethod() }
另外,對於全部的聲明,它們的前綴修飾是徹底分開的,也就是說你不能像是在 Objective-C 裏那樣用一個 @optional
指定接下來的若干個方法都是可選的了,必須對每個可選方法添加前綴,對於沒有前綴的方法來講,它們是默認必須實現的:性能優化
@objc protocol MyProtocol { @objc optional func optionalMethod() // 可選 func necessaryMethod() // 必須 @objc optional func anotherOptionalMethod() // 可選 }
一個不可避免的限制是,使用 @objc
修飾的 protocol 就只能被 class
實現了,也就是說,對於 struct
和 enum
類型,咱們是沒法令它們所實現的協議中含有可選方法或者屬性的。另外,實現它的 class
中的方法還必須也被標註爲 @objc
,或者整個類就是繼承自 NSObject
。對於這種問題,在 Swift 2.0 中,咱們有了另外一種選擇,那就是使用 protocol extension。咱們能夠在聲明一個 protocol 以後再用 extension 的方式給出部分方法默認的實現,這樣這些方法在實際的類中就是可選實現的了:閉包
protocol MyProtocol { func optionalMethod() // 可選 func necessaryMethod() // 必須 func anotherOptionalMethod() // 可選 } extension MyProtocol { //默認的可選實現 func optionalMethod() { print("optionalMethod") } //默認的可選實現 func anotherOptionalMethod() { print("anotherOptionalMethod") } }
class ViewController: UIViewController,MyProtocol { // 必須的實現 func necessaryMethod() { print("necessaryMethod") } override func viewDidLoad() { super.viewDidLoad() self.optionalMethod(); self.necessaryMethod(); self.anotherOptionalMethod(); } } // 輸出: // optionalMethod // necessaryMethod // necessaryMethod
Swift 中的單例很是簡單,Swift 1.2 以及以後:
class Singleton { static let sharedInstance = Singleton() private init() {} }
這種寫法不可是線程安全的,也是懶加載的,let
定義的屬性自己就是線程安全的,同時 static
定義的是一個 class constant,擁有全局做用域和懶加載特性。
另外,這個類型中加入了一個私有的初始化方法,來覆蓋默認的公開初始化方法,這讓項目中的其餘地方不可以經過 init 來生成本身的 Singleton
實例,也保證了類型單例的惟一性。若是你須要的是相似 default 的形式的單例 (也就是說這個類的使用者能夠建立本身的實例) 的話,能夠去掉這個私有的 init
方法。
在 Objective-C 中的 %@
這樣的格式在指定的位置設定佔位符,而後經過參數的方式將實際要輸出的內容補充完整。例如 Objective-C 中經常使用的向控制檯輸出的 NSLog
方法就使用了這種格式化方法:
float a = 1.234567; NSString *b = @"Helllo"; NSLog(@"float:%.2f str:%p",a,b); // 輸出: // float:1.23 str:0x1024a1078
對應 Swift 中咱們能夠這樣:
let a = 1.234567 let b = "Helllo" let c = String(format:"float:%.2f str:%p",a,b) print(c) // 輸出: // float:1.23 str:0x604000249e10
@selector
是 Objective-C 時代的一個關鍵字,它能夠將一個方法轉換並賦值給一個 SEL 類型,它的表現很相似一個動態的函數指針。在 Swift 中沒有 @selector
了,取而代之,從 Swift 2.2 開始咱們使用 #selector
來從暴露給 Objective-C 的代碼中獲取一個 selector
,而且由於 selector
是 Objective-C runtime 的概念,在 Swift 4 中,默認狀況下全部的 Swift 方法在 Objective-C 中都是不可見的,因此你須要在這類方法前面加上 @objc
關鍵字,將這個方法暴露給 Objective-C,才能進行使用:
let btn = UIButton.init(type: .system) btn.backgroundColor = UIColor.red btn.frame = CGRect(x: 100, y: 100, width: 150, height: 40) btn.setTitle("Button", for: .normal) //無參數 btn.addTarget(self, action: #selector(btnClick), for: .touchUpInside) view.addSubview(btn) @objc func btnClick() { print("button click !") }
... //有參數 btn.addTarget(self, action: #selector(btnClick(_ :)), for: .touchUpInside) ... @objc func btnClick(_ button: UIButton) { print("button click !") }
Swift 的 protocol 不只能夠被 class 類型實現,也適用於 struct
和 enum
,由於這個緣由,咱們在寫給別人用的協議時須要多考慮是否使用 mutating
來修飾方法。Swift 的 mutating
關鍵字修飾方法是爲了能在該方法中修改 struct
或是 enum
的變量,因此若是你沒在協議方法裏寫 mutating
的話,別人若是用 struct
或者 enum
來實現這個協議的話,就不能在方法裏改變本身的變量了,好比下面的代碼是編譯不過的:
protocol Vehicle { func changeColor() } struct MyCar: Vehicle { var color = "blue" func changeColor() { color = "red" } } // 編譯失敗 // Cannot assign to property: 'self' is immutable
咱們應該加上 mutating
關鍵字:
protocol Vehicle { mutating func changeColor() } struct MyCar: Vehicle { var color = "blue" mutating func changeColor() { color = "red" } } override func viewDidLoad() { super.viewDidLoad() var car = MyCar() print(car.color) car.changeColor() print(car.color) } // 輸出: // blue // 輸出: // red
使用 NSArray 時一個很常碰見的的需求是在枚舉數組內元素的同時也想使用對應的下標索引,在 Objective-C 中最方便的方式是使用 NSArray 的 enumerateObjectsUsingBlock:
,在 Swift 中存在一個效率,安全性和可讀性都很好的替代,那就是快速枚舉某個數組的EnumerateGenerator
,它的元素是同時包含了元素下標索引以及元素自己的多元組:
let arr = ["a","b","c","d","e"] for (idx, str) in arr.enumerated() { print("idx: \(idx) str: \(str)") } // 輸出: idx: 0 str: a idx: 1 str: b idx: 2 str: c idx: 3 str: d idx: 4 str: e
函數參數默認是常量,若是試圖在函數體中更改參數值將會致使編譯錯誤,好比下面的例子中嘗試着交換值:
func swapTwoInts(_ a: Int, _ b: Int) { let temporaryA = a a = b b = temporaryA } // 編譯失敗 // Cannot assign to value: 'a' is a 'let' constant // Cannot assign to value: 'b' is a 'let' constant
若是想要一個函數能夠修改參數的值,而且想要在這些修改在函數調用結束後仍然存在,那麼就應該把這個參數定義爲輸入輸出參數(In-Out Parameters):
func swapTwoInts(_ a: inout Int, _ b: inout Int) { let temporaryA = a a = b b = temporaryA }
Swift 的方法是支持默認參數的,也就是說在聲明方法時,能夠給某個參數指定一個默認使用的值。在調用該方法時要是傳入了這個參數,則使用傳入的值,若是缺乏這個輸入參數,那麼直接使用設定的默認值進行調用。和其餘不少語言的默認參數相比較,Swift 中的默認參數限制更少,並無所謂 "默認參數以後不能再出現無默認值的參數"這樣的規則,舉個例子,下面兩種方法的聲明在 Swift 裏都是合法可用的:
func sayHello1(str1: String = "Hello", str2: String, str3: String) { print(str1 + str2 + str3) } func sayHello2(str1: String, str2: String, str3: String = "World") { print(str1 + str2 + str3) }
sayHello1(str2: " ", str3: "World") sayHello2(str1: "Hello", str2: " ") //輸出都是 Hello World
延時加載或者說延時初始化是很經常使用的優化方法,在構建和生成新的對象的時候,內存分配會在運行時耗費很多時間,若是有一些對象的屬性和內容很是複雜的話,這個時間更是不可忽略。另外,有些狀況下咱們並不會當即用到一個對象的全部屬性,而默認狀況下初始化時,那些在特定環境下不被使用的存儲屬性,也同樣要被初始化和賦值,也是一種浪費。在 Objective-C 中,一個延遲加載通常是這樣的:
// ClassA.h @property (nonatomic, copy) NSString *testString; // ClassA.m - (NSString *)testString { if (!_testString) { _testString = @"Hello"; NSLog(@"只在首次訪問輸出"); } return _testString; }
對應在 Swift 中,使用 lazy
做爲屬性修飾符時,只能聲明屬性是變量,且咱們須要顯式地指定屬性類型,不然會編譯錯誤:
class ClassA { lazy let str: String = { let str = "Hello" print("只在首次訪問輸出") return str }() } // 編譯失敗 // 'lazy' cannot be used on a let class ClassA { lazy var str = { let str = "Hello" print("只在首次訪問輸出") return str }() } // 編譯失敗 // Unable to infer complex closure return type
咱們應該聲明爲 var
並指定好類型:
class ClassA { lazy var str: String = { let str = "Hello" print("只在首次訪問輸出") return str }() } override func viewDidLoad() { super.viewDidLoad() let ca = ClassA() print(ca.str) print(ca.str) print(ca.str) } // 輸出: // 只在首次訪問輸出 // Hello // Hello // Hello
若是不須要作什麼額外工做的話,也能夠對這個 lazy
的屬性直接寫賦值語句:
lazy var str: String = "Hello"
咱們還能夠利用 lazy
配合像 map
或是 filter
這類接受閉包並進行運行的方法一塊兒,讓整個行爲變成延時進行的。在某些狀況下這麼作也對性能會有不小的幫助。例如,直接使用 map 時:
let data = 1...3 let result = data.map { (i: Int) -> Int in print("正在處理 \(i)") return i * 2 } print("準備訪問結果") for i in result { print("操做後結果爲 \(i)") } print("操做完畢") // 輸出: // 正在處理 1 // 正在處理 2 // 正在處理 3 // 準備訪問結果 // 操做後結果爲 2 // 操做後結果爲 4 // 操做後結果爲 6 // 操做完畢
而若是咱們先進行一次 lazy
操做的話,咱們就能獲得延時運行版本的容器:
let data = 1...3 let result = data.lazy.map { (i: Int) -> Int in print("正在處理 \(i)") return i * 2 } print("準備訪問結果") for i in result { print("操做後結果爲 \(i)") } print("操做完畢") // 準備訪問結果 // 正在處理 1 // 操做後結果爲 2 // 正在處理 2 // 操做後結果爲 4 // 正在處理 3 // 操做後結果爲 6 // 操做完畢
對於那些不須要徹底運行,可能提早退出的狀況,使用 lazy 來進行性能優化效果會很是有效。
在 Objective-C 中,咱們常常在代碼中插入 #param
符號來標記代碼的區間,這樣在 Xcode 的導航欄中咱們就能夠看到組織分塊後的方法列表。在 Swift 中咱們能夠用 MARK:
來代替:
在 Objective-C 中還有一個很經常使用的編譯標記,那就是 #warning
,一個 #warning
標記能夠在 Xcode 的代碼編輯器中顯示爲明顯的黃色警告條,很是適合用來提示代碼的維護者和使用者須要對某些東西加以關注。在 Swift 中咱們能夠用 FIXME:
和 TODO:
配合 shell
來代替:
腳本:
TAGS="TODO:|FIXME:" echo "searching ${SRCROOT} for ${TAGS}" find "${SRCROOT}" \( -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/"
效果:
在 Swift 3 中,須要換行時是須要 \n
:
let str = "xxxx\nxxx" // 輸出: // xxxx // xxx
在 swift 4 中,咱們可使用 """
:
let jsonStr = """ { "id": 123455, "nickname": "xxxx", "isMale": true, "birthday": "2000年3月24日", "personalURL": "https://xxxxxx.github.io" } """ // 輸出: { "id": 123455, "nickname": "xxxx", "isMale": true, "birthday": "2000年3月24日", "personalURL": "https://xxxxxx.github.io" }
咱們須要切割某個字符串時能夠用 split
方法,須要注意的是,返回的結果是個數組:
let str = "Hello,world !" print(str.split(separator: ",")) // 輸出: // ["Hello", "world !"]
class MyClass { var name = "ifelseboyxx" }
Swift 4 中 Apple 引入了新的 KeyPath 的表達方式,如今,對於類型 MyClass
中的變量 name
,對應的 KeyPath 能夠寫爲 \MyClass.name
,利用 KVC 修改 name
值的話,咱們能夠這麼操做:
let object = MyClass() print("name: \(object.name)") // set object[keyPath: \MyClass.name] = "ifelseboy" // get print("name: \(object[keyPath: \MyClass.name])") // 輸出: // name: ifelseboyxx // name: ifelseboy
另外 Swift 4 中 struct
一樣支持 KVC :
struct MyStruct { var age: Int } var obj = MyStruct(age: 18) print("我今年 \(obj.age) 歲了") obj[keyPath: \MyStruct.age] = 8 print("我今年 \(obj[keyPath: \MyStruct.age]) 歲了") // 輸出: // 我今年 18 歲了 // 我今年 8 歲了
KVC 一節中代碼裏有個注意點:
var obj = MyStruct(age: 18) //替換爲 let obj = MyStruct(age: 18)
是編譯不過的,會報錯:
Cannot assign to immutable expression of type 'Int'
筆者初次也犯了這樣的錯誤,想固然的認爲 MyClass
用 let
聲明的是沒有問題的,struct
也同樣:
let object = MyClass()
其實緣由很簡單,swift 中 Class 是引用類型的,而 struct 是值類型的:值類型在被賦給一個變量,或被傳給函數時,實際是作了一次拷貝。引用類型在被賦給一個變量,或被傳給函數時,傳遞的是引用。
很遺憾,依然只有 NSObject
才能支持 KVO,另外因爲 Swift 爲了效率,默認禁用了動態派發,所以想用 Swift 來實現 KVO,咱們還須要作額外的工做,那就是將想要觀測的對象標記爲 dynamic
和 @objc
,下面的 ? 是 ViewController
監聽 MyClass
的 date
屬性:
class MyClass: NSObject { @objc dynamic var date = Date() } class ViewController: UIViewController { var myObject: MyClass! var observation: NSKeyValueObservation? override func viewDidLoad() { super.viewDidLoad() myObject = MyClass() print("當前日期:\(myObject.date)") observation = myObject.observe(\MyClass.date, options: [.old,.new], changeHandler: { (_, change) in if let newDate = change.newValue , let oldDate = change.oldValue { print("日期發生變化 old:\(oldDate) new:\(newDate) ") } }) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { self.myObject.date = Date() } } } // 輸出: // 當前日期:2017-12-07 06:31:26 +0000 // 日期發生變化 old:2017-12-07 06:31:26 +0000 new:2017-12-07 06:31:27 +0000
在 Objective-C 中咱們幾乎能夠沒有限制地對全部知足 KVC 的屬性進行監聽,而如今咱們須要屬性有 dynamic
和 @objc
進行修飾。大多數狀況下,咱們想要觀察的類包含這兩個修飾 (除非這個類的開發者有意爲之,不然通常也不會有人願意多花功夫在屬性前加上它們,由於這畢竟要損失一部分性能),而且有時候咱們極可能也沒法修改想要觀察的類的源碼。遇到這樣的狀況的話,一個可能可行的方案是繼承這個類而且將須要觀察的屬性使用 dynamic
和 @objc
進行重寫。好比剛纔咱們的 MyClass
中若是 date
沒有相應標註的話,咱們可能就須要一個新的 MyChildClass
了:
class MyClass: NSObject { var date = Date() } class MyChildClass: MyClass { @objc dynamic override var date: Date { get { return super.date } set { super.date = newValue } } } class ViewController: UIViewController { var myObject: MyChildClass! var observation: NSKeyValueObservation? override func viewDidLoad() { super.viewDidLoad() myObject = MyChildClass() print("當前日期:\(myObject.date)") observation = myObject.observe(\MyChildClass.date, options: [.old,.new], changeHandler: { (_, change) in if let newDate = change.newValue , let oldDate = change.oldValue { print("日期發生變化 old:\(oldDate) new:\(newDate) ") } }) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { self.myObject.date = Date() } } } // 輸出: // 當前日期:2017-12-07 06:36:50 +0000 // 日期發生變化 old:2017-12-07 06:36:50 +0000 new:2017-12-07 06:36:51 +0000
在 Objective-C 中,若是咱們想疊加按鈕的某個狀態,能夠這麼寫:
UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom]; [button setTitle:@"Test" forState:UIControlStateNormal | UIControlStateSelected];
對應的 Swift 咱們能夠這麼寫:
let btn = UIButton.init(type: .custom) btn.setTitle("hehe", for: [.normal ,.selected])
把須要疊加的狀態用個數組裝起來就好了。
在 Objective-C 中,咱們能夠用 @synchronized
這個關鍵字能夠用來修飾一個變量,併爲其自動加上和解除互斥鎖。這樣,能夠保證變量在做用範圍內不會被其餘線程改變:
- (void)myMethod:(id)anObj { @synchronized(anObj) { // 在括號內持有 anObj 鎖 } }
雖然這個方法很簡單好用,可是很不幸的是在 Swift 中它已經 (或者是暫時) 不存在了。其實 @synchronized
在幕後作的事情是調用了 objc_sync
中的 objc_sync_enter
和 objc_sync_exit
方法,而且加入了一些異常判斷。所以,在 Swift 中,若是咱們忽略掉那些異常的話,咱們想要 lock 一個變量的話,能夠這樣寫:
func myMethod(anObj: AnyObject!) { objc_sync_enter(anObj) // 在 enter 和 exit 之間持有 anObj 鎖 objc_sync_exit(anObj) }
更進一步,若是咱們喜歡之前的那種形式,甚至能夠寫一個全局的方法,並接受一個閉包,來將 objc_sync_enter
和 objc_sync_exit
封裝起來:
func synchronized(_ lock: AnyObject, closure: () -> ()) { objc_sync_enter(lock) closure() objc_sync_exit(lock) } // 使用: synchronized(self) { }
這樣使用起來就和 Objective-C 中 @synchronized
很像了。
再舉個 ? ,若是咱們想爲某個類實現一個線程安全的 setter
,能夠這樣:
class Obj { var _str = "123" var str: String { get { return _str } set { synchronized(self) { _str = newValue } } } }
在 Objective-C 中,咱們一般會自定義日誌輸出來完善信息以及避免 release
下的輸出,好比下面這種,能夠額外提供行數、方法名等信息:
#ifdef DEBUG #define XXLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) #else #define XXLog(...) #endif
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. XXLog(@"ifelseboyxx"); return YES; } // 輸出: // 2017-12-08 13:32:02.211306+0800 Demo[17902:88775537] -[AppDelegate application:didFinishLaunchingWithOptions:] [Line 28] ifelseboyxx
在 Swift 中,咱們能夠這樣自定義:
func xxprint<T>(_ message: T, filePath: String = #file, line: Int = #line, function: String = #function) { #if DEBUG let fileName = (filePath as NSString).lastPathComponent.replacingOccurrences(of: ".Swift", with: "") let dateFormatter = DateFormatter() dateFormatter.locale = Locale.current dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" print("[" + dateFormatter.string(from: Date()) + " " + fileName + " " + function + " \(line)" + "]:" + "\(message)") #endif }
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() xxprint("ifelseboyxx") DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { xxprint("ifelseboyxx") } } } // 輸出: // [2017-12-08 13:49:38 ViewController.swift viewDidLoad() 27]:ifelseboyxx // [2017-12-08 13:49:39 ViewController.swift viewDidLoad() 29]:ifelseboyxx
在 Objective-C 中,咱們一般把屬性聲明爲 readonly
來提醒別人:「不要修改!!」,一般這麼寫:
@interface Person : NSObject @property (nonatomic, readonly, copy) NSString *name; @end
若是外部嘗試修改的話,會編譯錯誤:
- (void)viewDidLoad { [super viewDidLoad]; Person *p = [Person new]; p.name = @"ifelseboyxx"; } // 編譯錯誤: // Assignment to readonly property
有些狀況下,咱們但願內部能夠點語法訪問 name
屬性,也就是 self.name
,可是由於是 readonly
的,會編譯錯誤:
@implementation Person - (instancetype)init { self = [super init]; if (self) { self.name = @"ifelseboyxx"; } return self; } @end // 編譯錯誤: // Assignment to readonly property
這時候咱們就會在內部的 extension
從新聲明一個 readwrite
的一樣的屬性,也就是「外部只讀,內部可寫」:
@interface Person () @property (nonatomic, readwrite, copy) NSString *name; @end
在 Swift 中,咱們可能有一樣的場景。這裏就不得不提到 private
和 fileprivate
關鍵字了。private
表示聲明爲私有的實體只能在其聲明的範圍內被訪問。好比我在 MyClass
中聲明瞭一個私有的 name
屬性,外部訪問的話會編譯錯誤:
class MyClass { private var name: String = "Test" } class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let only = MyClass() print(only.name) only.name = "ifelseboyxxv587" } } // 編譯異常: // 'name' is inaccessible due to 'private' protection level
而 fileprivate
,看命名咱們大概能猜到,就是將對實體的訪問權限於它聲明的源文件。通俗點講,好比我上面的代碼都是在 ViewController.swift
這個文件裏的,我把 private
修改成 fileprivate
,就不會編譯錯誤了:
class MyClass { fileprivate var name: String = "Test" }
那麼若是非 ViewController.swift
文件,也想訪問 MyClass
的 name
屬性該怎麼辦呢?咱們能夠把 name
屬性聲明爲 fileprivate(set)
,就要就達到相似 Objective-C 中的 readonly
效果了 :
// ViewController.swift 文件 class MyClass { fileprivate(set) var name: String = "Test" } class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let only = MyClass() print(only.name) only.name = "ifelseboyxxv587" print(only.name) } } // 編譯正常,ViewController.swift 文件內可讀可寫 // 輸出: // Test // ifelseboyxxv587
// AppDelegate.swift 文件 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. let only = MyClass() print(only.name) //只能讀 only.name = "ifelseboyxxv587" //這裏報錯,不能寫 return true } // 編譯異常: // Cannot assign to property: 'name' setter is inaccessible
在 Objective-C 中,咱們能夠利用 {}
來開闢新的做用域,來避免對象名稱重複的問題:
NSString *ha = @"測試一"; { NSString *ha = @"測試二"; NSLog(@"%@",ha); } NSLog(@"%@",ha); // 輸出: // 2017-12-11 16:55:20.303132+0800 Demo[48418:93027416] 測試二 // 2017-12-11 16:55:20.303316+0800 Demo[48418:93027416] 測試一
在 Swift 中,取代 {}
的是 do {}
:
let ha = "測試一" do { let ha = "測試二" print(ha) } print(ha) // 輸出: // 測試二 // 測試一
在 Objective-C 中,咱們若是想倒序數組通常這樣:
NSArray *array = @[@"1",@"2",@"3"]; NSArray *reversedArray = [[array reverseObjectEnumerator] allObjects]; // 輸出: // 2017-12-11 17:39:57.127466+0800 Demo[49004:93210504] ( 3, 2, 1 )
在 Swift 中,相對簡單點:
let arr:[String] = ["1","2","3"] let reversedArr:[String] = arr.reversed() // 輸出: // ["3", "2", "1"]
在 Objective-C 中,若是遇到多層嵌套的條件語句,咱們若是想要指定跳出某個條件語句是很不方便的。好比有兩個循環,一旦找到它們相同的,就馬上中止循環,咱們可能會這麼作:
NSArray *arr1 = @[@"1",@"2",@"3",@"4",@"5"]; NSArray *arr2 = @[@"4",@"6",@"8",@"9",@"2"]; BOOL finded = NO; for (NSString *x in arr1) { if (finded) { break; } NSLog(@"x:%@",x); for (NSString *y in arr2) { NSLog(@"y:%@",y); if ([x isEqualToString:y]) { NSLog(@"找到相等的了:%@",x); finded = YES; break; } } }
咱們須要藉助 finded
這個 BOOL
,來方便咱們跳出循環。在 Swift 中,咱們就能夠利用標籤語句,來指定具體跳出哪一個循環,語法是這樣的:
標籤名: 條件語句 { }
上面的 ? 咱們能夠這麼寫:
let arr1 = ["1","2","3","4","5"] let arr2 = ["4","6","8","9","2"] label: for x in arr1 { print("x: \(x)") for y in arr2 { print("y: \(y)") if x == y { print("找到相等的了:\(y)") break label } } }
上面代碼,咱們把第一層循環定義了標籤:label
。在第二層循環中,一旦條件成立,馬上跳出第一層循環 label
。這個特性,能夠說十分方便了!
在 Objective-C 中,咱們自定義通知時,對於名稱的定義通常都有規範:
// xxx.h UIKIT_EXTERN NSString * const XXXXNotification; // xxx.m NSString * const XXXXNotification = @"XXXXNotification";
在 Swift 中,咱們能夠參考 Alamofire 的方式,建立個專門存放通知名的文件,擴展 Notification.Name
並以結構體 struct
方式聲明:
// XXNotification.swift 文件 import Foundation extension Notification.Name { public struct Task { public static let 通知名 = Notification.Name(rawValue: "org.target名稱.notification.name.task.通知名") } }
而後咱們就能夠愉快的使用了:
// add NotificationCenter.default.addObserver(self, selector: #selector(myNotification(_ :)), name: NSNotification.Name.Task.通知名, object: self) // post NotificationCenter.default.post(name: NSNotification.Name.Task.通知名, object: self) // remove NotificationCenter.default.removeObserver(self, name: NSNotification.Name.Task.通知名, object: self)