- About Swift
- Token, Expressions, Statements
- Literal, Identifier, Keyword
- Compile, Link, Run
- Special Characters
- First Class Citizen
- Naming Convention
- Scope
- Overflow
- Short-circuit Evaluation
- Value Bindings In Switch
- Labeled Statements
- String
- Optional
- Functions
- Closure
- Collection
- Enumeration
- Structures and Classes
- Property
- Inheritance and Ploymorphism
- Initializer and Deinitializer
- Extension
- Protocol
- Generics
- Error Handling
- Selector, Keypath
- Memory, Value Type & Reference Type
- Metatype
참고
- yagom's Swift Basic
- [부스트코스] iOS 프로그래밍을 위한 스위프트 기초
- kxcoding Mastering Swift
- THE SWIFT PROGRAMMING LAGNUAGE GUIDE
Swift : A powerful open language that lets everyone build amazing apps.
기존 Objective-C의 단점을 보완하고, LLVM/Clang 컴파일러로 빌드되는 애플의 신규 프로그래밍 언어이다. 기존 Objective-C에 비해 클로저, 다중 리턴 타입, 네임스페이스, 제네릭, 타입 인터페이스 등 Objective-C에는 없었던 현대 프로그래밍 언어가 갖고 있는 기능을 많이 포함시켰으며, 이에 따라 일정한 성능 향상을 보이고 있다. 스위프트(Swift)는 안전, 성능, 소프트웨어 설계 패턴에 대한 현대적인 접근 방식을 사용하여 구축된 범용 프로그래밍 언어다.
안전성(Safe)
스위프트는 안전한 프로그래밍을 지향합니다. 소프트웨어가 배포되기 전에, 즉 프로그래밍을 하는 중에 프로그래머가 저지를 수 있는 실수를 엄격한 문법을 통하여 미연에 방지하고자 노력했습니다. 때론 너무 강제적이라고 느껴질 수 있지만 문법적 제재는 실수를 줄이는 데 도움이 됩니다. 버그를 수정하거나 실수를 찾아내는 시간을 절약할 수 있습니다. 옵셔널이라는 기능을 비롯하여 guard 구문, 오류처리, 강력한 타입통제 등을 통해 스위프트는 안전한 프로그래밍을 구현하고 있습니다.
신속성(Fast)
스위프트는 C 언어를 기반으로 한 C, C++, Objective-C와 같은 프로그래밍 언어를 대체하려는 목적으로 만들어졌습니다.
아직은 부분적으로 미흡하지만 성능 또한 C 언어 수준을 목표로 개발되었습니다.
그래서 스위프트는 성능을 예측할 수 있고 일정한 수준으로 유지할 수 있는 부분에 초점을 맞춰 개발되었습니다.
실행속도의 최적화 뿐만 아니라 컴파일러의 지속된 개량을 통해 더 빠른 컴파일 성능을 구현해 나가고 있습니다.
더 나은 표현성(Expressive)
지난 수십 년 간 컴퓨터 과학 분야는 발전해왔습니다.
이와 함께 성장한 수많은 프로그래밍 언어는 제 각각의 문법 별로 다양한 장단점이 있었습니다.
스위프트는 이를 참고해 더 사용하기 편하고 보기 좋은 문법을 구사하려 노력했습니다. 개발자들이 원하던 현대적이고 세련된 문법을 구현할 수 있었죠.
그러나 지금의 스위프트가 끝이 아닙니다. 계속된 업데이트를 통해 더욱 보기좋고 쓰기좋은 언어로 발전해 나갈 것입니다.
Swift는 다음과 같이 코드의 표현력을 높이기 위한 다른 많은 기능을 제공합니다.
- 함수 포인터와 통합된 클로저
- 튜플 및 멀티플 반환 값
- 제네릭
- 범위 또는 컬렉션에서의 빠르고 간결한 반복
- 메소드, 확장 프로그램 및 프로토콜을 지원하는 구조
- 함수형 프로그래밍 패턴 (예: map 및 filter)
- try/catch/throw를 사용한 기본 오류 처리
출처: https://swift.org/about/ , https://blog.yagom.net/526/
- 토큰, 표현식, 문장의 개념
- 공백이나 구두점으로 분리할 수 없는 가장 기본적인 요소, 가장 작은 요소. 원자와 같은 것
- token의 종류로는 Identifiers, Keywords, Punctuations, Operators, Literals
- 공백은 토큰을 구분하는 역할을 함
- 각 변수, 연산자, 함수 같은 것들이 하나 이상 모여서 하나의 값으료 표현되는 코드
- 표현식을 통해서 하나의 결과값을 도출하는 것을 표현식을 평가한다고 표현. ( Evaluate )
- 코드를 실행해서 값을 얻는다.
- 하나 이상의 표현식이 모여서 특정 작업을 실행하는 것이 statement.
- if, switch, guard, for in, while 등
- 리터럴, 식별자, 키워드의 개념
- 코드 내에서 의미가 변하지 않고 있는 그대로 사용되는 값
- 각 자료형 Literal 들이 존재 ( ex) Integer Literals, Floating-point Literal 등 )
- 코드에 포함된 요소를 구별하는데 사용되는 이름
- ex) 변수의 이름, 함수의 이름 등
- 프로그래밍 언어가 제공하는 기능을 위해서 예약되어있는 단어
- ex) var, let, func 등
- 소스코드를 작성하고 프로그램을 생성하는 과정
- 텍스트로 작성한 code를 컴퓨터가 이해 가능한 0,1 binary code로 바꿔주는 과정
- 변환에 필요한 프로그램이 compiler
- xcode에서 컴파일러는 소스코드를 분석할 때 warning, error로 구분.
- 소스코드들을 연결해주는 과정
- 링크를 담당하는 도구는 Linker
소스코드를 컴파일하면 바이너리 코드로 변환되고, 거기에 프레임워크나 라이브러리에 포함된 코드가 Link되고 실행파일이 생성됨. 이러한 과정을 하나로 묶어서 build 라고 함. 이 과정에 필요에 따라 정적 분석, unitTest 같은 부가적인 작업이 포함되는 경우도 있음. 여기 까지가 Compile Time 이고 이후는 Runtime
- 실행파일을 생성하는 방법 크게 두가지 debug, release mode
- 프로그래밍에서 자주 사용되는 특수문자 영문 명칭
! : Exclamation Mark
// ~ : Tilde
` : Grave Accent / Back Tick
@ : At Symbol
// # : Sharp / Pound / Hashtag
$ : Dollar Sign
% : Percent Sign
^ : Carrot
& : Ampersand
// * : Asterisk
() : Parentheses
// - Minus Sign / Hyphen
_ : Underscore
= : Equal Sign
[ ] : Square Bracket
{} : Curly Bracket / Brace
\ : Backslash
| : Vertical Bar / Pipe
// ; : Semicolon
// : : Colon
, : Comma
. : Period
<> : Angle Bracket
/ : Slash
? : Question Mark
- First Class Citizen 주요 특징
- can be stored in variables and data structures
- 상수와 변수에 저장할 수 있다.
- can be passed as a parameter to a function
- 파라미터로 전달할 수 있다.
- can be returned as the result of a function
- 함수에서 리턴할 수 있다.
-
Naming Convention of Swift
-
Camel Case
- UpperCamelCase
- lowerCamelCase
-
UpperCamelCase 사용하는 경우
- Class, Structure, Enumeration, Extension, Protocol
-
lowerCamelCase 사용하는 경우
- variable, constant, function, property, method, parameter
-
전역범위, 지역범위, 선언된 위치에 따른 접근 가능성 변화
-
Global Scope
-
Local or Nested Scope
Scope Rules
- 동일한 범위에 있는 변수와 상수에 접근할 수 있다.
- 동일한 범위에서는 이전에 선언되어있는 변수와 상수에 접근할 수 있다.
- local scope 에서는 상위 스코프에 선언되어있는 변수와 상수에 접근할 수 있다.
- 상위 스코프에서는 하위 스코프에 선언되어있는 변수와 상수에 접근할 수 없다.
- 서로 다른 범위에 동일한 이름이 존재한다면 가장 인접한 범위에 있는 이름을 사용한다.
- Swift 는 Operator 에서 Overflow 를 허용하지 않음.
- 그래서 Overflow를 허용해야 하는 상황에서 Overflow Operator 로 따로 처리를 해줘야함.
let a: Int8 = Int8.max
let b: Int8 = a &+ 1 // -128
let c: Int8 = Int8.min
let d: Int8 = c &- 1 // 127
let e: Int8 = Int8.max &* 2 // -2
- Swift 가 조건식을 평가하는 방법
var a = 1
var b = 1
func updateLeft() -> Bool {
a += 1
return true
}
func updateRight() -> Bool {
b += 1
return true
}
if updateLeft() || updateRight() {
// 왼쪽이 이미 true 이기 때문에 오른쪽은 리턴하지 않음
// 따라서 a = 2 , b = 1 의 결과값이 나옴.
// 이것이 단락 평가 (Short-circuit Evaluation)
}
if updateLeft() && updateRight() {
// 왼쪽이 false 일 경우 거기서 평가를 끝내고 오른쪽은 리턴하지 않음.
// 따라서 a = 2, b = 1 의 결과값이 나옴.
}
a
b
- switch 문에서의 Value Binding Pattern
- 특정 x, y 값을 각각 다른 case에 정의하고 그 정의된 상수를 또 다른 case에서 사용
let a = 1
switch a {
case let value where value < 100:
print(value)
default:
break
}
let point = (1, 2)
switch point {
case let (x, y):
print(x, y)
case (let x, let y):
print(x, y)
case (let x, var y):
print(x, y)
case let(x, _):
print(x)
}
let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
print("on the x-axis with an x value of \(x)")
case (0, let y):
print("on the y-axis with a y value of \(y)")
case let (x, y):
print("somewhere else at (\(x), \(y))")
}
// Prints "on the x-axis with an x value of 2"
- for 문, switch 문등에 lable 이름을 넣어 특정 구문을 실행하는 구문으로 사용이 가능.
outer: for i in 1...3 {
print("OUTER LOOP", i)
for j in 1...3 {
print(" inner loop", j)
break outer
}
}
//OUTER LOOP 1
// inner loop 1
- 문자열 인덱스로 특정 문자의 위치를 표현하는 방법
let str = "Swift"
let firstCh = str[str.startIndex]
print(firstCh)
let lastCharIndex = str.index(before: str.endIndex) // 정수의 경우 -1로 구할 수 있지만 문자의 경우 이 메소드를 사용해야 함.
let lastCh = str[lastCharIndex]
print(lastCh)
let secondCharIndex = str.index(after: str.startIndex)
let secondCh = str[secondCharIndex]
print(secondCh)
let thirdCharStartIndex = str.index(str.startIndex, offsetBy: 2) // 이 메소드를 사용하면 정수처럼 접근 가능.
let thirdStartCh = str[thirdCharStartIndex]
print(thirdStartCh)
let thirdCharEndIndex = str.index(str.endIndex, offsetBy: -3)
let thirdEndCh = str[thirdCharEndIndex]
print(thirdEndCh)
- 문자열을 처리할 때 메모리를 절약하기 위해 사용.
- Substring은 값을 읽기만 할 때는 원본 문자열의 메모리를 공유하고, 값을 변경하는 시점에만 새로운 메모리를 생성.
let str = "Hello, Swift"
let l = str.lowercased()
var first = str.prefix(1)
first
first.insert("!", at: first.endIndex)
str
first
let newStr = String(str.prefix(1)) // 새로운 메모리 생성
// MARK: 특정 범위 추출
let s = str[..<str.index(str.startIndex, offsetBy: 2)]
str[str.index(str.startIndex, offsetBy: 2)...]
let lower = str.index(str.startIndex, offsetBy: 2)
let upper = str.index(str.startIndex, offsetBy: 5)
str[lower ... upper]
- 추가
// 추가의 경우 append, appending, insert 등이 사용
var str = "Hello"
str.append(", ") // append 는 원본 값을 수정
str
let s = str.appending("Swift") // appending은 새로운 메모리 생성
str
s
s.appending("!!")
"File size is ".appendingFormat("%.1f", 12.3456)
var str2 = "Hello Swift"
str2.insert(contentsOf: ", ", at: str.index(str.startIndex, offsetBy: 5))
if let sIndex = str2.firstIndex(of: "S") {
str2.insert(contentsOf: "Awesome ", at: sIndex)
}
str2
str2.appending("!!")
- 수정
// 문자열 수정
var str = "Hello, Objective-C"
if let range = str.range(of: "Objective-C") {
str.replaceSubrange(range, with: "Swift") // replace 의 경우 원본 값 수정
str
}
if let range = str.range(of: "Hello") {
let s = str.replacingCharacters(in: range, with: "Hi!") // replacing 의 경우 새로운 메모리 생성
s
str
}
var s = str.replacingOccurrences(of: "Swift", with: "Awesome Swift!")
s = str.replacingOccurrences(of: "swift", with: "Awesome Swift!") // 대소문자 구분함.
s = str.replacingOccurrences(of: "swift", with: "Awesome Swift!", options: [.caseInsensitive]) // 옵션으로 구분 안하게
- 삭제
// 문자열 삭제
var str = "Hello, Awesome Swift!!!"
let lastCharIndex = str.index(before: str.endIndex)
var removed = str.remove(at: lastCharIndex)
removed
str
removed = str.removeFirst()
removed
str
str.removeFirst(2)
str
str.removeLast()
str
str.removeLast(2)
str
if let removeRange = str.range(of: "Awesome") {
str.removeSubrange(removeRange)
str
}
str.removeAll() // 파라미터 없이 삭제하면 메모리 공간까지 삭제
str
str.removeAll(keepingCapacity: true) // 메모리 공간을 삭제하지 않음.
str = "Hello, Awesome Swift!!!"
var substr = str.dropLast() // drop 은 원본과 메모리 공유 ( 그래서 타입이 Substring )
str
substr = str.dropLast(3)
substr = str.drop { (ch) -> Bool in
return ch != ","
}
substr
- compare, prefix, suffix, 대소문자
let largeA = "Apple"
let smallA = "apple"
let b = "Banana"
largeA == smallA
largeA != smallA
largeA < smallA
largeA < b
smallA < b
largeA.compare(smallA) == .orderedSame
largeA.compare(smallA) == .orderedAscending
largeA.compare(smallA) == .orderedDescending
largeA.caseInsensitiveCompare(smallA) == .orderedSame
largeA.compare(smallA, options: [.caseInsensitive]) == .orderedSame
let str = "Hello, Swift Programming!"
let prefix = "Hello"
let suffix = "Programming!"
str.hasPrefix(prefix)
str.lowercased().hasPrefix(prefix.lowercased())
str.hasSuffix(suffix)
- contains, range, commonPrefix
let str = "Hello, Swift"
str.contains("Swift")
str.lowercased().contains("swfit")
str.range(of: "Swift")
str.range(of: "swift", options: [.caseInsensitive])
let str2 = "Hello, Programming"
let str3 = str2.lowercased()
var common = str.commonPrefix(with: str2) // 공통된 접두어
common = str.commonPrefix(with: str3)
str.commonPrefix(with: str3, options: [.caseInsensitive])
str3.commonPrefix(with: str, options: [.caseInsensitive])
let a = CharacterSet.uppercaseLetters
let b = a.inverted
var str = "loRem Ipsum"
var charSet = CharacterSet.uppercaseLetters
if let range = str.rangeOfCharacter(from: charSet) {
print(str.distance(from: str.startIndex, to: range.lowerBound))
}
if let range = str.rangeOfCharacter(from: charSet, options: [.backwards]) {
print(str.distance(from: str.startIndex, to: range.lowerBound))
}
str = " A p p l e "
charSet = .whitespaces // 처음과 끝부분의 공백 제거
let trimmed = str.trimmingCharacters(in: charSet) // 문자열에서 해당 옵션에 해당하는 부분 삭제
print(trimmed)
var editTarget = CharacterSet.uppercaseLetters
editTarget.insert("#")
editTarget.insert(charactersIn: "~!@")
editTarget.remove("A")
editTarget.remove(charactersIn: "BCD")
let customCharSet = CharacterSet(charactersIn: "@.")
let email = "[email protected]"
let components = email.components(separatedBy: customCharSet)
// print -> ["userId", "example", "com"]
- 값을 가지지 않아도 되는 형식
let str: String = "Swift" // Non-Optional
let optionalStr: String? = nil // Optional \(String)
let a: Int? = nil
let b = a // b 의 type => Optional Int
// Forced Unwrapping
var num: Int? = nil
num = 123
print(num!)
num = nil
//print(num!) // fatal error
if num != nil {
print(num!)
}
num = 123
let before = num // Optional Int
let after = num! // Int
- Forced Unwrapping은 매우 위험한 코드이니 특수한 상황이 아닌이상 최대한 사용하면 안된다. ( 값이 없는 경우 앱이 터져버림.)
- 안전한 Unwrapping
var num: Int? = nil
if let num = num {
print(num)
} else {
print("empty")
}
var str: String? = "str"
guard let str = str else {
fatalError()
}
let a: Int? = 12
let b: String? = "str"
if let num = a, let str = b, str.count < 5 { // 하나라도 바인딩이 실패하면 구문 동작 안함
print(num, str)
} else {
fatalError()
}
- 간단한 이항 연산자로 nil 값 피하기
var msg = ""
var input: String? = "Swift"
if let inputName = input {
msg = "Hello, " + inputName
} else {
msg = "Hello, Stranger"
}
print(msg)
var str = "Hello, " + (input != nil ? input! : "Stranger")
print(str)
//input = nil
str = "Hello, " + (input ?? "Stranger")
print(str)
- 옵셔널을 연달아서 호출하기
- 옵셔널 체이닝의 결과는 항상 옵셔널이다
- 옵셔널 표현식이 하나라도 포함되면 옵셔널로 리턴된다.
- 옵셔널 체이닝에 포함된 표현식 중에서 하나라도 nil을 리턴한다면 나중의 표현식을 평가하지 않고 바로 nil을 리턴한다
import UIKit
struct Contacts {
var email: [String : String]?
var address: String?
func printAddress() {
return print(address ?? "no address")
}
}
struct Person {
var name: String
var contacts: Contacts?
init(name: String, email: String) {
self.name = name
contacts = Contacts(email: ["Home" : email], address: "Seoul")
}
func getContacts() -> Contacts? {
return contacts
}
}
var p = Person(name: "James", email: "[email protected]")
let a = p.contacts?.address
var optionalP: Person? = Person(name: "James", email: "[email protected]")
let b = optionalP?.contacts?.address
b
optionalP = nil
let c = optionalP?.contacts?.address
c
p.getContacts()?.address
let f: (() -> Contacts?)? = p.getContacts
f?()?.address // 함수나 메소드가 리턴하는 옵셔널 값에 접근할때는 괄호 앞뒤에 ?
let d = p.getContacts()?.printAddress() // optional void
if let _ = p.getContacts()?.printAddress() {
}
let e = p.contacts?.email?["Home"]
p.contacts?.email?["Home"]?.count
p.contacts?.address = "Daegu"
p.contacts?.address
optionalP?.contacts?.address = "Daegu"
optionalP?.contacts?.address
---
// optional pattern
let a: Int? = 0
let b: Optional<Int> = 0
if a == nil {
}
if a == .none {
}
if a == 0 {
}
if a == .some(0) {
}
if let x = a {
print(x)
}
if case .some(let x) = a {
print(x)
}
if case let x? = a {
print(x)
}
let list: [Int?] = [0, nil, nil, 3, nil, 5]
for item in list {
guard let x = item else {
continue
}
print(x)
}
for case let x? in list {
print(x)
}
- 하나의 파라미터로 두개이상의 인자를 전달할 수 있다.
- 인자는 배열의 형태로 전달 된다.
- 가변 파라미터는 개별 함수마다 하나씩만 선언할 수 있음.
- 가변 파라미터는 기본값을 가질 수 없음.
func printSum(of nums: Int...) {
var sum = 0
for num in nums {
sum += num
}
print(sum)
}
printSum(of: 1, 2, 3)
printSum(of: 1, 2, 3, 4, 5)
- copyIn, copyOut 방식으로 동작
- 함수 내부에서 값을 변경할 수 있음.
- 상수, 리터럴, 기본 값, 가변 파라미터 불가
var num1 = 12
var num2 = 34
func swapNumber(_ a: inout Int, with b: inout Int) {
let tmp = a
a = b
b = tmp
}
num1
num2
swapNumber(&num1, with: &num2)
num1 // 34
num2 // 12
- 함수안의 하나의 포현식만 있는 경우 return을 생략 해줘도 된다.
- closure, method, subscript 등에서도 동일하게 사용
func add(a: Int, b: Int) -> Int {
// return a + b Explicit Return
a + b // Implicit Return
// print(a + b) 표현식이 두개 이상인 경우 에러
}
add(a: 1, b: 2)
- Swift는 최대한 단순하게 작성하는 것을 선호.
- 문법 최적화 규칙
- 파라미터와 리턴형을 생략할 수 있다.
- 파라미터 이름은 인자 이름 축약 (Shorthand Arguments Names)로 대체 ( 이 경우 파라미터 이름과 in keyward는 생략 )
- 단일 리턴문인 경우 Implicit Return ( return keyward 생략 )
- 인라인 클로저에서 후위 클로저로 변경
- 괄호 사이에 파라미터가 더이상 없다면 괄호를 생략
let products = [
"MacBook Air", "MacBook Pro",
"iMac", "iMac Pro", "Mac Pro", "Mac mini",
"iPad Pro", "iPad", "iPad mini",
"iPhone Xs", "iPhone Xr", "iPhone 8", "iPhone 7",
"AirPods",
"Apple Watch Series 4", "Apple Watch Nike+"
]
var proModels = products.filter { (name: String) -> Bool in
return name.contains("pro")
}
products.filter {
$0.contains("pro")
}
proModels.sort { (lhs: String, rhs: String) -> Bool in
return lhs.caseInsensitiveCompare(rhs) == .orderedDescending
}
proModels.sort {
$0.caseInsensitiveCompare($1) == .orderedDescending
}
- 시작 시점과 종료 시점이 특정되지 않음.
- 함수가 종료 된 뒤에 closure를 실행하려면 escaping 해줘야 함.
// Non Escaping
func performNonEscaping(closure: () -> ()) {
print("start")
closure()
print("end")
}
performNonEscaping {
print("closure")
}
// Escaping
func performEscaping(closure: @escaping () -> ()) {
print("start")
var a = 12
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
closure()
a = 13
print(a)
}
print("end")
}
performEscaping {
print("closure")
}
- 검색속도가 중요한 경우에 배열대신 사용
- 배열과 달리 인덱스를 사용하지않고, 정렬되어있지 않음.
- 중복된 요소를 허용하지않음.
- Hashing 알고리즘을 사용하기 때문에 속도가 빠름
let set: Set<Int> = [1, 2, 2, 3, 3, 3]
set.count
set.contains(1)
var words = Set<String>()
var insertResult = words.insert("Swift")
insertResult.inserted // true
insertResult.memberAfterInsert
insertResult = words.insert("Swift")
insertResult.inserted // false
insertResult.memberAfterInsert
var updateResult = words.update(with: "Swift")
updateResult
updateResult = words.update(with: "Apple")
updateResult // nil -> nil로 리턴되면 insert, 값으로 리턴되면 update
var value = "Swift"
value.hashValue
updateResult = words.update(with: value)
updateResult
value = "Hello"
updateResult = words.update(with: value)
updateResult
struct SampleData: Hashable {
var hashValue: Int = 123
var data: String
init(_ data: String) {
self.data = data
}
static func == (lhs: SampleData, rhs: SampleData) -> Bool {
return lhs.hashValue == rhs.hashValue
}
}
var sampleSet = Set<SampleData>()
// 새로운 요소로 추가
var data = SampleData("Swift")
data.hashValue
var r = sampleSet.insert(data)
r.inserted
r.memberAfterInsert
sampleSet
data.data = "Hello"
data.hashValue
r = sampleSet.insert(data)
r.inserted
r.memberAfterInsert
sampleSet // data -> "Swift"
sampleSet.update(with: data)
sampleSet // data -> "Hello" 로 update
var a: Set = [1, 2, 3, 4, 5, 6, 7, 8, 9]
var b: Set = [1, 3, 5, 7, 9]
var c: Set = [2, 4, 6, 8, 10]
let d: Set = [1, 7, 5, 9, 3]
// 부분집합, 진부분집합
a.isSubset(of: a) // 부분집합
a.isStrictSubset(of: a) // 진부분집합
b.isSubset(of: a)
b.isStrictSubset(of: a)
// 상위집합
a.isSuperset(of: a)
a.isStrictSuperset(of: a)
a.isSuperset(of: b)
a.isStrictSuperset(of: b)
a.isSuperset(of: c)
a.isStrictSuperset(of: c)
// 교집합
a.isDisjoint(with: b) // false 일 경우에 교집합.
a.isDisjoint(with: c)
b.isDisjoint(with: c)
// 집합연산
a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
b = [1, 3, 5, 7, 9]
c = [2, 4, 6, 8, 10]
// 합집합
var result = b.union(c)
result = b.union(a)
b.formUnion(c) // 원본 변경
a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
b = [1, 3, 5, 7, 9]
c = [2, 4, 6, 8, 10]
// 교집합
result = a.intersection(b)
result = c.intersection(b)
a.formIntersection(b)
b.formIntersection(c)
a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
b = [1, 3, 5, 7, 9]
c = [2, 4, 6, 8, 10]
// 여집합
result = a.symmetricDifference(b)
result = c.symmetricDifference(b)
a.formSymmetricDifference(b)
a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
b = [1, 3, 5, 7, 9]
c = [2, 4, 6, 8, 10]
// 차집합
result = a.subtracting(b)
a.subtract(b) // 원본 변경
- collection 열거
- for-in 과 forEach 의 차이점?
- for-in은 Swift가 제공하는 built-in 함수
- forEach는 collection에서 제공하는 기능이며 closure 방식으로 사용
- 그렇기 때문에, forEach는 break, continue 문 사용 불가
- forEach 에서 클로저 내 return을 사용하는 경우, 밖의 스코프에는 영향을 주지 않고 오직 현재 호출 클로저만 빠져나간다
// for-in
var arr = [1, 2, 3]
for num in arr {
print(num)
}
var set: Set = [1, 2, 3]
for num in set.sorted() {
print(num)
}
var dict = ["A" : 1, "B" : 2, "C" : 3]
for (key, value) in dict.sorted(by: < ) {
print(key, value)
}
// forEach
arr.forEach { (num) in
print(num)
}
set.forEach { (num) in
print(num)
}
dict.forEach { (elem) in
print(elem.key, elem.value)
}
func withForIn() {
print(#function)
for num in arr {
print(num)
}
break
return
}
func withForEach() {
print(#function)
arr.forEach { (num) in
print(num)
}
return
}
withForIn()
withForEach()
- Swift가 제공하는 경량 collection
- 딕셔너리에서 키값은 반드시 해셔블 프로토콜을 채용한 타입만 사용해야함, 동일한 키를 한번만 저장할 수 있음, 정렬 x
- 키형식의 제한이 없음. 동일한 키를 두번이상 저장하는것도 가능, 저장한 순서를 유지, 접근할 때 키로 접근하는 딕셔너리와 달리 인덱스로 접근.
- 순서가 중요한 경우에 딕셔너리대신 사용 ( 그냥 딕셔너리 쓰고 소트하면 되지 않낭? )
let words: KeyValuePairs = ["A" : "Apple", "B" : "Banana", "C": "City"]
words[0]
words[0].key
words[0].value
for elem in words {
print(elem)
}
words.forEach { (elem) in
print(elem)
}
- 열거형은 독립적인 자료형
- 열거형은 코드의 가독성과 안전성을 높여줌
enum Alignment {
case left
case right
case center
}
Alignment.left
var textAlignment = Alignment.center
textAlignment = .right
switch textAlignment {
case .left:
print("left")
case .right:
print("left")
case .center:
print("left")
}
- enum에 원시값을 지정해줄 수 있음
enum Alignment: Int {
case left
case right = 100
case center
}
Alignment.left.rawValue
Alignment.right.rawValue
Alignment.center.rawValue
// Alignment.left.rawValue = 10 // 원시값은 immutable
Alignment(rawValue: 0) // left
Alignment(rawValue: 200) // nil
enum Weekday: String {
case sunday
case monday = "MON"
case tuesday
case wednesday
}
Weekday.sunday.rawValue
Weekday.monday.rawValue
enum ControlChar: Character { // 원시값을 Character로 지정한 경우에는 반드시 원시값을 지정해줘야함.
case tab = "\t"
case newLine = "\n"
}
- 연관 값을 사용하는 Enum case
enum VideoInterface {
case dvi(width: Int, height: Int)
case hdmi(Int, Int, Double, Bool)
case displayPort(CGSize)
}
var input = VideoInterface.dvi(width: 2048, height: 1536)
switch input {
case .dvi(width: 2048, height: 1536):
print("dvi 2048 x 1536")
case .dvi(width: 2048, _):
print("dvi 2048 x Any")
case .dvi:
print("dvi")
case .hdmi(let width, let height, let version, let audioEnabled):
print("hdmi \(width) x \(height) version: \(version) audioEnabled: \(audioEnabled)")
case let .displayPort(size):
print("dp \(size)")
}
input = .hdmi(1, 1, 1, true)
- 조건문과 반복문에서 연관 값을 매칭
enum Transportaion {
case bus(number: Int)
case taxi(company: String, number: String)
case subway(lineNumber: Int, express: Bool)
}
var tpt = Transportaion.bus(number: 7)
switch tpt {
case .bus(let n):
print(n)
case .taxi(let c, var n):
print(c, n)
case let .subway(l, e):
print(l, e)
}
tpt = Transportaion.subway(lineNumber: 2, express: false)
if case let .subway(2, express) = tpt { // 2호선 인지 확인하고 급행인지 아닌지 분기해주는 코드
if express {
} else {
}
}
if case .subway(_, true) = tpt {
print("express")
}
let list = [
Transportaion.subway(lineNumber: 2, express: false),
Transportaion.bus(number: 402),
Transportaion.subway(lineNumber: 7, express: true),
Transportaion.taxi(company: "SeoulTaxi", number: "1234")
]
for case let .subway(n, _) in list {
print("1. subway \(n)")
}
for case let .subway(n, true) in list {
print("2. subway \(n)")
}
for case let .subway(n, true) in list where n == 2 {
print("3. subway \(n)")
}
- 모든 case를 열거할 수 있게 도와주는 CaseIterable 프로토콜
enum Weekday: Int, CaseIterable { // CaseIterable protocol 을 채택할 경우, Allcases라는 collection 프로퍼티가 생성됨.
case sunday
case monday
case tuesday
case wednesday
case thursday
case friday
case saturday
}
let rnd = Int.random(in: 0...Weekday.allCases.count)
Weekday(rawValue: rnd)
Weekday.allCases.randomElement()
for w in Weekday.allCases {
print(w)
}
- 새로운 case를 안전하게 처리
- default 앞에 @unknown 을 붙여주는 경우에 케이스 처리가 안된 부분이 있을 경우 경고 처리를 해줄 수 있음
enum ServiceType {
case onlineCourse
case offlineCamp
case onlineCamp
case seminar
}
let selectedType = ServiceType.onlineCourse
switch selectedType {
case .onlineCourse:
print("send online course email")
case .offlineCamp:
print("send offline camp email")
case .onlineCamp:
print("send online camp email")
@unknown default:
break
}
- Custom Data Type을 만들기 위해 필요한 Enumeration, Structure, Class
- Structure, class 모두 멤버변수로 property, method, initializer, subscript, extension, protocol 가능
- Structure는 Value Type 이며 Stack에 저장.
- Class는 Reference Type 이며 Heap에 저장.
- Structure는 Deinitializer, Inheritance, Reference Counting 이 불가하지만 Class 는 모두 가능
- 값 형식인 Struct에서 속성을 바꾸는 메소드를 구현할 때에는 반드시 mutating으로 선언해야함.
예시
struct PersonStruct {
var firstName: String
var lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
var fullName: String { // computed property
return "\(firstName) \(lastName)"
}
mutating func uppercaseName() { // property 를 변경하려면 mutating
firstName = firstName.uppercased()
lastName = lastName.uppercased()
}
}
class PersonClass {
var firstName: String
var lastName: String
init(firstName: String, lastName: String) { // class 객체를 생성할때 사용하는 생성함수 init
self.firstName = firstName // parm과 똑같을경우 self.
self.lastName = lastName
}
var fullName: String {
return "\(firstName) \(lastName)"
}
func uppercaseName() { // class에선 mutating 사용하지 않음
firstName = firstName.uppercased()
lastName = lastName.uppercased()
}
}
var personStruct1 = PersonStruct(firstName: "Mino", lastName: "Jo")
var personStruct2 = personStruct1
var personClass1 = PersonClass(firstName: "Mino", lastName: "Jo")
var personClass2 = personClass1
personStruct2.firstName = "Minjin"
personStruct1.firstName // = Mino // Struct는 값 타입이기 때문에
personStruct2.firstName // = Minjin // 기존의 데이터 값을 복사해서 새로운 데이터를 만듦.
personClass2.firstName = "Minjin"
personClass1.firstName // = Minjin // Class는 참조 타입이기 때문에
personClass2.firstName // = Minjin // 첫 데이터를 참조해서 그 데이터에 덮어 씌움.
personClass2 = PersonClass(firstName: "Babo", lastName: "Jo")
personClass1.firstName // = Minjin
personClass2.firstName // = Babo
personClass1 = personClass2
personClass1.firstName // = Babo
personClass2.firstName // = Babo
- 두 object를 "같다, 다르다" 로 비교해야 하는 경우
- copy 된 각 객체들이 독립적인 상태를 가져야 하는 경우
- 코드에서 오브젝트의 데이터를 여러 스레드 걸쳐 사용할 경우 ( 안전하게 사용 가능 )
- 두 object의 인스턴스 자체가 같음을 확인해야 할때
- 하나의 객체가 필요하고, 여러 대상에 의해 접근되고 변경이 필요한 경우
일단 struct로 쓰자. 그리고 나서 class를 사용해야할 경우 class로 포팅하자. swift는 struct를 좋아한다.
class Position {
var x: Double
var y: Double
init() { // 생성자는 속성 초기화가 가장 중요한 규칙.
x = 0.0
y = 0.0
}
init(value: Double) {
x = value
y = value
}
}
let a = Position() // 인스턴스 생성
a.x // 0으로 초기화
a.y
let b = Position(value: 100)
b.x
b.y
- 지연 저장 속성
- lazy 변수는 처음 사용되기 전까지는 연산이 되지 않는다.
- struct와 class에서만 사용 가능
- Computed Property에는 lazy 키워드 사용 불가 ( 처음 사용될 때 메모리에 값을 올리고 그 이후 부터는 계속해서 메모리에 올라온 값을 사용. 사용할때 마다 값을 연산하여 사용하는 computed property에서는 사용할 수 없음. )
- lazy에 어떤 특별한 연산을 통해 값을 넣어주기 위해서는 코드 실행 블록인 closure를 사용
struct Image {
init() {
print("New Image")
}
}
struct BlogPost {
let title: String = "Title"
let content: String = "Content"
lazy var attachment: Image = Image()
let date: Date = Date()
lazy var formattedDate: String = {
let f = DateFormatter()
f.dateStyle = .long
f.timeStyle = .medium
return f.string(from: date)
}()
}
var post = BlogPost()
post.attachment
post.date
- 계산 속성
- 수학적 계산이 아니라 다른 속성을 기반으로 속성값이 결정된다는 의미
- Stored Property는 값을 저정할 메모리 공간을 가지고 있음
- Computed Property는 값을 지정할 메모리 공간을 가지고 있지 않음
- 다른 속성에 저장된 값을 읽어서 필요한 계산을 실행한 다음에 리턴하거나, 속성으로 전달된 값을 다른 속성에 저장.
class Person {
var name: String
var yearOfBirth: Int
init(name: String, year: Int) {
self.name = name
self.yearOfBirth = year
}
var age: Int {
get {
let calender = Calendar.current
let now = Date()
let year = calender.component(.year, from: now)
return year - yearOfBirth
}
set {
let calender = Calendar.current
let now = Date()
let year = calender.component(.year, from: now)
yearOfBirth = year - newValue
}
}
}
let p = Person(name: "Mino", year: 1996)
p.age
p.age = 50
p.yearOfBirth
- 프로퍼티 값이 변경되기 직전, 직후를 감지
- 프로퍼티 옵저버를 사용하기 위해서는 프로퍼티의 값이 반드시 초기화 되어 있어야 함.
class Size {
var width = 0.0 {
willSet {
print(width, "=>", newValue)
}
didSet {
print(oldValue, "=>", width)
}
}
}
let s = Size()
s.width = 123
- class에서 상속을 통해 Super Class로부터 멤버를 상속
- final class는 상속이 금지된 class이므로 상속 불가
- Super Class로부터 상속한 멤버를 재정의 -> Overriding
- Overriding이 가능한 대상은 methods, properties, subscripts, initializers
- Super Class를 기반으로 하는 방법과 아예 새롭게 재정의 하는 방법이 있음.
class Figure {
var name = "UnKnown"
init(name: String) {
self.name = name
}
func draw() {
print("draw \(name)")
}
}
class Circle: Figure {
var radius = 0.0
}
let c = Circle(name: "Circle")
c.radius
c.name
c.draw()
final class Rectangle: Figure { // final class는 상속이 금지된 class
var widht = 0.0
var height = 0.0
}
// class Square: Rectange { //error
//
//}
----
// Overriding
class Figure {
var name = "Unknown"
init(name: String) {
self.name = name
}
func draw() {
print("draw \(name)")
}
}
class Circle: Figure {
var radius = 0.0
var diameter: Double {
return radius * 2
}
// override func draw() { // Super Class 를 무시하고 새롭게 구현
// print("Overriding \(name)")
// }
override func draw() { // Super Class 를 기반으로 구현
super.draw()
print("Overriding \(name)")
}
}
let c = Circle(name: "Circle")
c.draw()
class Oval: Circle {
override var radius: Double {
willSet {
print(newValue)
}
didSet {
print(oldValue)
}
}
override var diameter: Double { // 읽기 전용 프로퍼티 상속은 읽기만 가능, 프로퍼티 옵저버도 불가
get {
return super.diameter
}
set {
super.radius = newValue / 2
}
}
}
- Overriding은 상속된 멤버를 현재 클래스에 적합하게 다시 구현할떄 사용
- Overloading은 하나의 형식에서 동일한 이름을 가진 다수의 멤버를 구현할때 사용
- 스위프트는 Overloading을 지원함. 그렇기 때문에 이름이 같아도 자료형이 다르면 다른것으로 인식
- 함수, 메소드, 서브스크립트, 생성자 -> Overloading을 지원
- Overloading Rule #1 - 함수 이름이 동일하면 파라미터 수로 식별
- Overloading Rule #2 - 함수 이름, 파라미터 수가 동일하면 파라미터 자료형으로 식별
- Overloading Rule #3 - 함수 이름, 파라미터가 동일하면 Argument Label로 식별
- Overloading Rule #4 - 함수 이름, 파라미터, Argument Label이 동일하면 리턴형으로 식별 // 리턴형으로 식별은 가급적이면 안하는게 좋음
func process(value: Int) {
print("Int")
}
func process(value: String) {
print("String")
}
func process(value: String, anotherValue: String) {
}
func process(_ value: String) {
print("str")
}
func process(value: Double) -> Int {
return Int(value)
}
func process(value: Double) -> String? {
return String(value)
}
process(value: 0)
process(value: "")
process("str")
var results: Int = process(value: 1234)
struct Rectangle {
func area() -> Double {
return 0.0
}
static func area() -> Double {
return 1
}
}
let r = Rectangle()
r.area()
Rectangle.area()
- 타입캐스팅은 인스턴스의 타입을 확인하거나, 인스턴스의 타입을 슈퍼클래스 또는 서브클래스 타입처럼 다루기위해 사용
class Figure {
let name: String
init(name: String) {
self.name = name
}
func draw() {
print("draw \(name)")
}
}
class Triangle: Figure {
override func draw() {
super.draw()
print("🔺")
}
}
class Rectangle: Figure {
var width = 0.0
var height = 0.0
override func draw() {
super.draw()
print("⬛️ \(width) x \(height)")
}
}
class Square: Rectangle {
}
class Circle: Figure {
var radius = 0.0
override func draw() {
super.draw()
print("🔴")
}
}
// Type Check Operator
// type check는 Runtime에서 확인
let num = 123
num is Int
num is Double
num is String
let t = Triangle(name: "Triangle")
let r = Rectangle(name: "Rect")
let s = Square(name: "Square")
let c = Circle(name: "Circle")
r is Rectangle
r is Figure
r is Square
// Compile Time Cast
var upcasted: Figure = s
let nsstr = "str" as NSString
upcasted = s as Figure
// Runtime Cast
upcasted as? Square // Conditional Cast
upcasted as! Square // Forced Cast -> 느낌표는 최대한 사용 금지
upcasted as? Rectangle
upcasted as! Rectangle
upcasted as? Circle // nil
//upcasted as! Circle // crash
if let c = upcasted as? Circle {
}
let list = [t, r, s, c] // 가장 인접한 Super Class인 Figure Class 로 upcasting
for item in list {
item.draw() // 다형성 Polymorphism -> 업캐스팅 되어있는 인스턴스를 통해서 메소드를 호출하더라도 실제 형식에서 오버라이딩한 메소드가 호출된다.
if let c = item as? Circle {
c.radius
}
}
class Position {
var x = 0.0
var y: Double // 기본값이 없을경우 init 해줘야함
var z: Double? // 옵셔널은 기본값이 없을경우 기본으로 nil로 초기화
init() {
y = 0.0
}
// 평소에 init을 안해도 되는 부분은 Compiler에서 Default Initializer를 제공하기 때문.
}
let p = Position()
class SizeObj {
var width = 0.0
var height = 0.0
init(width: Double, height: Double) {
self.width = width
self.height = height
}
convenience init(value: Double) {
self.init(width: value, height: value) // 이런식으로 다른 initializer를 호출하는것은 Initializer Delegation
}
}
struct SizeValue {
var width = 0.0
var height = 0.0
}
let s = SizeValue()
SizeValue(width: 1.2, height: 3.4) // Memberwise Initializer
// 구조체에서 직접 Initializer를 구현할경우 더이상 사용할 수 없음.
// 그래서 Default Initializer 처럼 sturct는 Memberwise Initializer를 제공함
- class에서 사용하는 Initializer는 지정 생성자와 간편 생성자로 나뉨. ( Designated Initializer, Convenience Initializer )
- class의 메인 Initializer는 Designated Initializer. ( 클래스가 가진 모든 속성을 초기화 )
- Convenience Initializer는 다양한 초기화 방법을 구현하기 위한 유틸리티 성격을 지님.
class Position {
var x: Double
var y: Double
// Designated Initializer
init(x: Double, y: Double) {
self.x = x
self.y = y
}
// Convenience Initializer
convenience init(x: Double) {
self.init(x: x, y: 0.0)
}
}
class Figure {
var name: String
init(name: String) {
self.name = name
}
func draw() {
print("draw \(name)")
}
convenience init() {
self.init(name: "UnKnown")
}
}
// initailizer inheritance
class Rectangle: Figure {
var width: Double = 0.0
var height: Double = 0.0
init(name: String, width: Double, height: Double) {
self.width = width
self.height = height
super.init(name: name)
}
override init(name: String) {
width = 0
height = 0
super.init(name: name)
}
convenience init() { // convenience는 오버라이딩이란 개념이 적용되지 않음.
self.init(name: "UnKnown")
}
}
- 서브클래스에서 반드시 동일한 이니셜라이저를 구현 하도록 해주는 생성자.
class Figure {
var name: String
required init(name: String) {
self.name = name
}
func draw() {
print("draw \(name)")
}
}
class Rectangle: Figure {
var width = 0.0
var height = 0.0
init() {
width = 0.0
height = 0.0
super.init(name: "unknown")
}
required init(name: String) {
width = 0.0
height = 0.0
super.init(name: name)
fatalError("init(name:) has not been implemented")
}
}
- Initializer Delegation은 초기화 코드에서 중복을 최대한 제거하고, 모든 속성을 효율적으로 초기화하기 위해서 사용.
- 값형식과 참조형식에서 서로 다른 규칙으로 구현
- Initializer Delegation Rules
- designated 생성자는 반드시 슈퍼 클래스의 designated 생성자를 호출해야 한다.
- convenience 생성자는 반드시 같은 클래스의 다른 생성자를 호출 해야한다.
- Convenience 생성자를 호출 했을 때 최종적으로는 반드시 designated 생성자가 호출 되어야 한다.
struct Size {
var width: Double
var height: Double
init(w: Double, h: Double) {
width = w
height = h
}
init(value: Double) { // Initializer Delegation 첫번째 이니셜라이저에게 위임, 유지보수가 쉬워짐.
self.init(w: value, h: value)
}
}
class Figure {
let name: String
// delegate across
init(name: String) { // designated
self.name = name
}
convenience init() {
self.init(name: "unknown")
}
}
class Rectangle: Figure {
var width = 0.0
var height = 0.0
//Rule1(Delegate Up)
init(n: String, w: Double, h: Double) {
width = w
height = h
super.init(name: n)
}
convenience init(value: Double) {
self.init(n: "rect", w: value, h: value)
}
}
class Squre: Rectangle { //delegated up 불가
convenience init(value: Double) {
self.init(n: "squre", w: value, h: value)
}
convenience init() {
self.init(value: 0.0)
}
}
- 이름 그대로 형식을 확장하는데 사용
- 확장 가능한 것 : Class / Structure / Enumeration / Protocol
- 멤버를 추가하는 것은 가능 하지만, 기존 멤버를 오버라이딩 하는 것은 불가능(상속을 통해 서브클래싱 해야됨)
struct Size {
var width = 0.0
var height = 0.0
}
extension Size {
var area: Double {
return width * height
}
}
let s = Size()
s.width
s.height
s.area
extension Size: Equatable {
//비교 연산
public static func == (lhs: Size, rhs: Size) -> Bool {
return lhs.width == rhs.width && lhs.height == rhs.height
}
}
// Adding Properties
//Date 형식에 년도를 리턴하는 속성 추가
extension Date {
var year: Int {
let cal = Calendar.current
return cal.component(.year, from: self)
}
var month: Int {
let cal = Calendar.current
return cal.component(.month, from: self)
}
}
//let today = Date()
//today.year
//today.month
//Double 형식에 라디안/디그리 변환 속성 추가
extension Double {
var radianValue: Double {
return (Double.pi * self) / 180.0
}
var degreeValue: Double {
return self * 180.0 / Double.pi
}
}
let dv = 45.0
dv.radianValue
dv.radianValue.degreeValue
// Adding Methods
//Double 형식에 화씨/섭씨 온도 변환 메소드 추가
extension Double {
func toFahrenheit() -> Double {
return self * 9 / 5 + 32
}
func toCelsius() -> Double {
return (self - 32) * 5 / 9
}
static func converToFahrenheit(from celsius: Double) ->
Double {
return celsius.toFahrenheit()
}
static func converToCelsius(from fahrenheit: Double) ->
Double {
return fahrenheit.toCelsius()
}
}
let c = 30.0
c.toFahrenheit() //화씨 변환
Double.converToFahrenheit(from: 30.0)
//Date 형식에 문자열 포멧팅 메소드 추가
extension Date {
func toString(format: String = "yyyyMMdd") ->
String {
let privateFormatter = DateFormatter()
privateFormatter.dateFormat = format
return privateFormatter.string(from: self)
}
}
let today = Date()
today.toString()
today.toString(format: "MM/dd/yyyy")
//String 형식에 랜덤 문자열 생성 메소드 추가
//지정된 길이의 랜덤 문자열 생성을 스트링 형식에 추가
extension String {
static func random(length: Int, characterIn chars:
String =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJZ1234567890") -> String {
var randomString = String()
randomString.reserveCapacity(length) //지정 길이만큼의 리소스 확보
for _ in 0 ..< length {
guard let char = chars.randomElement() else {
continue
}
randomString.append(char)
}
return randomString
}
}
String.random(length: 5)
// Adding Initializer
//Date 형식에 년,월,일로 초기화 하는 생성자 추가
extension Date {
init?(year: Int, month: Int, day: Int) {
let cal = Calendar.current
var comp = DateComponents()
comp.year = year
comp.month = month
comp.day = day
guard let date = cal.date(from: comp) else {
return nil
}
self = date //셀프로 초기화
}
}
Date(year: 2014, month: 4, day: 16)
//UIColor 클래스에 RGB 파라미터를 받는 생성자 추가
extension UIColor {
convenience init(red: Int, green: Int, blue: Int) {
self.init(red: CGFloat(red) / 255, green: CGFloat(green) / 255,
blue: CGFloat(blue) / 255, alpha: 1.0)
}
}
UIColor(red: 0, green: 0, blue: 255)
struct Size2 {
var width = 0.0
var height = 0.0
}
extension Size2 {
// extenstion 으로 초기화 해주면 기본 생성자와 함께 사용가능하다
init(value: Double) {
width = value
height = value
}
}
Size2()
Size2(width: 12, height: 34)
// Adding Subscript
//String 형식에 정수 인덱스를 처리하는 서브스크립트 추가
extension String {
subscript(idx: Int) -> String? {
guard (0 ..< count).contains(idx) else {
return nil
}
let target = index(startIndex, offsetBy: idx)
return String(self[target])
}
}
let str = "Swift"
str[1]
str[100]
//Date 형식에 컴포넌트를 리턴하는 서브스트립트 추가
extension Date {
subscript(component: Calendar.Component) -> Int? {
let cal = Calendar.current
return cal.component(component, from: self)
}
}
let today1 = Date()
today1[.year]
today1[.month]
today1[.day]
프로토콜(Protocol)
- 인터페이스
- 최소한으로 가져아 할 속성이나 메서드를 정의.
- 구현은 하지 않음. 정의만!
- 공통적으로 제공하는 멤버 목록.
- 구현해야하는 멤버가 선언되어있음.
// Defining Protocols
protocol Something {
func doSomething()
}
// Adopting Protocols
struct Size: Something {
func doSomething() {
print(#function)
}
}
// Class-Only Protocols
protocol SomethingObject: AnyObject, Something {
}
//struct Value: SomethingObject {
//불가
//}
class Object: SomethingObject {
func doSomething() {
print(#function)
}
}
// Property Requirements
// 프로토콜에서 속성은 무조건 var 키워드로!
protocol Figure {
static var name: String { get set }
var age: Int { get }
}
struct Rectangle: Figure {
static var name: String = "Rect"
var age: Int
}
class Circle: Figure {
var age: Int = 0
class var name: String {
get {
return "Circle"
}
set {
}
}
}
// Method Requirements
// Method Head 부분만 선언.
protocol Resettable {
mutating func reset()
static func reset()
}
class Size: Resettable {
var width = 0.0
var height = 0.0
func reset() {
width = 0.0
height = 0.0
}
static func reset() {
}
}
struct ValueSize: Resettable {
var width = 0.0
var height = 0.0
mutating func reset() { // 값 형식의 인스턴스 메소드에서 속성값을 바꾸러면 mutatitng keyword 필요
width = 0.0
height = 0.0
}
static func reset() {
}
}
// Initializer Requirements
// method와 마찬가지로 바디 생략
protocol Figure {
var name: String { get }
init(name: String)
}
struct Rectangle: Figure {
var name: String // Memberwise 생성자로 요구사항 충족
}
class Circle: Figure {
var name: String
required init(name: String) {
self.name = name
}
}
final class Triangle: Figure { // final class 는 더이상 상속을 고려하지 않아도 되기 때문에 required init 불필요
var name: String
init(name: String) {
self.name = name
}
}
class Oval: Circle {
var prop: Int
init() {
prop = 0
super.init(name: "Oval")
}
required convenience init(name: String) {
self.init()
}
}
protocol Grayscale {
init?(white: Double)
}
struct Color: Grayscale {
init(white: Double) {
}
}
// Subscript Requirements
protocol List {
subscript(idx: Int) -> Int { get }
}
struct DataStore: List {
subscript(idx: Int) -> Int {
get { // get 요구사항만 충족시켜도 가능.
return 0
}
set {
}
}
}
// Optional Requirements
// Optional 형식을 지칭하는것이 아닌, 단어 그대로 선택형 이라는 뜻
// class 에서만 채용이 가능함. -> AnyObject protocol이 자동으로 상속되기 떄문.
@objc protocol Drawable {
@objc optional var strokeWidth: Double { get set }
@objc optional var strokeColor: UIColor { get set }
func draw()
@objc optional func reset()
}
class Rectangle: Drawable {
func draw() {
}
}
let r: Drawable = Rectangle()
r.draw()
r.strokeWidth
r.strokeColor
r.reset?()
- 값의 동일성을 비교할 수 있는 타입이라면, 반드시 구현해야하는 프로토콜
- int, double, string 같은 타입들은 이미 Equatable 채용 따라서 ==, != 연산자 사용이 가능
- 연관값이 선언되지 않은 열거형은 Equatable 구현이 자동으로 추가되고, 연관값을 가지고 있고 모든 연관값의 형식이 Equatable을 구현한 형식인 경우에도 자동으로 추가.
enum Gender {
case female
case male
}
Gender.female == Gender.male
struct MySize {
let width: Double
let height: Double
}
enum VideoInterface: Equatable {
case dvi(width: Int, height: Int)
case hdmi(width: Int, height: Int, version: Double, audioEnabled: Bool)
case displayPort(size: CGSize)
}
let a = VideoInterface.hdmi(width: 2560, height: 1440, version: 2.0, audioEnabled: true)
let b = VideoInterface.displayPort(size: CGSize(width: 3840, height: 2160))
a == b
// Equatable for Structures
struct Person: Equatable {
let name: String
let age: Int
}
let a = Person(name: "Steve", age: 50)
let b = Person(name: "Paul", age: 27)
a == b
// Comparable for Classes
class Person { // class는 자동으로 추가해주지 않음.
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
extension Person: Equatable {
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name && lhs.age == rhs.age
}
}
let a = Person(name: "Steve", age: 50)
let b = Person(name: "Paul", age: 27)
a == b
a != b
- 딕셔너리 키 타입과 셋 요소 타입은 반드시 Hashable 프로토콜을 채용해야한다.
- Hash 장점: 값의 유일성을 보장하고 검색 속도가 빠름.
- 열거형 선언에 연관값이 포함되어 있지 않다면 자동으로 채용
enum ServiceType {
case onlineCourse
case offlineCamp
}
let types: [ServiceType: String]
let typeSet: Set = [ServiceType.onlineCourse]
enum VideoInterface: Hashable {
case dvi(width: Int, height: Int)
case hdmi(width: Int, height: Int, version: Double, audioEnabled: Bool)
// case displayPort(size: CGSize)
}
let interfaces: [VideoInterface: String]
let interfaceSEt: Set = [VideoInterface.dvi(width: 1024, height: 768)]
// Hashable for Structures
struct Person: Hashable {
let name: String
let age: Int
}
let set: Set = [Person(name: "Tom", age: 12)]
// Hashable for Classes
class Person { // Hashable 의 경우 Equatable을 상속하고 있기 때문에 Equatable과 동일하게 따로 구현 해줘야함.
let name: String
let age: Int
init() {
name = "Mino"
age = 0
}
}
extension Person: Hashable, Equatable {
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name && lhs.age == rhs.age
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
hasher.combine(age)
}
}
- 값의 크기와 순서를 비교해야 하는 타입에서 필수로 구현해야하는 프로토콜
- ==, != --> Equatable / >, >=, <, <= --> Comparable
enum Weekday: Int {
case sunday
case monday
case tuesday
case wednesday
case thursday
case friday
case saturday
}
extension Weekday: Comparable {
static func < (lhs: Weekday, rhs: Weekday) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
Weekday.sunday < Weekday.monday
- 제네릭 함수는 형식에 관계없이 하나의 구현으로 모든 자료형을 처리.
- 제네릭을 사용하면 형식에 의존하지 않는 범용 코드를 작성할 수 있고, 코드의 재사용성과 유지보수가 편해진다는 장점이 있음.
func swapInteger(lhs: inout Int, rhs: inout Int) {
let tmp = lhs
lhs = rhs
rhs = tmp
}
var a = 10
var b = 20
swapInteger(lhs: &a, rhs: &b)
a
b
func swapInteger16(lhs: inout Int16, rhs: inout Int16) {
// ...
}
func swapInteger64(lhs: inout Int64, rhs: inout Int64) {
// ...
}
func swapDouble(lhs: inout Double, rhs: inout Double) {
// ...
}
func swapValue<T: Equatable>(lhs: inout T, rhs: inout T) { // T -> Type Parameter
if lhs == rhs {
return
}
let tmp = lhs
lhs = rhs
rhs = tmp
print("first func")
}
a = 1
b = 2
swapValue(lhs: &a, rhs: &b)
a
b
var c = 1.2
var d = 3.4
swapValue(lhs: &c, rhs: &d)
c
d
func swapValue(lhs: inout String, rhs: inout String) {
print("")
if lhs.caseInsensitiveCompare(rhs) == .orderedSame {
return
}
let tmp = lhs
lhs = rhs
rhs = tmp
print("second func")
}
var aS = 1
var bS = 2
swapValue(lhs: &aS, rhs: &bS)
var cS = "Swift"
var dS = "Programming"
swapValue(lhs: &cS, rhs: &dS)
cS
dS
- 제네릭 함수를 추가하면 Swift는 자신만의 제네릭 타입을 정의할 수 있음.
- 사용자 클래스, 구조체, 열거형은 어떤 타입으로도 작업할 수 있음. 유사하게는 배열과 딕셔너리가 있음.
struct Color<T> {
var red: T
var green: T
var blue: T
}
var c = Color(red: 128, green: 80, blue: 200)
let d: Color<Double> = Color(red: 128.0, green: 80.0, blue: 200.0)
let arr: Array<Int>
let dict: Dictionary<String, Double>
extension Color { // extensions 에서 type parameter를 변경하는건 불가능. <T> -> compile error
func getComponents() -> [T] {
return [red, green, blue]
}
}
let intColor = Color(red: 1, green: 2, blue: 3)
intColor.getComponents()
let dblColor = Color(red: 1.0, green: 2.0, blue: 3.0)
dblColor.getComponents()
struct Stack<T> {
var items = [T]()
mutating func push(item: T) {
items.append(item)
}
mutating func pop() -> T {
return items.removeLast()
}
}
- 제네릭 프로토콜을 선언할때는 Associated Type이 필요함.
protocol QueueCompatible {
associatedtype Element: Equatable // 프로토콜에서 사용하는 placeholder 형식이며, 요구사항을 채용하는 것이 아님.
func enqueue(value: Element)
func dequeue() -> Element?
}
class IntegerQueue: QueueCompatible {
typealias Element = Int
func enqueue(value: Int) {
}
func dequeue() -> Int? {
return 0
}
}
class DoubleQueue: QueueCompatible { // 실제 사용하는 타입으로 연관 타입을 추론할 수 있기 때문에 typealias를 생략할 수 있음.
func enqueue(value: Double) {
}
func dequeue() -> Double? {
return 0
}
}
- 프로그램 내에서 에러가 발생한 상황에 대해 대응하고 이를 복구하는 과정
- swift에서는 런타임에 에러가 발생한 경우 이를 처리하기 위한 발생(Throwing), 감지(Catching), 전파(propagating), 조작(manipulating) 을 지원하는 일급 클래스를 제공.
enum DataParsingError: Error {
case invalidType
case invalidField
case missingRequiredField(String)
}
// Throw
func parsing(data: [String: Any]) throws {
guard let _ = data["name"] else {
throw DataParsingError.missingRequiredField("name")
}
guard let _ = data["age"] as? Int else {
throw DataParsingError.invalidType
}
}
// try Statements
try? parsing(data: [:])
func handleError() throws {
do {
try parsing(data: ["name":""])
} catch DataParsingError.invalidType {
print("invalid Type Error")
} catch {
print("handle error")
}
}
try? handleError()
func handleErrors() throws {
do {
try parsing(data: ["name" : ""])
} catch { // 패턴이 없는 catch 블록
if let error = error as? DataParsingError {
switch error {
case .invalidType:
print("invalid type")
default:
print("handle error")
}
}
}
}
try? handleErrors()
- Swift 5.3부터 도입. 캐치블럭이 두개이상의 에러를 동시에 매칭할 수 있도록 개선되었음.
func multiHandleError() throws {
do {
try parsing(data: ["name":""])
} catch DataParsingError.invalidType, DataParsingError.invalidField { // 이런식으로 동시에 매칭이 가능
print("invalid Type Error")
} catch {
print("handle error")
}
}
- Scope 종료 시점으로 코드의 실행을 연기
- 주로 코드에 사용했던 자원을 정리할 때 활용
func processFile(path: String) {
print("1")
let file = FileHandle(forReadingAtPath: path)
defer { // 함수가 종료될때 까지 연기됨. 항상 함수가 종료되는 시점에 실행
print("2")
file?.closeFile()
}
if path.hasSuffix(".jpg") {
print("3")
return
}
defer {
print("5")
}
print("4")
}
processFile(path: "file.jpg")
func testDefer() {
defer {
print(1)
}
defer {
print(2)
}
defer { // 가장 마지막에 예약된 블록이 먼저 실행.
print(3)
}
}
testDefer() // 3 2 1
- Selector는 UIKit에서 메소드를 지칭하거나, 속성의 게터나 세터를 지칭할 때 활용. ( 지칭 => 메소드 호출이 아니라 메소드를 가리키는 특정 인스턴스를 얻는다는 뜻. )
- 코드를 통해 버튼과 메소드를 연결하거나 제스쳐와 메소드를 연결할때 사용
- UIKit은 objective-c로 개발되었고, objective-c는 function type을 제공하지 않음.
class Figure {
@objc let color: UIColor = .blue
@objc func draw() { // @objc 코드는 struct에서 사용 불가
print("draw something")
}
}
let selector = #selector(Figure.draw)
let colorSelector = #selector(getter: Figure.color)
class ViewController: UIViewController {
@IBOutlet weak var numberLabel: UILabel!
@objc func reset() {
numberLabel.text = "0"
}
@objc func update(_ sender: Any) {
let rnd = Int.random(in: 1...1000)
numberLabel.text = "\(rnd)"
}
lazy var updateBtn: UIButton = {
let btn = UIButton(type: .system)
btn.setTitle("Update", for: .normal)
btn.frame = CGRect(x: 0.0, y: self.view.frame.height - 100, width: view.frame.width, height: 60.0)
return btn
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(updateBtn)
updateBtn.addTarget(self, action: #selector(ViewController.update), for: .touchUpInside)
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Reset", style: .plain, target: self, action: #selector(reset))
}
}
- Key Value Coding과 Key Value Observing의 근간을 이루는 개념
- a.b.c 처럼 하나 이상의 키가 점으로 이루어진 형태. a.b.c의 경우 c라는 속성에 접근하는 Keypath
- String으로 사용할 수 있으나, 오타와 같은 안정성 리스크를 줄이기위해 Keypath String Expression, Keypath Expression을 사용
- Keypath String Expression의 경우 타입캐스팅이 필요한데, 타입캐스팅 과정에서 에러가 날 수 있기 때문에 Keypath Expression보다 불안정함.
// Keypath String Expression
class Person: NSObject { // Keypath String Expression으로 속성에 접근하려면 NSObject, @objc가 필요 -> struct에선 불가
@objc let name: String = "Jane Doe"
@objc var age: Int = 0
}
let p = Person()
p.value(forKey: "name") // 단순 String으로 접근
var keypath = #keyPath(Person.name) // Keypath String Expression
p.value(forKey: keypath)
p.value(forKeyPath: keypath)
// Keypath Expression
struct Person2 {
let name: String = "Jane Doe"
var age: Int = 0
}
var p2 = Person2()
let keyPathToName = \Person2.name // KeyPath - 읽기전용
let keyPathToAge = \Person2.age // WritableKeyPath - 변경가능
let naveValue = p2[keyPath: keyPathToName]
let ageValue = p2[keyPath: keyPathToAge]
p2[keyPath: keyPathToAge] = 1
var keyPathToLength = \Person2.name.count
p2[keyPath: keyPathToLength]
keyPathToLength = keyPathToName.appending(path: \.count) // Keypath 확장 가능
p2[keyPath: keyPathToLength]
// Keypath Types
/*
class AnyKeyPath
class PartialKeyPath<Root>: AnyKeyPath
class KeyPath<Root, Value>: PartialKeyPath<Root>
class WritableKeyPath<Root, Value>: KeyPath<Root, Value>
class ReferenceWritableKeyPath<Root, Value>: WritableKeyPath<Root, Value>
*/
- Value Type: Structure, Enumeration, Tuple
- Reference Type: Class, Closure
- Value Type은 stack에 저장되어있는 값을 비교.
- Reference Type은 heap에 저장되어있는 값을 비교. -> 형식에 관계없이 실제 값을 비교
struct SizeValue {
var width = 0.0
var height = 0.0
}
var value = SizeValue() // stack에 메모리 공간 생성 및 저장
var value2 = value // 값 복사, 복사본이 새로운 메모리 생성 및 저장 value와 개별 인스턴스
value2.width = 1.0
value2.height = 2.0
value // 0 0
value2 // 1 2
class SizeObject {
var width = 0.0
var height = 0.0
}
var object = SizeObject() // stack에는 heap 메모리 주소가 저장되고, heap에는 인스턴스가 저장
// 값 형식과 달리 인스턴스에 바로 접근할 수 없고 항상 stack에 거쳐서 접근.
var object2 = object // stack에 새로운 메모리 공간이 생성되고, 이전 주소가 그대로 복사됨.
object2.width = 1.0
object2.height = 2.0
object // 1 2
object2 // 1 2
let v = SizeValue()
let o = SizeObject()
o.width = 1.0
o.height = 2.0
- ARC(Automatic Reference Countinh
- stack에 저장된 데이터는 자동으로 제거되기 때문에 특별한 관리 불필요.
- heap에 저장되는 데이터는 필요하지 않는 시점에 직접 관리해야함.
- 메모리 관리 모델은 heap에 저장되는 데이터를 관리. -> class 인스턴스에 메모리를 관리.
- keyword - Ownership Policy, Reference Count ( 참조 카운트가 1 이상이면 유지, 0이되면 제거 ), Strong Reference, Weak Reference, Unowned Reference
- 기존 object-c 에서는 MRC(Manual Reference Counting) 과 ARC를 같이 지원.
- MRC는 관련 코드를 직접 작성해야 하기 때문에 안정성을 보장하지 못하고, 디버깅이 어려움. 그래서 Compiler 단에서 자동으로 작성해주는 ARC 도입.
class Person {
var name = "John Doe"
deinit {
print("person deinit")
}
}
var person1: Person?
var person2: Person?
var person3: Person?
person1 = Person()
person2 = person1
person3 = person1 // 현재 Person Instance의 참조 카운팅은 3
person1 = nil
person2 = nil // nil을 저장하는것은 소유권을 포기하는 것과 같음. 참조 카운팅은 1
person3 = nil // 참조 카운팅이 0이 되면서 제거되고, 소멸자가 호출.
- Strong Reference Cycle이란 변수와 인스턴스간의 레퍼런스는 사라졌지만, 각 인스턴스간의 참조가 여전히 남아있어서 참조 카운트가 0이 될 수 없어 해당 인스턴스에 접근할 방법이 없어져 메모리 누수가 발생하는 상황.
- ARC는 메모리 관리를 대신 처리해주지만, 참조 싸이클까지 자동으로 처리하지 못함.
- 따라서 Weak Reference 와 Unowned Reference를 통해 해결. -> 두 방식 모두 인스턴스 사이에 강한 참조를 제거하는 방식으로 문제를 해결.
- Weak Reference 와 Unowned Reference는 강한 참조와 달리 참조 카운트를 증가시키거나 감소시키지 않음. 인스턴스에 접근 할 수는 있지만 인스턴스가 사라지지 않도록 유지하는것은 불가.
- Weak Reference는 인스턴스를 참조하지만, 소유하지는 않음. 이런 특징으로 소유자에 비해서 짧은 생명주기를 가진 인스턴스를 참조할 때 주로 사용
- Unowned Reference는 약한 참조와 동일한 방식. Swift 5 부터 옵셔널 방식으로 사용 가능. 하지만 참조 대상이 사라진경우 직접 초기화 하지 않으면 크래쉬가 남. 소유자와 생명주기가 같거나 더 긴 인스턴스를 참조할 때 주로 사용
// Strong Reference Cycle
class Person {
var name = "John Doe"
var car: Car?
deinit {
print("person deinit")
}
}
class Car {
var model: String
var lessee: Person?
init(model: String) {
self.model = model
}
deinit {
print("car deinit")
}
}
var person: Person? = Person()
var rentedCar: Car? = Car(model: "Porsche") // 참조 카운트 1
person?.car = rentedCar
rentedCar?.lessee = person // 참조 카운트 2
person = nil
rentedCar = nil // 참조 카운트 1 여전히 참조가 남아있음. 메모리 누수 발생.
// Weak Reference
class Person {
var name = "John Doe"
var car: Car?
deinit {
print("person deinit")
}
}
class Car {
var model: String
weak var lessee: Person?
init(model: String) {
self.model = model
}
deinit {
print("car deinit")
}
}
var person: Person? = Person()
var rentedCar: Car? = Car(model: "Porsche")
person?.car = rentedCar
rentedCar?.lessee = person // person 인스턴스를 소유하지않고 참조 카운트가 증가하지 않음.
person = nil // 참조 카운트 0 Strong Reference Cycle 제거 -> Person 소멸자 호출
rentedCar = nil // 참조 카운트 0 Strong Reference Cycle 제거 -> Car 소멸자 호출
// Unowned Reference
class Person {
var name = "John Doe"
var car: Car?
deinit {
print("person deinit")
}
}
class Car {
var model: String
unowned var lessee: Person?
init(model: String, lessee: Person) {
self.model = model
self.lessee = lessee
}
deinit {
print("car deinit")
}
}
var person: Person? = Person()
var rentedCar: Car? = Car(model: "Porsche", lessee: person!)
person?.car = rentedCar
person = nil
rentedCar = nil
- Closure가 인스턴스를 캡쳐하고, 인스턴스가 클로저를 강한 참조로 저장하고 있다면 인스턴스가 해제되지 않음.
- Closure 내의 Strong Reference Cycle 을 Closure Capture List로 해결 할 수 있음.
- Closure Capture List의 경우 축약형태에서 in을 생략할 수 없음
- Reference Type을 캡쳐할 때는 반드시 weak 키워드나 unowned 키워드를 추가해줘야함.
class Car {
var totalDrivingDistance = 0.0
var totalUsedGas = 0.0
lazy var gasMileage: () -> Double = { [weak self] in
guard let strongSelf = self else {
return 0.0
}
return strongSelf.totalDrivingDistance / strongSelf.totalUsedGas
}
func drive() {
self.totalDrivingDistance = 1200.0
self.totalUsedGas = 73.0
}
deinit {
print("car deinit")
}
}
var myCar: Car? = Car()
myCar?.drive()
myCar?.gasMileage()
myCar = nil
// Value Type
var a = 0
var b = 0
let c = { [a] in
print(a, b)
}
a = 1 // 0 값 형식을 Closure Capture List에 추가하면 참조대신 복사본을 캡쳐함.
b = 2
c()
- Metatype은 값의 타입을 표현하는 타입.
- 예시로 아이폰으로 사진을 찍으면 EXIF이라는 메타데이터가 저장됨. (날짜, 시간, 조리개값 등등의 데이터를 설명하는 메타데이터)
func checkType(of value: Any) {
let typeOfValue = type(of: value) // type(of:) 메타타입 인스턴스
print("\(value) => \(typeOfValue)")
}
let name = "Jane Doe"
checkType(of: name)
let age = 0
checkType(of: age)
// tableView 예시
class MyCell: UITableViewCell {
}
class CellRegistrationViewController: UIViewController {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(MyCell.self, forCellReuseIdentifier: "cell")
// Metatype을 파라미터로 전달할때는 self를 붙여야함. 여기서 self는 기존의 self와 다른 메타타입 인스턴스를 리턴하는 속성.
}
}
// json decoder 예시
struct Book: Codable {
let id: Int
let title: String
let description: String
}
class JSONDecodingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
do {
let decoder = JSONDecoder()
try decoder.decode(Book.self, from: jsonData)
} catch {
}
}
}
// VC 예시
class GenericFactoryViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let vc = instantiateViewController(ofType: JSONDecodingViewController.self)
}
}
extension UIViewController {
func instantiateViewController<VC: UIViewController>(ofType type: VC.Type) -> VC? {
let vcClassName = String(describing: type)
return storyboard?.instantiateViewController(withIdentifier: vcClassName) as? VC
}
func duplicateCurrentViewController() -> UIViewController? {
let vcType = type(of: self)
return vcType.init()
}
}