#14FaceID&TouchID

Apple範例第28個,是有關生物技術驗證的範例,也就是我們俗稱的臉部辨識或指紋辨識.

除此外,也用上了NotificationCenter,來執行即時更新的動作.

程式技術:

  • notificationCenter.addObserver
  • LocalAuthentication
  • UIResponder
  • KeychainWrapper
  • weak
  • 感想:生物技術驗證本身不困難,文件雖然有點硬度,但實行出來並不困難.核心的部分,Apple幫我們做了非常多事情的樣子.在UIResponder的部分,花了比較多的時間進行理解,最後發現是關於鍵盤出現時,與UITextView之間的關係.而KeychainWrapper則是沒有碰過,但看起來跟UserDefaults相差無幾,差別是在跟密碼有關和確保安全性時,會使用KeychainWrapper,相關文件要去GitHub抓取.最後則是針對弱引用的部分,再進行複習.

讓我們來coding吧!

  • LocalAuthentication
import LocalAuthentication

@IBAction func authenticateTapped(_ sender: Any) {
//先問你能不能用Face ID
// 創建一個上下文實例
let context = LAContext()
// 宣告一個變數接收 canEvaluatePolicy 返回的錯誤
var error: NSError?

// 評估是否可以針對給定方案進行身份驗證
/*測試方案可用性
在嘗試進行身份驗證之前,請通過調用 canEvaluatePolicy 方法進行測試以確保確實能夠執行此操作:*/

if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
// 描述使用身份辨識的原因
let reason = "Identify yourself!"

/*接下來我們可以透過 evaluatePolicy 方法,藉由該方法所回調中的 Success 這個 Bool 類型值來判斷方案是否執行成功,而其錯誤 error 如同上面的錯誤相同*/

context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { [weak self] success, authenticationError in
DispatchQueue.main.async {
if success {
//解鎖訊息
self?.unlockSecretMessage()
} else {
//失敗的話,使用密碼
self?.useFallBackAuthentication()
}
}
}
} else {
//不可驗證
let ac = UIAlertController(title: "沒有生物驗證權限", message: "沒有生物驗證的權限,請去設定開啟", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
present(ac, animated: true)
}
}
  • notificationCenter.addObserver
override func viewDidLoad() {
super.viewDidLoad()

title = "Nothing to see here"

let notificationCenter = NotificationCenter.default

notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)

notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)

notificationCenter.addObserver(self, selector: #selector(saveSecretMessage), name: UIApplication.willResignActiveNotification, object: nil)

}

//調整UITextView 為了輸入的字
@objc func adjustForKeyboard(notification: Notification) {
//Notification裡面有一個字典,字典的Key可以轉換成後面的Value,所以這個Key可以取後面的值
//keyboardFrameEndUserInfoKey: 可以拿到鍵盤的Frame
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }


//從NSValue 取出cgRectValue
let keyboardScreenEnd = keyboardValue.cgRectValue
// 鍵盤轉換成螢幕上面的位置
let keyboardViewEndFrame = view.convert(keyboardScreenEnd, from: view.window)

// 通知的名稱等於 UIResponder.keyboardWillHideNotification的話,否則就是UIResponder.keyboardWillChangeFrameNotification
if notification.name == UIResponder.keyboardWillHideNotification {
secret.contentInset = .zero
} else {
//文字在文字框內的距離
secret.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height - view.safeAreaInsets.bottom, right: 0)
}

//文字在文字框內的距離 等於 scroll滑動範圍的指標(那一個滑動軸)
secret.scrollIndicatorInsets = secret.contentInset

//可滑動範圍
let selectedRange = secret.selectedRange
//選擇到範圍 顯示在可到看的範圍.滑到哪看到哪
secret.scrollRangeToVisible(selectedRange)
}

設置密碼

@objc func setPassword() {
let ac = UIAlertController(title: "設定密碼", message: "請在這裡輸入密碼", preferredStyle: .alert)
ac.addTextField() { textField in
textField.isSecureTextEntry = true
textField.placeholder = "password"
}

ac.addTextField() { textField in
textField.isSecureTextEntry = true
//重複輸入密碼
textField.placeholder = "confirm password"
}

ac.addAction(UIAlertAction(title: "取消", style: .cancel))
ac.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak self, weak ac] _ in
//錯誤的話,會跳出Alert
guard let password = self?.checkSetPassword(ac: ac!) else { return }

// 如果有的話,把密碼用KeychainWrapper存取起來
if let passwordKey = self?.passwordKey {
//Key是設置好的字串.password則是 剛剛驗證完的password
KeychainWrapper.standard.set(password, forKey: passwordKey)
}
}))

present(ac, animated: true)
}


//認證密碼,登錄密碼
func authenticateWithPassword() {
let ac = UIAlertController(title: "輸入密碼", message: nil, preferredStyle: .alert)

//輸入文字框
ac.addTextField() { textField in
textField.isSecureTextEntry = true
textField.placeholder = "password"
}

ac.addAction(UIAlertAction(title: "取消", style: .cancel))
ac.addAction(UIAlertAction(title: "OK", style: .default, handler: {
//ARC 每當AlertController是參考型別,是強引用.用弱引用後,結束後會釋放記憶體.
//capture list
[weak self, weak ac] _ in
//如果輸入的密碼有的話
guard let password = self?.getField(ac: ac!, field: 0) else { return }

//如果密碼鑰匙 等於設置的密碼鑰匙的話
if let passwordKey = self?.passwordKey {
//取出密碼 等於KeychainWrapper.standard.string的密碼鑰匙字串的話
if let storedPassword =
KeychainWrapper.standard.string(forKey: passwordKey) {
if password == storedPassword {
self?.unlockSecretMessage()
//跳出
return
}
}
}

//有問題的話,驗證有錯
let ac = UIAlertController(title: "認證錯誤", message: "驗證有誤,請重新輸入密碼", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
self?.present(ac, animated: true)
}))

present(ac, animated: true)
}

何謂弱引用?

牽涉到ARC概念

何謂ARC?

• Automatic Reference Counting

• 自動追蹤reference物件的數字。當數字為0時,物件即死亡

  • 只作用在物件,不會作用在 struct & enum(struct & enum 是 value type,是複製的概念).指作用在reference型別,也就是參考型別或稱為引用型別.
  • Swift 中以記憶體配置的方式不同來說,可以分為值型別(value type)參考型別(reference type)
  • 記憶體分成 heap 和 stack 兩塊。
  • class 物件是 reference type,會被儲存在 heap
  • struct 物件是 value type,會被存在 stack。
  • 值型別(value type)會儲存實際的值。
  • 參考型別(reference type)只會儲存其在記憶體空間中配置的位置。

如果我們一直使用參考型別太多,就會讓引用持續增加,增加記憶體的負擔.透過弱引用的使用,可以消除引用.弱引用有這3個特性.

1. 不關心物件,不會增加reference

2. 當物件死亡時,變數會被設成nil

3.weak只能宣告optional的變數

如果要弱引用沒有ptional的變數,就要使用unowned

參考文章:

程式碼:

--

--