클로저는 이름 없는 클로저(익명 함수)와, 이름 있는 클로저(함수)로 구분된다.
// 클로저 표현식 문법
{ (parameters) -> ReturnType in
statements
}
{ statements } // 파라미터와 리턴 타입 생략시
클로저 표현식은 전역범위에서 단독으로 사용이 불가하다
클로저는 인자라벨을 사용하지 않는다, 따라서 호출시에 인자라벨을 사용하지 않음
let c = { (str: String) -> String in
return "Hi, \(str)"
}
let result = c("minsu")
print(result) // "Hi, minsu"
// 함수를 받아서 호출하는 모습, typealias를 사용해서 가독성을 높이는것도 가능하다.
func perform(closure: (String) -> String) {
print(closure("minsu"))
}
perform(closure: c) // "Hi, minsu"
perform(closure: { (str: String) -> String in
return "Hi, \(str)" // return 생략 가능
})
문법 최적화에 대해 알아보겠습니다.
// filter함수를 사용한 예시
var proModels = products.filter( { (name: Strung) -> Bool in
return name.contains("Pro")
})
// 1. 프레임 워크에 구현된 메소드는 타입 추론이 가능하므로 파라미터와, 리턴을 생략 가능함.
var proModels = products.filter( { (name) in
return name.contains("Pro")
})
// 2. Shorthand Argument name으로 파라미터를 대체 가능 in도 생략한다 이 경우
var proModels = products.filter({
return $0.contains("Pro")
})
// 3. return 키워드 생략가능
var proModels = products.filter({
$0.contains("Pro")
})
// 4. 클로저 파라미터가 마지막이라면 Trailing closure로 작성한다.
var proModels = products.filter() {
$0.contains("Pro")
}
// 5. 괄호도 생략이 가능하다. 안에 다른 파라미터가 없다면,
var proModels = products.filter{
$0.contains("Pro")
}
클로저에서 값을 캡처하는 방법을 알아보겠습니다. 캡쳐는 간단하게, 값을 가져와서 쓴다고 생각하면 된다. swift는 참조로 값을 캡처한다. 그래서 클로저 내부에서 캡처한 값을 바꾸면 외부의 값도 바뀐다.
// 클로저 내부에서 외부의 값에 접근하면 값을 캡쳐한다.
var num = 0
let c = {
num +=1
print("#1: \ num")
}
c()
탈출 클로저에 관해 알아보겠습니다.
먼저, Non-escaping Closure는 항상 함수가 종료되기 전에 종료됩니다. Escaping Clousre는 시작 시점과 종료시점이 특정되지 않습니다.
func nonEscaping(closure: () -> ()) {
print("start")
closure()
print("end")
}
nonEscaping {
print("closure")
}
// 기본적으로, 클로져는 non-escaping이므로 명시해줘야한다.
func Escaping(closure: @escaping () -> ()) {
print("start")
var num = 12
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
closure()
print(a) // 값을 캡처해서 클로저가 종료될 때 까지 사용이 가능한 장점이있다.
// 이런 이유 등으로 탈출 클로저가 필요한듯
}
print("end")
}
파라미터로 전달되는 표현식을 클로저로 래핑해주는 Autoclosure에 대해 알아보겠습니다.
func takeClosure(param: @autoclosure () -> Int) {
print(#function)
print(param())
}
// autoclosure는 표현식을 클로저로 래핑해서 전달해준다. 중괄호 생략 가능 !
takeClosure(param: Int.random(in: 0...100) )
autoclosure로 특성을 선언하면 파라미터에 타입을 넣을 수 없고 빈 괄호로 놔둬야 한다.
swift 5.3부터 추가된 기능, 원래라면 마지막 파라미터만 Trailing Closure로 사용 할 수 있었는데, 이제는 상관없이 사용이 가능하다.
처음의 인자라벨은 생략이 가능하나, 두번째 후행 클로저부터는 인자라벨을 필수로 명시해야한다. 이는 가독성을 높여주기 때문도 있다. 그리고 함수의 인자라벨이 생략되어 있다면 다중 후행 클로저는 사용이 불가하고, 인라인 클로저 형식으로 전달해야한다.
// SwiftUI의 예시
var body: some View {
Button {
self.showOptions.toggle()
}
// 두번째 부터는 인자라벨을 표기하는 모습
label: {
Image(systemName: "gear")
}
}
이는 다양한 값(데이터)들의 묶음이다. 하나의 변수의 여러 값을 담을 수 있게 해준다.
튜플은 한 번 정하면 바꿀 수 없다. 하지만 멤버의 값은 바꿀 수 있다.
let data = ("hi", 1, 234.234)
// . 과 index를 사용해서 접근이 가능하다.
print(data.0) // "hi"
print(data.1) // 1
// 이름이 있는 튜플(Named Tuples)
let data = (name: "minsu", age: 20, height: 174.5)
data.name
data.age // 멤버 이름으로 접근이 가능, 물론 index로도 가능하다
튜플을 분해하는 것을 알아보겠습니다.
let data = (name: "minsu", age: 20, height: 174.5)
// name, age, height의 각각 튜플의 값이 들어간다. var로도 가능.
let (name, age, height) = data
// 만약 두 가지 요소만 분해하고 싶다면 와일드카드 패턴을 사용하면 된다.
var (name, age, _) = data
튜플의 요소들을 매칭하는 것에 대해 알아보겠습니다. switch문을 활용한다.
let size = (1000.0, 200.0)
switch size {
// Interval 매칭도 사용 가능하다.
case (1000, 200...300):
print("hi")
case (_, 500) :
print("wild")
// 비율이 5:1 이라면 true를 나타내는 case
case let(w, h) where w / h == 5 / 1 :
print("value binding, where")
default:
break
}
Swift의 문자열은 구조체로 이루어져있으며, 값 형식이다.
문자열을 참조형식으로 다루고 싶다면 Foundation의 클래스로 이루어진 NSString을 사용하면 된다. 두 문자열은 서로 호환이 된다고 한다.
var nsstr: NSString = "str"
// Type casting을 사용해서 저장해야한다.
let swiftStr: String = nsstr as String
nsstr = swiftStr as NSString
// control + command + space를 하면 창이 뜬다.
var thumsUp = "👍"
// 또는 창을 확장하여 문자 정보에서 유니코드 값으로도 가능, "\u{code}" 로 입력한다.
thumsUp = "\u{1F44D}"
여러줄의 문자열 값을 저장하는 방법
// 큰따옴표랑 같은 라인에서 시작하면 안된다, 무조건 다음 라인 부터 시작 해야 한다.
let text = """
sdfknsldnflsn sdnflseworpwe sfsdf sfsdf sdf,
sdfsdfasf lknlnvlskd sdkfoe pqwnv snckx sdfkewo
"""
// 마지막 위치의 큰따옴표가 들여쓰기의 기준이 된다. 제일 왼쪽에 있다고 생각하면 된다.
// 백 슬래쉬를 추가하면 줄 바꿈을 없애줌.
Swift 5에 새롭게 추가된 Raw String에 대해 알아보겠습니다.
var str = "\"Hello\", Swift 5"
var rawStr = #"\"Hello\", Swift 5"#
print(str) // "Hello", Swift 5
print(rawStr) // \"Hello\", Swift 5
// rawStr은 #으로 감싼다. 문자열 안의 특수기호를 그대로 보여줌.
// 줄바꿈 등 기능을 사용하려면 백슬래쉬 뒤에 #을 추가하면 된다. #의 개수는 짝을 맞춰야한다.
let firstName = "Min-su"
let lastName = "Kim"
// 두번째 파라미터를 먼저 받고, 첫번째 파라미터를 뒤에받는 모습.
let KorFormat = "그의 이름은 %2$@ $1$@ 입니다."
let engFormat = "His name is $1$@ %2$@."
String(format: korForamt, firstName, lastName)
String(format: engFormat, firstName, lastName)
let str = "Hello"
let first = str[str.startIndex]
// str.endIndex는 마지막 인덱스의 다음이므로 before을 이용한다
let lastIndex = str.index(before: str.endIndex)
let last = str[lastIndex]
// 세번째 인덱스를 찾는 방법
thirdIndex = str.index(str.startIndex, offsetBy: 2)
thirdIndex = str.index(str.endIndex, offsetBy: -3)
//같은 문자의 반복으로 초기화 하는 방법
let repeatStr = String(repeating: "%", count: 100)
// 소문자로 새로운 값을 리턴하는 메소드
str.lowercased() // ed가 붙은건 원본을 건드리지 않는다
// 대문자
str.uppercased()
// 각 단어의 첫글자를 대문자로 만들어줌
str.capitalized
// for-in 문에서도 사용이 가능하다
for char in "Swift" {
print(char)
}
Substring은 원본 문자열의 메모리를 공유합니다. 읽을 때는 원본 메모리를 공유하고 서브 스트링의 문자열을 변경할 때 새로운 값을 만듭니다. 이를 Copy-on-Write 최적화라고 부릅니다
let s = str[..<str.index(str.startIndex, offsetBy: 2)]
let p = str[str.index(str.startIndex, offsetBy:2)...]
var str = "hi, Swift"
// 문자열 뒤 새로운 문자열 연결
// 원본을 변경함
str.append(", ") // "hi, Swift, "
// 복사본을 변경함
str.appending("bye")
// Format 활용
"number is ".appendingFormat("%.1f", 59.23)
// 특정 위치에 새 문자열을 추가한다, 원본에 추가한다.
str.insert(",", at: str.index(str.startIndex, offsetBy: 5))
if let sIndex = str.firstIndex(of: "S") {
str.insert(contentsOf: "hi,hi ", at: sIndex)
}
var str = "Hello, Minsu"
// replace 범위를 지정하고 대체 문자열로 바꿈
if let range = str.range(of: "Minsu") {
str.replaceSubrange(range, with: "Kim")
}
// replacing으로 전달, 원본에는 영향이 없다.
if let range = str.range(of: "Hello") {
let s = str.replacingCharacters(in: range, with: "Hi")
}
// Kim이라는 문자열을 뒤의 문자열로 바꿈
// 만약 대소문자 구별없이 하고 싶다면 파라미터로 options: [.caseInsensitive] 를 추가하면된다.
var s = str.replacingOccurrences(of: "Kim", with: "Bye Kim")
// 문자열 제거 부분
var str = "Hi, Kim Min Su!"
let lastIndex = str.index(before: str.endIndex)
var removed = str.remove(at: lastIndex) // ! 가 출력된다.
// 특정 문자열을 제거
if let range = str.range(of: "Hi") {
str.removeSubrange(range)
}
// drop, 서브스트링을 리턴함
var substr = str.dropLast() // 마지막 문자를 제외한 문자열을 공유한다.
let A = "Apple"
let B = "Banana"
let smallA = "apple"
// A와 smallA가 같은지 판단하는 코드
A.compare(smallA) == .orderedSame
// 대소문자 구별 x
A.caseInsensitiveCompare(smallA) == .orderedSame
이것 말고도 접두사, 접미사 비교하는 함수도 존재함.
// 단어 검색
str.contains("word")
// 범위 검색
str.range(of: "word")
문자열의 파라미터중 options 에 사용하는 메소드들에 대해 알아보겠습니다.
options: [.caseInsensitive]
같이 수정자 처럼 표현하는 이유는 앞에 형식들이 추론이 되므로 생략한 것이다. (NSString.CompareOptions.caseInsensitive)
// 대소문자를 구별 안하는 옵션
options: [.caseInsensitive]
// 읽는 방향을 반대로 하는 옵션
options: [.backwards]
// 리터럴을 비교하는 옵션, 일반 비교보다 더 빠르다고 함
options: [.literal]
a.compare(b, options:[.literal]) == .orderedSame
// 문자열에 포함된 숫자를 숫자로 판단해서 비교함
options: [.numeric]
"text5".compare("text10", options: [.numeric]) == .orderedAscending // true
// 정규식 옵션
options: [.regularExpression]
말 그대로 문자 집합이다. 문자열의 검색이나 잘못된 문자를 삭제할 때 주로 사용한다.
// 대문자가 들어가있는 집합
let a = CharacterSet.uppercaseLetters
let b = a.inverted // 나머지 문자들이 들어가있는 문자집합
let str = "loRem Ipsum"
if let range = str.rangeOfCharacter(from: a) {
print(str.distance(from: str.startIndex, to: range.lowerBound))
} // 대문자는 R 이 있으므로, 2가 출력된다.
// 공백, 탭 같은 문자들을 포함하는 문자집합
var str = " h i d e "
var charSet = .whitespaces
let trimmed = str.trimmingCharacters(in: charSet) // 시작과 끝의 공백이 삭제된다
// 문자 집합에 삽입,삭제
a.insert("#")
a.insert(charactersIn: "~@!")
a.remove("A")
a.remove(charactersIn: "BC")
// 커스텀 문자 집합
let customCharSet = CharacterSet(charactersIn:"@.")
let email = "minsu@gamil.com"
let components = email.components(separatedBy: customCharSet)
// @와 . 으로 구분해서 문자열 배열을 리턴해줌, ["minsu", "gamil", "com"]