# 이름짓기
function, method, variable, constant : Lower Camel Case 사용
class, struct, enum, extension: Upper Camel Case 사용
# 콘솔로그 print, dump
print : 단순 문자열 출력
dump : 인스턴스의 자세한 설명(description 프로퍼티)까지 출력
(streaming 을 이용해 파일로 로그 기록할 수 있다)
# 문자열 보간법 \()
"안녕하세요 제 이름은 \(name)입니다."
# 변수(var)와 상수(let)
var : 차후 값이 변경되는 변수 선언시 사용
let : 차후 값이 변경되지 않는 변수 선언시 사용
(TIP)
// ` 사용하면 사용불가 한 변수명 사용가능해짐 var `class`: String = "Sample" |
# 기본 데이터 타입
Bool, Int, UInt, Float, Double, String, Character
* Character 타입은 " 로 값 초기화
* String, Int, Double은 구조체로 선언되어 있다
# 컬렉션 타입
Array : 순서가 있는 리스트 컬렉션
Dictionary : 키와 값의 쌍으로 이루어진 컬렉션
Set : 순서가 없고, 멤버가 유일한 컬렉션
# Any, AnyObject
Any : Swift의 모든 타입을 지칭하는 키워드
AnyObject : 모든 클래스 타입을 지칭하는 키워드
# 함수 고급 (기본 함수형태는 생략)
* 매개변수 기본값
func funcName(param1: String, param2: String = "hi") { /* 함수 구현부 */ }
* 전달인자 레이블
func funcName(a param1: String, b param2: String) { /* 함수 구현부 */ }
* 가변 매개변수
func funcName(param1: String, param2: String...) { /* 함수 구현부 */ }
* 함수 변수
함수 매개변수로 사용될 수도 있음
var sampleFunc: (String, String) -> Void = funcName(a:b:)
# 조건문
if-else
if 조건 {
/* 구현 */
} else if 조건 {
/* 구현 */
} else {
/* 구현 */
}
switch
switch 변수 {
case 값/범위:
/* 구현 */
case 값/범위:
/* 구현 */
default:
/* 구현 */
}
# 반복문
for-in
for (key, value) in people { // people은 Dictionary
/* 구현 */
}
while
while 조건 {
/* 구현 */
}
repeat-while
repeat {
/* 구현 */
} while 조건
# 옵셔널
Non-Optional : 변수값에 nil 비허용
Optional : 변수값에 nil 허용
let someInt: Int = 0 // non-optional
let someInt: Int? = nil // optional
let someInt: Int! = nil // optional (암시적 추출)
# 옵셔널 값 추출
옵셔널 바인딩
if-let
var myName: String? = "hob"
var yourName: String? = nil
if let name = myName, let friend = yourName {
/* yourName 값이 nil이기 때문에 실행되지 않음 */
}
옵셔널 강제 추출
var name1: String! = nil
print(name1) /* 런타임 오류 발생 */
var name2: String? = nil
print(name2!) /* 런타임 오류 발생 */
# 구조체
struct Sample {
var mutableProperty: Int = 100 // 인스턴스 프로퍼티 (가변)
let immutableProperty: Int = 100 // 인스턴스 프로퍼티 (불변)
static var typeProperty: Int = 100 // 타입 프로퍼티
// 인스턴스 메서드
func instanceMethod() {
/* 구현 */
}
// 타입 메서드
static func typeMethod() {
/* 구현 */
}
}
// 타입 프로퍼티 및 메서드
Sample.typeProperty = 200
Sample.typeMethod()
// 인스턴스 프로퍼티 및 메서드
var mutable: Sample = Sample() // 가변 인스턴스
mutable.mutableProperty = 200 // 가능
mutable.immutableProperty = 200 // 불가능
let immutable: Sample = Sample() // 불변 인스턴스
immutable.mutableProperty = 200 // 불가능
immutable.immutableProperty = 200 // 불가능
# 클래스
class Sample {
var mutableProperty: Int = 100
let immutableProperty: Int = 100
static var typeProperty: Int = 100
func instanceMethod() {
/* 구현 */
}
// 타입 메서드 (재정의 가능)
static func typeMethod() {
/* 구현 */
}
// 타입 메서드 (재정의 불가능)
class func classMethod() {
/* 구현 */
}
}
// 인스턴스 프로퍼티 및 메서드
var mutable: Sample = Sample() // 가변 인스턴스
mutable.mutableProperty = 200 // 가능
mutable.immutableProperty = 200 // 불가능
let immutable: Sample = Sample() // 불변 인스턴스
immutable.mutableProperty = 200 // 가능
immutable.immutableProperty = 200 // 불가능
# 열거형
- enum은 타입이므로 대문자 카멜케이스를 사용하여 이름을 정의
- 각 case는 소문자 카멜케이스로 정의
- 각 case는 그 자체가 고유의 값
enum Weekday {
case mon
case tue
case wed, thu, fri, sat, sun // 연속해서 쓸 수도 있다
func printMsg() { // 메서드를 구현할 수도 있다!!
switch self {
case .mon, .tue, .wed, .thu, .fri:
print("주중")
case .sat, sum:
print("주말")
}
}
}
// C 언어의 enum처럼 원시값(정수값/문자열값)을 가질 수 있음 (rawValue 사용)
// case 별로 각각 다른 값을 가져야 함 (중복 불가)
enum Fruit: Int {
case apple = 0
case grape = 1
case peach // 값을 안적으면 자동으로 1씩 증가함 (문자열값 생략시 case 이름 그대로를 원시값으로 가짐)
}
// 변수 생성
var day1: Weekday = .mon
var day2 = Weekday.tue
// switch & enum
// 모든 case에 enum값이 명시되면 default 쓰지 않아도 됨
switch day {
case .mon, .tue, .wed, .thu:
print("평일")
case Weekday.fri:
print("불금")
case .sat, .sun:
print("주말")
}
let myValue: Int = Fruit.apple.rawValue // 원시값
let myFruit: Fruit? = Fruit(rawValue: 0) // 원시값으로 가져오는 enum값은 반드시 optional 이다
# 값 타입과 참조 타입
Class
- 전통적인 OOP 관점에서의 클래스
- 단일상속
- (인스턴스/타입) 메서드
- (인스턴스/타입) 프로퍼티
- 참조 타입
- Apple 프레임워크의 대부분의 큰 뼈대는 모두 클래스로 구성
Struct
- C 언어 등의 구조체보다 다양한 기능
- 상속 불가
- (인스턴스/타입) 메서드
- (인스턴스/타입) 프로퍼티
- 값 타입
- Swift의 대부분의 큰 뼈대는 모두 구조체로 구성
Enum
- 다른 언어의 열거형과는 많이 다른 존재
- 상속 불가
- (인스턴스/타입) 메서드
- (인스턴스/타입) 연산 프로퍼티
- 값 타입
- Enumeration
- 유사한 종류의 여러 값을 유의미한 이름으로 한 곳에 모아 정의 (ex_ 요일, 상태값, 월 등)
- 열거형 자체가 하나의 데이터 타입, 열거형의 case 하나하나 전부 하나의 유의미한 값으로 취급
- 선언 키워드 enum
Class | Struct | Enum | |
Type | Reference | Value | Value |
Subclassing | O | X | X |
Extension | O | O | O |
* 구조체를 사용하는 경우
- 연관된 몇몇의 값들을 모아서 하나의 데이터 타입으로 표현하고 싶을 때
- 다른 객체 또는 함수 등으로 전달될 때 (참조가 아닌 복사를 원할 때)
- 자신을 상속할 필요가 없거나, 자신이 다른 타입을 상속받을 필요가 없을 때
(참고)
Value
: 데이터를 전달할 때 값을 복사하여 전달
Reference
: 데이터를 전달할 때 값의 메모리 위치를 전달
* Swift는 구조체, 열거형 사용을 선호
public struct Int public struct Double public struct String public struct Dictionary<Key: Hashable, Value> public struct Array<Element> public struct Set<Element: Hashable> |
# 클로저
// 기본 형태 { (매게변수 목록) -> 반환타입 in 실행 코드 } // 매게변수 / 반환타입이 없는 경우 { () -> Void in 실행 코드 } |
// 클로저의 사용 var sum: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in return a+b } print(sum(1, 2)) // 3 |
* 후행 클로저
func calculate(a: Int, b: Int, method: (Int, Int) -> Int) -> Int { return a+b } // 후행 클로저 result = caculate(a: 10, b: 10) { (left: Int, right: Int) -> Int in return left + right } // 반환타입 생략 result = caculate(a: 10, b: 10) { (left: Int, right: Int) in return left + right } // 단축 인자이름 result = caculate(a: 10, b: 10) { return $0 + $1 } // 암시적 반환 표현 result = caculate(a: 10, b: 10) { $0 + $1 } |
# 프로퍼티
1. stored property (저장 프로퍼티)
2. computed property (연산 프로퍼티)
3. instance property (인스턴스 프로퍼티)
4. type property (타입 프로퍼티)
// 인스턴스 저장 프로퍼티 var koreanAge: Int = 20 // 인스턴스 연산 프로퍼티 var westernAge: Int { get { return koreanAge - 1 } set { koreanAge = newValue + 1 } } |
// 타입 저장 프로퍼티 static var typeDesc: String = "학생" |
// 읽기전용 인스턴스 연산 프로퍼티 val selfIntroduction: String { get { return "저는 \(name)입니다." } } val selfIntroduction: String { return "저는 \(name)입니다." // get 생략 가능 } |
# 프로퍼티 감시자
struct Money { var currencyRate: Double = 1100 { willSet { print("willSet : \(currencyRate) -> \(newValue)") } didSet { print("didSet: \(oldValue) -> \(currencyRate)") } } } var money: Money = Money() // (print) willSet : 1100.0 -> 1150.0 money.currencyRate = 1150 // (print) didSet : 1100.0 -> 1150.0 |
# 상속
- 스위프트의 상속은 클래스, 프로토콜에서 가능
- 열거형, 구조체는 상속 불가
- 스위프트는 다중상속을 지원하지 않음
class Person { var name: String = "" func selfIntroduce() { print("저는 \(name) 입니다") } // final 키워드 - 재정의 방지 final func sayHello() { print("hello") } // 타입 메서드 // 재정의 불가 타입 메서드 - static static func typeMethod() { print("type method - static") } // 재정의 불가 타입 메서드 - class class func classMethod() { print("type method - class") } // 재정의 가능한 class 메서드 라도 final 키워드를 사용하면 재정의 불가 // 메서드 앞의 `static`과 `final class`는 똑같은 역할을 합니다 final class func finalClassMethod() { print("type method - final class") } } |
class Student: Person { var major: String = "" override func selfIntroduce() { print("저는 \(name)이고, 전공은 \(major)입니다.") // super.selfIntroduce() // 부모 클래스의 함수 호출 가능 } override class func classMethod() { print("overriden type method - class") } // override final func sayHello() {} // final 메소드 재정의 불가 // override static func typeMethod() {} // static 메소드 재정의 불가 // override final class func finalClassMethod() {} // final class 메소드 재정의 불가 } |
# 인스턴스의 생성과 소멸 (init, deinit)
class Person { var name: String var age: Int var nickName: String? // 이니셜라이저 - designated init init(name: String, age: Int) { self.name = name self.age = age } // 이니셜라이저 - convenience init(보조 이니셜라이저) // self.init을 호출하는 init 앞에는 convenience를 붙여준다 convenience init(name: String, age: Int, nickName: String) { self.init(name: name, age: age) self.nickName = nickName } } |
class Person { var name: String var age: Int var nickName: String? // init? // 원하는 범주의 값이 아니라면 nil을 반환하게 된다 init?(name: String, age: Int) { if (0...120).contains(age) == false { return nil } if name.characters.count == 0 { return nil } self.name = name self.age = age } deinit { // Person 객체가 nil 할당을 받으면(메모리에서 해제되면) deinit이 실행됨 } } |
# 옵셔널 체이닝
: 연속적인 nil 체크 가능
if let guardJob = owner?.home?.guard?.job { print("우리집 경비원의 직업은 \(guardJob)입니다.") } else { print("우리집 경비원은 직업이 없어요.") } |
# nil 병합 연산자
: 값이 nil인 경우, default 값 지정 가능
var guardJob: String = owner?.home?.guard?.job ?? "무직" |
# 타입 캐스팅 (is, as)
1) 인스턴스의 타입을 확인하는 용도
2) 인스턴스를 부모/자식 클래스의 타입으로 사용할 수 있는지 확인하는 용도
var isPerson: Bool = person is Person // true or false // switch 문으로 type 확인할 수도 있음 switch person { case is Student: // case is Person: // default: // } |
/* 업 캐스팅 자식 클래스 -> 부모 클래스 as 사용 non-optional as Any는 생략가능 */ var mike: Person = Student() as Person |
var mike: Person = Student() as Person var jenny: Person = Person() /* 다운 캐스팅 부모클래스 -> 자식 클래스 as? 또는 as! 사용 optional */ // 조건부 다운 캐스팅 as? var studentA = mike as? Student var studentA = jenny as? Student // nil // 강제 다운 캐스팅 as! var studentA = mike as! Student var studentA = jenny as! Student // 런타임 오류 |
// 이런식으로 함수내에서 클래스 타입확인해서 사용 func doSomething(someone: Person) { if let student = someone as? Student { student.studentFunc() } else if let person = someone as? Person { person.personFunc() } } |
# Assertion
- assert(_:_:file:line:) 함수를 사용
- assert 함수는 debug 모드에서만 동작, release 모드에서는 동작하지 않음
- 주로 디버깅 중 조건의 검증을 위해 사용
var someInt: Int = 0 assert(someInt == 0, "someInt != 0") |
# Early Exit
- guard를 사용하여 잘못된 값의 전달 시 특정 실행구문을 빠르게 종료
- debug, release 모두에서 동작
- guard의 else 블럭 내부에는 종류 지시어(return/break..) 가 꼭 있어야 함
- 타입 캐스팅, 옵셔널과도 자주 사용, 단순 조건 판단 후 빠르게 종료할 때에도 용이
func someFunc(age: Int?) { guard let unwrappedAge = age, unwrappedAge < 130, unwrappedAge >= 0 else { return } print("나이 : \(unwrappedAge)" // guard문에서 선언한 변수명을 아래코드에서도 사용 가능! } |
// while 문에서 쓰기도 함 var count =1 while true { guard count < 3 else { break } print(count) count += 1 } |
// Dictionoary를 사용할 때에도 용이함 func someFunc(info: [String: Any]) { guard let name = info["name"] as? String else { return } guard let age = info["age"] as? Int, age >= 0 else { return } print("\(name) : \(age)") } |
# Protocol(프로토콜)
- 특정 역할을 수행하기 위한 메서드, 프로퍼티, 이니셜라이저 등의 요구사항을 정의
- 구조체, 클래스, 열거형은 프로토콜을 채택(Adopted)해서 프로토콜의 요구사항을 실제로 구현
- 어떤 프로토콜의 요구사항을 모두 따르는 타입은 '프로토콜을 준수한다(Conform)'고 표현
protocol Talkable { // 프로퍼티 요구 // var 키워드만 사용할 수 있음 var topic: String { get set } // { get set } : 읽기 쓰기 가능 var language: String { get } // { get } : 읽기만 가능 // 메서드 요구 func talk() // 이니셜라이저 요구 init(topic: String, language: String) } // --------------------------------------- struct Person: Talkable { // <- Person 구조체는 Talkable 프로토콜을 채택 // 프로퍼티 구현 (1) var topic: String // { get set }으로 정의된 property이기 떄문에 무조건 var로 선언 let language: String // { get }으로 정의된 property이기 때문에 var/let 모두 가능 // 프로퍼티 구현 (2) - 연산 프로퍼티로 대체 // 읽기 전용 프로퍼티 var language: String { return "한국어" } // 일기쓰기 전용 프로퍼티 var subject: String = "" var topic: String { set { self.subject = newValue } get { return self.subject } } // 메서드 func talk() { print("\(topic), \(language)") // 이니셜라이저 init(topic: String, language: String) { self.topic = topic self.language = language } } } |
* 프로토콜의 상속
- 프로토콜은 클래스와 다르게 다중상속이 가능
- 클래스에서 상속과 프로토콜 채택을 동시에 하려면, 상속받으려는 클래스를 먼저 명시하고 그뒤에 채택할 프로토콜 목록을 작성해야함
* 프로토콜 준수 확인 (is, as)
// is someAny is Talkable // true or false // as if let someTalkable: Talkable = someAny as? Talkable { someTalkable.talk() } |
# Extension
- 익스텐션은 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가할 수 있는 기능
* 익스텐션으로 추가할 수 있는 기능
1. 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티
2. 타입 메서드 / 인스턴스 메서드
3. 이니셜라이저
4. 서브스크립트
5. 중첩 타입
6. 특정 프로토콜을 준수할 수 있도록 기능 추가
(기존에 존재하는 기능을 재정의할 수는 없음)
extension Int { var isEven: Bool { return self % 2 == 0 } var isOdd: Bool { return self % 2 == 1 } func multiplay(by n: Int) -> Int { return self * n } } print(1.isEven) extension String { init(intTypNumber: Int) { self = "\(intTypeNumber)" } } print(String(intTypNumber: 100)) |
# 오류처리
- Error 프로토콜과 (주로) 열거형을 통해 오류를 표현
enum VendingMachineError: Error { case invalidInput case insufficientFunds(moneyNeeded: Int) case outOfStock } class VendingMachine { let itemPrice: Int = 100 var itemCount: Int = 5 var deposited: Int = 0 // 돈 받기 func receiveMoney(_ money: Int) throws { // 'throws' 는 오류를 발생할 수 있다는 의미 // 입력한 돈이 0이하면 오류 던짐 guard money > 0 else { throw VendingMachineError.invalidInput } // 오류가 없으면 정상처리 self.deposited += money print("\(money)원 받음") } // 물건 팔기 func vend(numberOfItems numberOfItemsToVend: Int) throws -> String { guard numberOfItemsToVend > 0 else { throw VendingMachineError.invalidInput } // } } |
# 고차함수
- 전달인자로 함수를 전달받거나 함수 실행의 결과를 함수로 반환하는 함수
(예시) map, filter, reduce
let numbers: [Int] = [1, 2, 3, 4, 5] // map // 컨테이너 내부의 기존 데이터를 변형하여 새로운 컨테이너 생성 let strings: [String] = numbers.map { "\($0)" } // ["1", "2", "3", "4", "5"] // filter // 컨테이너 내부의 값을 걸러서 새로운 컨테이너로 추출 let oddNumbers: [Int] = numbers.filter { $0 % 2 == 1 } // [1, 3, 5] // reduce // 컨텐츠 내부의 콘턴츠를 하나로 통합 let sum: Int = numbers.reduce { $0 + $1 } // 15 |
# Subscript
stuct Classroom { var students = [ ["Eric", "Peter", "Randy"], ["Messi", "Nicki", "Alicia", "Bill", "Jeff"] ] subscript(row: Int, col: Int) -> String { get { return students[row][col] } set { students[row][col] = newValue } } } let class = Classroom class[1, 2] // Alicia class[0, 0] = "newStudent" class[0, 0] // newStudent |
# 추가로 더 공부할 것들
- 제네릭(Generics)
- 접근수준(Access Control)
- 중첩타입(Nested Types)
- 사용자정의 연산자(Custom Operators)
구름EDU - 야곰의 스위프트 프로그래밍 (무료)
YouTube - yagom
'iOS' 카테고리의 다른 글
[iOS] 공부 링크 (ing...) (0) | 2022.02.07 |
---|---|
[iOS] Rendering (0) | 2020.12.31 |
[iOS] Nine-Patch, 둥근모서리 이미지 소스 사용하기 (0) | 2020.10.20 |
[iOS] Warning 없는 Clean Code 만들기 (SwiftLint, Pods file) (0) | 2020.10.07 |
[iOS] RealmSwift 라이브러리 사용해보기 (0) | 2020.10.06 |