나만의 공부 노트
3강 정리 본문
학습 내용
Range
우리는 for loop를 통해서 반복적인 작업을 수행할 수 있었습니다.
for문을 사용하기 위해선 시퀀스가 필요합니다. (보통 배열, 딕셔너리, 문자열 등등이 시퀀스입니다.)
시퀀스는 Range라고도 표현이 되는데 여기서 Closed가 접두사로 붙으면 보통 마지막 숫자를 제외한다는 뜻입니다.(stride(from:,through:,by:)가 ClosedRange에 해당합니다.)
Range는 lowerbound를 닫힌 범위로 upperbound는 열린 범위로 갖고, ClosedRange는 lowerbound와 upperbound 둘 다 닫힌 범위로 포함하는 시퀀스입니다.(수학적으로 닫힘, 열림의 의미와 동일합니다.)
다만, 단순히 끝값들만 가지고 있을 뿐이지 중간의 값들을 가지고 있는 형태는 아닙니다.(예를 들어 1...10은 2,3,...,9를 가지고 있는 배열이 아닙니다.)
다시 1...10과 같은 시퀀스는 ClosedCountableRange입니다.
정수형은 다음 값이 항상 1씩 증가한다는 것을 알기때문에 countable한 것이고, comparable 타입(프로토콜)을 따르고 있습니다.
그러나 1.0...10.0과 같은 double형의 시퀀스는 다릅니다.
항상 얼마만큼 증가하는지 모르기 때문에(not define) countable하지 않고, comparable 타입 또한 따르지 않습니다.
익스텐션을 통해 comparable 프로토콜을 구현해줌으로써 시퀀스를 형성할 수도 있습니다.(또는, stride를 사용하면 됩니다.)
Tuple
Tuple은 메소드나 변수가 없는 소형 구조체 입니다.
튜플은 각각의 값이 이름을 정할 수 도 있습니다.
계산 프로퍼티
get과 set을 통해 구현합니다
다음과 같이 사용할 수 있으며, newValue같은 경우 이름을 그대로 사용한다면 생략할 수도 있습니다.
var example: Int {
get { return 0 }
set(newValue) { another = newValue }
}
여기서 값을 가지는 get만 있을 수는 있지만 set만 있을 수는 없습니다. 즉, get 또는 get, set은 가능합니다.
set만 사용하고 싶다면 함수를 사용해야합니다.(변수를 사용하는 목적에 어울리지 않습니다.)
또한 get만 사용한다면 var example: Int { return 0 } 와 같이 get을 생략해도 무방합니다.
* 언뜻 프로퍼티처럼 보이지만 실제론 계산을 하기 때문에 간단한 연산일 때만 사용하고, 복잡한 연산일 경우 함수로 만들어줌으로써 다른 프로그래머에게 자주 사용되지 않도록 주의를 줄 필요도 있습니다.
접근 제어(Access Control)
비교적 소규모의 프로젝트를 진행하는 경우에는 특별히 접근제어 대한 요구가 없을 것입니다. 하지만 개발하고 있는 프로그램의 크기가 커짐에 따라 API를 사용해야 하는 경우가 발생하게 되고 이에 대해 미리 약속 되어 있는 키워드를 사용함으로서 API에 접근할 수 있게 됩니다.
Internal: 디폴트로 정해져 있으며, 입력하지 않는다면 기본적으로 internal로 인식됩니다. 앱내 또는 프레임워크 내에서 접근 가능하도록 합니다.(즉 접근 제어 키워드를 아무것도 사용하지 않는다면 서로 다른 프레임워크가 서로 간섭할 수 없습니다.)
Private: 객체 내에서만 접근 가능합니다.
Private(set): 호출은 할 수 있지만 값을 설정하지 못하도록 할때 사용합니다.
Fileprivate: Source file 내에서 접근 가능하도록 할때 사용합니다.
- 아래의 두 개는 프레임워크를 위한 키워드입니다.
Public: 프레임워크 밖에서도 객체에 의해 사용될 수 있습니다.
Open: 프레임워크 밖에서도 객체에 접근 가능하며, 상속을 받아서 override도 가능 하도록 합니다.(UIKit의 대부분의 함수는 Open으로 선언되어 우리가 사용할 수 있는 것입니다.)
Assert
assert(_ condition: Bool, _ message: String)의 형태의 전역 함수이며, 조건이 거짓일 때 디버거가 message를 출력합니다.
익스텐션
기존에 존재하는 데이터 구조를 확장시켜줄 수 있는 매우 강력한 문법이며, class/ struct / enum등에 method와 properties를 추가할 수 있습니다.
그러나 2가지의 제한 사항과 주의해야 할 점 1가지가 있습니다.
첫번째로 저장 프로퍼티는 사용할 수 없습니다. (오로지 프로퍼티는 계산 프로퍼티만 추가가 가능합니다 == 즉, 메서드만 추가하는 것과 비슷한 맥락입니다.)
두번째로 메소드와 프로퍼티를 재정의할 수 없습니다. (즉 추가하는 형태로만 이루어져야 하기에 override 할 수 없습니다.)
주의해야할 점은 확장을 할 때 연동성이 적은 코드들을 작성하는 것은 지양해야 합니다.
추가로 익스텐션은 코드를 정리하는 데에도 매우 유용하게 사용될 수 있습니다.
예로 UITextFieldDelegate 프로토콜을 채택했다면, 해당 viewController에 delegate 메서드를 작성하는 것보다 클래스 아래에 extension으로 코드를 작성하는 것이 더욱 작성과 읽기에 용이할 수 있습니다.
Enum
enumeration의 약자로 일종의 타입이며, 또한 복사되는 형태의 값 타입입니다.
열거형의 개별 상태는 연동된 데이터(associated data)를 가질 수 있습니다.(Swift만의 강력한 문법입니다.)
연동된 데이터는 새롭게 이름을 부여해서 사용할 수도 있습니다.(switch문을 이용합니다.)
다음과 같이 사용할 수 있습니다.
enum FastFoodMenuItem {
case hamburger(numberOfPatties: Int)
case fries(size: FryOrderSize)
case drink(String, ounces: Int)
case cookie
}
연동된 데이터가 있다면, enum을 할당할 때 연동된 데이터도 함께 할당을 해주어야 합니다.
+ 만약 이 때 연동된 데이터를 할당하지 않는다면, (연동된 데이터 포맷) -> Enum 과 같은 함수 형태로 변수가 선언됩니다.
+ 추후에 함수 형태로 연동된 데이터를 넣어서 사용해야합니다.
enum은 if문이 아닌 switch문을 사용해서 확인합니다. (enum에는 == 과 같은 연산이 없기 때문에 if문을 사용을 못합니다.)
switch문을 통해 기존에 있던 연동 데이터의 이름을 바꿀 수도 있고, 이름이 없는 경우에는 새로운 이름을 할당할 수도 있습니다.
연동 데이터를 사용하지 않는다면 case의 명명만 사용해도 됩니다.
그러나 연동된 데이터를 사용할 필요가 있을 때 반드시 let으로 선언해야 합니다.(case .drink(let brand, let ounces): ... )
그렇지 않고서는 연동된 데이터를 불러올 방법이 없습니다.
추가로 enum은 계산 프로퍼티와 메소드는 가능하지만, 저장 프로퍼티는 불가능합니다.(필요하다면 연동된 데이터 구조를 활용합니다.)
메서드를 구현하면 메서드 내부에서 자신을 가리키기 위해 self와 switch문을 이용할 수 있습니다.
자신의 case를 바꾸거나 혹은 연동된 데이터를 바꾸는 함수라면 mutating 키워드를 사용하여야 합니다.(마찬가지로 self에 할당하면 됩니다.)
옵셔널
enum Optional<T> {
case none
case some(<T>)
}
옵셔널은 enum입니다.
어떠한 값이든 올수있는 제네릭이며, 값을 가지거나 가지지 않는 두가지 경우만 있습니다.
옵셔널을 enum의 switch문처럼 사용하지 않는 이유는 너무 자주 사용되기 때문입니다.
자주 사용되기 때문에 특수 키워드가 많습니다.(nil, !, ?, if let, guard, ??)
let example: Int? = .none
let example: Int? = nil
let example: Optional<Int> = some(0)
위의 선언은 모두 같은 맥락입니다.
또한, 옵셔널 체이닝이라는 매우 유용한 문법이 있습니다.
메모리 관리
클래스는 참조 타입으로 힙에 저장이 됩니다. (값 타입은 모두 stack 영역에 저장이 됩니다.)
+ 참조 타입은 여러번 참조 해야하므로 힙에 두어 관리하고, 값 타입은 한 개의 변수당 하나씩 할당하므로 stack 영역에 넣는 것이 옳습니다.
(class, enum, struct, protocol 등의 자료 구조 선언은 힙과 스택 영역이 아닌 code영역에 넣습니다.)
Static ( 정적인 공간 ) |
Code |
함수가 저장되는 공간 |
Data |
프로그램이 종료 될 때 까지 쓰일 변수들이 저장되는 공간 (전역변수나 static을 선언한 지역 변수) |
|
Dynamic ( 동적인 공간 ) |
Stack |
함수 수행 중에만 잠깐 필요한 데이터가 저장 되는 공간 (함수 안의 변수나 파라미터 값, 리턴값) |
Heap |
개발자가 필요에 따라 수시로 만들고 지우는 데이터들의 공간 |
힙의 영역에서 데이터를 관리해야하는데 Swift는 ARC(Automatic Reference Counting)기법을 사용합니다. (java에는 garbage collection 기법이 있습니다.)
즉, 강한 참조로 선언한 변수의 갯수를 세고, 해당 갯수가 0이 되면 그 즉시 힙의 영역에서 데이터를 삭제하는 방법입니다.
선언에는 strong, weak, unowned가 있습니다.
Reading 2
Typealias
typealias Name = String 과 같이 사용 가능합니다.
Unicode Representations of String
swift는 character마다 바이트 크기가 다르므로 인덱스로 접근할 때 주의해야 합니다.
string은 각각 UTF-8, UTF-16, Unicode scalar(= UTF-32)로 접근할 수 있습니다.
let dogString = "Dog‼🐶"라는 string이 있다면 어떤 표현형으로 접근하느냐에 따라서 갯수가 달라집니다.
1. for codeUnit in dogString.utf8 { ... }
2. for codeUnit in dogString.utf16 { ... }
3. for codeUnit in dogString.unicodeScalars { ... }
Sets
set은 집합과 같은 데이터 구조이며 Hashable 프로토콜을 준수합니다.
Closures
클로저는 기본적으로 참조 타입입니다.(힙 영역에 저장됩니다.)
클로저는 외부의 변수들도 사용할 수 있다는 장점이 있지만, 외부의 변수를 사용하게 되면 변수를 capture하게 됨으로써 메모리 사이클을 유발할 수도 있습니다.(메모리 사이클을 항상 염두에 두고 프로그래밍해야 합니다.)
추가로 @escaping 이라는 키워드가 붙은 탈출 클로저가 있습니다.
어떤 함수에 인자로 클로저를 넘겨주었지만, 해당 클로저가 함수가 종료된 뒤에 실행될 필요가 있는 경우 위의 키워드를 붙입니다.
함수가 종료되더라도 탈출 클로저는 메모리에 남아있습니다.