Như mọi người đã biết, tất cả các ứng dụng hiện nay đều có những ô nhập (TextField, TextView …) để hỗ trợ người dùng điền thông tin của họ và gửi về phía server để xử lí. Khi này để giảm tải công việc cho server chúng ta cần phải kiểm tra dữ liệu để loại bỏ bớt các trường hợp dữ liệu không đúng trước khi gửi lên server. Để làm việc này thì thông thường chúng ta sẽ nghĩ ngay đến Regex. Vậy Regex là gì? Áp dụng Regex vào source code Swift như nào? mời các bạn theo dõi tiếp bài viết của mình nhé.
Regex là gì?
Regex là viết tắt của Regular Expression, tiếng việt được gọi là Biểu Thức Chính Quy. Regex là một công cụ rất mạnh trong việc xử lí chuỗi, nó thường được dùng để kiểm tra tính hợp lệ của một chuỗi hay tìm kiếm, vì vậy chúng ta nên trang bị cho mình một số kiến thức cơ bản về Regex để có thể xử lí công việc nhẹ nhàng và nhàn hạ hơn.
Nhược điểm của Regex
Công nhận regex là một công cụ rất mạnh trong việc xử lí chuỗi, tuy nhiên nó cũng có một số nhược điểm như sau:
Khó đọc
Nhìn vào đoạn Regex: ^([A-Za-z0-9!@#$%^&*?]{8,})$
Khi mới bắt đầu chúng ta sẽ rất khó để biết được regex này đang làm nhiệm vụ gì. Vì nó được mô tả bới các kí tự không quen thuộc với ngôn ngữ chúng ta hay dùng.
Dễ quên
Do nó khó đọc, khó hiểu nên nó cũng khiến chúng ta dễ quên, vì vậy mỗi lần cần đến 1 regex nào là chúng ta thường phải tìm đến google để trợ giúp. Để khắc phục nhược điểm này mỗi khi chúng ta làm việc mới một regex khó hoặc dài thì chúng ta sẽ lưu lại vào 1 file lưu trữ, để sau này dùng đến tìm lại cho nhanh.
Regex dùng để làm gì?
Regex thường được dùng để tìm kiếm, để tách chuỗi, để kiểm tra chuỗi, …
1. Dùng để kiểm tra chuỗi
Ví dụ ta cần kiểm tra độ phức tạp của mật khẩu với yêu cầu như sau:
- Phải có ít nhất một kí tự viết thường
Regex: [a-z]: Sẽ khớp với bất kì kí tự viết thường nào trong chuỗi(a-z)
Input: AAA -> FALSE
Input: aabc hoặc Aza -> TRUE - Phải có ít nhất một kí tự viết hoa
Regex: [A-Z] : Sẽ khớp với bất kì kí tự viết hoa nào trong chuỗi(A-Z)
Input: AAA hoặc Abc -> TRUE
Input bbca -> FALSE - Phải có ít nhất một kí tự số
Regex: [0-9] : Sẽ khớp với bất kì kí tự số nào trong chuỗi(0-9)
Input: 1234 hoặc A0bc -> TRUE
Input bbca -> FALSE - Phải có ít nhất một trong số kí tự đặc biệt sau @!#$
Regex: [@!#$] : Sẽ khớp với bất kì kí tự số nào trong chuỗi(@!#$)
Input: 12@4 hoặc A!bc -> TRUE
Input bbca -> FALSE - Không được có dấu cách
Regex: [ ] : Sẽ khớp với bất kì kí tự nào trong chuỗi là space
Input: 12 4 hoặc A bc -> TRUE
Input bbca -> FALSE
2. Dùng để tách chuỗi
Ví dụ chúng ta có một chuỗi kí tự như sau: ABCxyz123mM
- Tách lấy hết các kí tự viết hoa từ chuỗi trên ta sử dụng regex: [A-Z], ta sẽ thu được kết quả từ chuỗi ABCxyz123mM -> ABCM
- Tách lấy hết kí tự viết thường ta dùng regex: [a-z], ta thu được kết quả như sau: ABCxyz123mM -> abcm
- Tách lấy hết kí tự số ta thường dùng regex: [0-9], Ta thu được kết quả như sau: ABCxyz123mM -> 123
3. Dùng để tìm kiếm
Regex khá là phổ biến, nó có mặt trên hầu hết các IDE hiện nay. xCode, Android studio, Subline text, note pad ++, …
Ví dụ phần mềm Subline text
Để kích hoạt tính năng này các bạn bấm command + f (tổ hợp tìm kiếm) và chọn mục regex (.*)
Ảnh dưới đây mình có viết 1 đoạn regex: ^N.*o$ để tìm ra chuỗi bắt đầu bằng kí tự N và có kết thúc bằng kí tự o
Giải thích ý nghĩa các kí tự để viết Regex
Sau đây mình sẽ giải thích kĩ hơn về các kí tự để viết regex và cách sử dụng chúng.
1.Kí tự thường
Mã Regex | Mô tả | Ghi chú |
a|b | Khớp với a hoặc b | |
[0-9] | Khớp với các kí tự là số 0,1,2..9 | |
[a-z] | Khớp với các kí tự viết thường từ a tới z | Nếu đổi z thành c thì sẽ là từ a tới c |
[ABC] | Khớp với các kí tự là ABC | |
[^ABC] | Khớp với các kí tự không phải là ABC | Nếu dấu ^ xuất hiện phía trong [] có nghĩa nó là Phủ định của tập hợp đó |
\d | Khớp với số bất kì | thay thế cho [0-9] |
\D | Khớp với các kí tự không phải số | Phủ định của \d |
\s | Khớp với tất cả kí tự là khoảng trằng, tab, hoặc xuống dòng | |
\S | Khớp với tất cả các kí tự không phải là khoảng trắng, tab hoặc xuống dòng | Phủ định của \s |
\S+ | Khớp với một hoặc nhiều kí tự không phải là khoảng trắng, tab hoặc xuống dòng | |
\w | Khớp với bất kì ký tự chữ | Thay thế cho [a-zA-Z0-9] |
\W | Khớp với kí tự bất kì không phải chữ | phủ định của \w |
\b | Khớp khi kí tự trước đó nằm ở cuối chuỗi | regex: dao\b minhdao: true daominh: false |
\B | Phủ định của \b |
2. Kí tự đặc biệt
Regex | Mô tả | Ghi chú |
. | Khớp với tất cả các kí tự trừ kí tự xuống dòng | Thay thế cho [^\n\r] |
^ | Bắt đầu | regex: ^dao daominh: True minhdao: False |
$ | Kết thúc | regex: dao$ daominh: False minhdao: True |
| | Điều kiện hoặc | a|b a: true b: true c: false |
\ | Biến 1 kí tự đặc biệt thành kí tự thường hoặc ngược lại | d: kí tự d thông thường \d: khớp với kí tự số bất kì |
3. Lặp
Regex | Mô tả | Ghi chú |
* | Xuất hiện 0 hoặc nhiều lần | Tương đương {0,} |
+ | Xuất hiện 1 hoặc nhiều lần | Tương đương {1,} |
? | Xuất hiện 0 hoặc 1 lần | Tương đương {0,1} |
{x,y} | Xuất hiện từ x lần tới y lần | {3}: xuất hiện đúng 3 lần {3,}: xuất hiện từ 3 lần hoặc nhiều hơn {3, 10} Xuất hiện từ 3 lần đến 10 lần |
4. Nhóm
Regex | Mô tả | Ghi chú |
() | Nhóm nhiều mã lại với nhau tạo thành nhóm điều kiện | |
(?:x) | Khớp với x nhưng không nhớ kết quả khớp | |
x(?=y) | Chỉ khớp x nếu ngay sau x là y | |
x(?!y) | chỉ khớp x nếu ngay sau x không phải y |
Ứng dụng regex vào việc kiểm tra dữ liệu UITextField bằng ngôn ngữ swift
Để ứng dụng được regex vào việc kiểm tra dữ liệu ở swift chúng ta cần biết một chút kiến thức cơ bản về regex(mình đã giới thiệu ở phía trên) và hiểu rõ về cách hoạt động của TextField
Các hàm common cần dùng
extension String {
/// check string is match regex or not
/// - Parameter regex: regular expression
/// - Returns: true if match
func isMatches(_ regex: String) -> Bool {
do {
let regex = try NSRegularExpression(pattern: regex)
let matches = regex.matches(in: self, range: NSRange(location: 0, length: self.count))
return !matches.isEmpty
} catch {
print("Something went wrong! Error: \(error.localizedDescription)")
}
return false
}
/// Return string matchs regex
/// - Parameter regex: regular expression
/// - Returns: all string match regex
func filter(regex: String) -> String {
do {
let regex = try NSRegularExpression(pattern: regex)
let results = regex.matches(in: self, range: NSRange(self.startIndex..., in: self))
return results.map { String(self[Range($0.range, in: self)!]) }.joined(separator: "")
} catch {
print("Something went wrong! Error: \(error.localizedDescription)")
}
return ""
}
/// Remove mark in string
var folded: String {
self.folding(options: .diacriticInsensitive, locale: nil)
.replacingOccurrences(of: "đ", with: "d")
.replacingOccurrences(of: "Đ", with: "D")
}
}
- func isMatches(_ regex: String) -> Bool: hàm này dùng để kiểm tra xem chuỗi có khớp với regex hay không?
- func filter(regex: String) -> String: Hàm này dùng để lấy ra tất cả các kí tự thoả mãn điều kiện của regex
- var folded: String : giúp biến đổi các kí tự có dấu về không dấu VD: Đ -> D, ê -> e …
extension UITextField {
/// validate input data
/// - Parameters:
/// - maxLength: max char of text field
/// - range: location and lenth of current selected text
/// - string: new string will be replacce at range
func validateInput(maxLength: Int, range: NSRange, string: String) {
guard let textFieldText = self.text else {
return
}
if self.text.safeValue.count == maxLength, !string.isEmpty {
return
}
let text: NSString = textFieldText as NSString
let finalString: String = text.replacingCharacters(in: range, with: string)
let newString = String(finalString.prefix(min(maxLength, finalString.count)))
self.text = newString
let countChange: Int = newString.count - textFieldText.count
let validateCount: Int = countChange > 0 ? countChange : 0
self.setCursorPosition(range.location + validateCount)
}
/// Set Cursor position
/// - Parameter cursorPosition: Int
func setCursorPosition(_ cursorPosition: Int) {
guard cursorPosition <= (self.text ?? "").count,
let posCursor = self.position(from: beginningOfDocument, offset: cursorPosition) else { return }
DispatchQueue.main.async {
self.selectedTextRange = self.textRange(from: posCursor, to: posCursor)
}
}
}
- func validateInput(maxLength: Int, range: NSRange, string: String): Dùng khi cần tự validate các kí tự cho phép, hoặc chặn, hoặc copy paste chuỗi, rồi set lại giá trị cho text field.
- func setCursorPosition(_ cursorPosition: Int): Dùng để di chuyển con trỏ khi copy paste.
Hiểu rõ các func trong UITextField delegate hoạt động
Ở đây mình chỉ nói đến các delegate hay dùng làm nhiệm vụ kiểm tra dữ liệu
- func textFieldDidEndEditing(_ textField: UITextField): Dùng để kiểm tra dữ liệu khi người dùng focus out, thường được dùng khi muốn kiểm tra dữ liệu tại thời điểm người dùng kết thúc thao tác nhập dữ liệu.
- func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool : thường được dùng khi muốn kiểm tra dữ liệu tại thời điểm người dùng nhập(realtime), dùng để chặn những kí tự không cho phép.
– textField: đây là text field đang được người dùng tương tác
– range: Vị trí và độ dài mã text sắp được thay đổi
– chuỗi được thay thế tại range
NOTE: Khi replacement string = empty có nghĩa là người dùng đang thực hiện thao tác xoá hoặc dùng gợi ý nội dung hoặc keychain của bàn phím. - event Editting Changed: Dùng khi không chặn kí tự ở hàm 2(shouldChangeCharacterIn), event này chạy khi có thay đổi kí tự trong text field, với điều kiện shouldCHangeCharacter phải return true
Sử dụng regex để kiểm tra và lọc realtime kí tự nhập vào UITextField
Từ các common code phía trên để kiểm tra chuỗi mà người dùng nhập vào chúng ta sẽ thực hiện nó trong hàm shouldChangeCharacter in range
Ví dụ: Text field chỉ cho nhập các kí tự chữ hoa chữ thường và không cho nhập tiếng việt
Ta sẽ làm như sau:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// case clear/use suggestion content type/ return/
if string.count == 0 {
return true
}
textField.validateInput(maxLength: 20, range: range, string: string.filterWithRegex(regex: "[a-zA-Z]"))
validatePassword(text: textField.text.safeValue)
return false
}
Nếu muốn khi copy paste một chuỗi có dấu tự convert về chuỗi không dấu ta làm như sau:
Sử dụng folded để convert chuỗi về chuỗi không dấu trước khi filter nó với regex
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// case clear/use suggestion content type/ return/
if string.count == 0 {
return true
}
textField.validateInput(maxLength: 20, range: range, string: string.folded.filterWithRegex(regex: "[a-zA-Z]"))
validatePassword(text: textField.text.safeValue)
return false
}
Tương tự với từng yêu cầu chúng ta sẽ tạo ra các regex khác nhau và đưa vào sử dụng một cách dễ dàng và hiệu quả
Sử dụng Regex để kiểm tra khi người dùng kết thúc nhập liệu
Nếu bạn muốn kiểm tra chuỗi người dùng vừa xong có đúng định dạng email hay không thì ta sử dụng hàm textFieldDidEndEditing khi người dùng kết thúc nhập để kiểm tra như sau:
func textFieldDidEndEditing(_ textField: UITextField) {
guard let textFieldText = textField.text else {
return
}
// check email format
if textFieldText.isMatches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$") {
// hide error
} else {
//show error
}
}
Để tránh sai sót khi sử dụng các regex ở nhiều chỗ khác nhau trong project, chúng ta nên tạo ra enum để quản lí các chuỗi regex này như sau:
enum Regex: String {
case none = "[\\s\\S]"
case min8CharNoSpace = "^([A-Za-z0-9!@#$%^&*?]{8,})$"
case haveCharAndNumber = "(?=.*[a-zA-Z])(?=.*[0-9])"
case haveUpercaseAndLowerCase = "(?=.*[a-z])(?=.*[A-Z])"
case haveSpecialChar = "[!@#$%^&*?]"
case vietnamese = "^[a-zA-Z0-9ÀÁÂÃÈÉÊÌÍÒÓÔÕÙÚĂĐĨŨƠàáâãèéêìíòóôõùúăđĩũơƯĂẠẢẤẦẨẪẬẮẰẲẴẶẸẺẼỀỀỂẾưăạảấầẩẫậắằẳẵặẹẻẽềềểếỄỆỈỊỌỎỐỒỔỖỘỚỜỞỠỢỤỦỨỪễệỉịọỏốồổỗộớờởỡợụủứừỬỮỰỲỴÝỶỸửữựỳỵỷỹ\\s]+$"
case email = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$"
case emailInput = "[a-zA-Z0-9.@]"
case password = "[a-zA-Z0-9!@#$%^&*?]"
var maxLength: Int {
switch self {
case .none:
return .max
case .min8CharNoSpace:
return 100
case .haveCharAndNumber:
return 100
case .haveUpercaseAndLowerCase:
return 100
case .haveSpecialChar:
return 100
case .vietnamese:
return 100
case .email:
return 100
case .emailInput:
return 100
case .password:
return 30
}
}
}
Mình hi vọng bài viết này giúp mọi người hiểu rõ hơn về regex và cách ứng dụng nó vào trong công việc. Chúc các bạn thành công!