Perform operations like assignment, arithmetic, and comparison.
Operator(연산자)는 unary, binary, ternary로 나뉜다.
Operator가 영향을 미치는 값을 Operand라고 한다. 예를 들어 1 + 2 라는 표현에서 '+'는 operator, '2'와 '3'은 Operand이다.
Assignment Operator(a = b)는 a의 값을 초기화하거나 업데이트한다.
아래와 같이 만약 assignment의 우측이 tuple이라면 그 요소는 다수의 상수 또는 변수로 분해될 수 있다.
let (x, y) = (1, 2)
C, Objective-C와 다르게 assignment operator는 값을 반환하지 않는다. 값을 반환하지 않음으로 인해 equal to operator(==)가 의도된 곳에 실수로 assignment operator(=)를 사용하는 것을 막을 수 있다.
Swift는 Addition(+), Subtraction(-), Multiplication(*), Division(/), 총 네 개의 표준 Arithmatic operator를 지원한다.
C에서의 Arithmetic Operator와 다르게 Swift에서는 디폴트로 값이 overflow 되는 것을 막는다.
Addition operator(+)는 아래와 같이 String concatenation을 지원한다.
"hello, " + "world" // "hello, world"와 같음.
Remainder operator(a % b)는 몇 개의 b가 a에 들어갈 수 있는지를 계산하고 나머지 값을 반환한다.
(다른 언어에서의 modulo operator로 알려져 있지만 음수를 처리하는 방식으로 보아 면밀하게 말하자면 mudulo보다는 remainder에 가깝다.)
a % b의 값을 정하기 위해 % operator는 다음의 공식을 사용한다.
a = (b x some multiplier) + remainder
여기서 some multiplier는 a 안에 들어갈 수 있는 b의 배수 중 가장 큰 숫자이다.
9 % 4를 위의 공식으로 바꾸면
9 = (4 x 2) + 1이고
-9 % 4를 위의 공식으로 바꾸면
-9 = (4 x -2) + -1이다.
b의 부호는 무시되고 따라서 a % b와 a % -b는 항상 같다.
prefix된 -는 숫자 값의 부호를 toggle할 수 있다.
let three = 3
let minusThree = -three
let plusThree = -minusThree
Unary plus operator(+)는 작용하는 값을 어떤 변화도 없이 반환한다. 실제로는 어떤 역할도 하지 않지만 음수에 대해 unary minus operator(-)를 사용할 때 코드에서 대칭감을 주기 위해 사용할 수 있다.
C와 마찬가지로 Swift는 assignment(=)와 다른 operation을 결합한 compound assignment operator를 제공한다.
var a = 1
a += 2 // a = a + 2와 동일
compound assignment operator는 값을 반환하지 않는다. 예를 들어 let b = a += 2는 성립할 수 없다.
Swift standard library에서 제공되는 operators 참고: https://developer.apple.com/documentation/swift/operator-declarations
Swift는 다음의 comparison operators를 지원한다.
Swift는 두 개의 object references가 동일한 object instance를 참조하는지를 알아보기 위해 사용하는 identity operators(===와 !==)도 제공한다.
참고: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/classesandstructures#Identity-Operators
각각의 comparision operator는 해당 statement가 참인지 거짓인지를 나타내기 위해 Bool 값을 반환한다. 보통 if 문과 같은 conditional statement에 자주 사용된다.
let name = "world"
if name == "world" {
print("hello, world")
} else {
print("I'm sorry \(name), but I don't recognize you")
}
// name이 "world"와 동일하기 때문에 "hello, world"를 출력.
만약 두 개의 tuple이 같은 타입, 그리고 동일한 개수의 값을 가지고 있다면 tuple도 비교할 수 있다.
Tuples는 comparison이 동일하지 않은 두 개의 값를 찾을 때까지 왼쪽에서 오른쪽으로, 한 번에 하나의 value가 비교된다. 만약 모든 element가 동일하다면 그 tuple들은 동일하다. 예를 들어 (1, "zebra") 와 (2, "apple")을 비교한다면 첫 번째 element인 1이 2보다 작기 때문에 (1, "zebra") < (2, "apple")가 된다. 두 번째 element인 "zebra"와 "apple"은 비교되지 않는다.
Tuple은 주어진 operator가 각각의 값에 적용될 수 있을 때만 비교할 수 있다. 예를 들어 (String, Int) 타입의 tuple은 String과 Int 모두 <로 비교될 수 있기 때문에 두 개의 tuple을 비교할 수 있다. 반면 (String, Bool) 타입의 tuple은 Bool이 <가 적용되지 않기 때문에 비교할 수 없다.
Swift standard library는 tuple이 일곱 개 미만의 element를 가질 때만 적용되는 tuple comparison operator를 제공한다. 일곱 개 이상의 element를 가진 tuple을 비교하고 싶다면 스스로 comparision operator를 구현해야 한다.
Ternary conditional operator는 question ? answer1 : answer2 의 형식을 가진다. question이 true라면 answer1을 반환하고 false라면 answer2를 반환한다. 이는 아래의 코드를 간결하게 쓴 형태이다.
if question {
answer1
} else {
answer2
}
아래의 코드는 header를 가지고 있다면 rowHeight에 50을, 가지고 있지 않다면 20을 더하는 코드이다.
let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)
Ternary conditional operator는 코드를 간결하게 작성할 수 있도록 하지만, 과도하게 사용된다면 읽기 어려운 코드가 될 수 있으니 조심히 사용해야 한다. 여러 개의 ternary conditional operator를 하나의 compound statement에 결합하는 것을 지양하자.
Nil-coalescing operator (a ?? b)는 optional a가 값을 가지고 있다면 unwrap하고, a가 nil이라면 default value b를 반환한다. a의 타입은 언제나 optional이고 b는 a에 저장된 값의 타입과 무조건 같아야 한다. 해당 operator는 다음의 코드와 동일하다.
a != nil ? a! : b
위의 코드는 a가 nil이 아니라면 a에 저장되어 있는 값에 접근하고, 반대라면 b를 반환하기 위해 ternary conditional operator와 forced unwrapping(a!)를 사용한다. nil-coalescing operator는 conditional checking 및 unwrapping을 간결하고 읽기 쉬운 형태로 캡슐화한다.
a의 값이 nil이 아니라면 b의 값은 평가되지 않는다. 이를 short-circuit evaluation(단축 평가 계산)이라고 한다. 첫 번째 인수가 결과를 도출하기에 충분하지 않은 상태에서만 두 번째 인수가 평가되는 방식이다. short-circuit evaluation의 반대 개념으로는 eager operator가 있다. 이는 첫 번째 인수와 상관없이 두 번째 인수가 무조건 평가되는 방식이다.
아래의 코드는 optional 타입인 userDefinedColorName이 nil 값을 가질 경우에는 colorNameToUse에 default 값인 "red"가 할당되고, nil이 아닐 경우에는 userDefinedColorName에 저장된 값이 할당되는 코드이다.
let defaultColorName = "red"
var userDefinedColorName: String? // default로 nil의 값을 가진다.
var colorNameToUse = userDefinedColorName ?? defaultColorName
Closed range operator (a...b)는 a에서 b까지의 범위를 정의한다. 이때 a와 b의 값도 해당 범위에 포함된다.
for-in loop처럼 범위 안의 모든 값을 반복하고 싶을 때 유용하다.
for index in 1...5 {
print("\(index) times 5 is \(index * 5)")
}
Half-open range operator (a..<b)는 a에서 b까지의 범위를 정의하지만 이때 b의 값은 포함되지 않는다. 다시 말해 첫 번째 값은 포함하지만 마지막 값은 포함하지 않는다. Closed range operator와 같이 a의 값은 b보다 커서는 안 된다. 만약 a와 b의 값이 동일하다면 결과 범위는 empty이다.
특히 array처럼 리스트의 길이까지(하지만 길이는 포함하지 않는) 세는 zero-based list에서 유용하다.
let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
print("Person \(i + 1) is called \(names[i])")
}
closed range operator는 한 방향으로 가능한 한 계속 진행하는 범위(예: array의 index 2부터 array의 끝까지를 포함하는 범위)에 대한 대체 형식이다. 이런 경우에 range operator의 한 쪽 값을 생략할 수 있다. 해당 operator는 한 쪽에만 값을 가지고 있기 때문에 one-sided range라고 불린다.
for name in names[2...] {
print(name)
}
// Brian, Jack 출력
for name in names[...2] {
print(name)
}
// Anna, Alex, Brian 출력
Half-open range도 마지막 값만 쓰여진 one-sided 형태를 가진다.
for name in names[..<2] {
print(name)
}
// Anna, Alex 출력
One-sided range는 subscript 뿐만 아니라 다양한 맥락에서 사용될 수 있다. 첫 번째 값을 생략한 one-sided range에서는 어디서 iteration이 시작되어야 하는지 확실하지 않기 때문에 iterate할 수 없다. 그러나 범위는 무한정 계속되기 때문에 loop에 명시적인 종료 조건을 추가해야 한다. 또한 다음 코드처럼 one-sided range가 특정 값을 포함하고 있는지 확인할 수도 있다.
let range = ...5
range.contains(7) // false
range.contains(4) // true
range.contains(-1) // true
Logical operator는 Boolean logic value인 true와 false를 변경하거나 합친다. Swift는 C 기반 언어처럼 다음 세 개의 standard operators를 지원한다.
Logical NOT operator (!a) 는 true를 false로, false를 true로 변환한다.
logical NOT operator는 prefix operator로 공백 없이 작용하는 값의 바로 앞에 위치한다. "not a"라고 읽는다.
let allowedEntry = false
if !allowedEntry {
print("ACCESS DENIED")
}
예시 코드처럼 Boolean 변수와 상수의 신중한 이름 선택은 코드의 가독성과 간결성을 높이고, 이중 부정이나 헷갈리는 논리 구조를 방지할 수 있다.
Logical AND operator (a && b)는 두 값이 모두 true여야 true가 되는 논리적 표현식을 만든다.
두 개의 값 중 하나라도 false라면 결과는 false이다. 만약 첫 번째 값이 false라면 두 번째 값은 평가되지 않는다. (short-circuit evaluation)
let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
Logical OR operator (a || b)는 두 값 중 하나라도 true라면 true가 되는 논리적 표현식을 만든다. Logical AND operator처럼 logical OR operator도 short-circuit evaluation을 사용한다. 따라서 왼쪽의 logical OR expression이 true라면 오른쪽은 평가되지 않는다.
let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
긴 compound expression을 생성하기 위해 여러 개의 logical operator를 결합할 수 있다.
if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
위의 코드에서 여러 개의 &&과 ||를 사용한다. 하지만 &&과 ||는 여전히 두 개의 값에만 작용한다. 따라서 이는 사실 세 개의 더 작은 expression이 연결된 것이다.
Swift logical operator &&과 ||는 left-associative다. 즉 가장 왼쪽의 expression부터 평가된다.
복잡한 표현식의 의도를 읽기 쉽게 하기 위해 실제로는 엄밀히 괄호가 필요하지 않을 때도 괄호를 사용하는 것이 유용하다. 위의 예시 코드를 괄호를 사용하여 바꾸어 보자.
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
결과는 달라지지 않지만 전반적인 의도는 더 명확해졌다. 가독성은 언제나 간결성보다 선호된다. 의도를 명확하게 하기 위해 괄호를 사용하자.