[iOS] 스탠포드 CS193P 3강

이번 강의는 Autolayout에 대한 간략한 설명과 Swift 문법을 다뤘습니다.
Autolayout

위 이미지에서 카드 배열에서 잘리는 부분이 생기는 이슈를 볼 수 있습니다. 이 이슈를 해결하기 위한 도구로 "Autolayout"을 소개합니다. Autolayout은 이번 강의에서는 teaser 수준으로 안내하고 다른 강의에서 다룬다고 합니다. Autolayout을 다루기 전에 위 이슈를 해결하기 위해 아래 두 가지 작업을 해야 합니다.
- 12개의 버튼을 한 그룹으로 묶기 👉 세로 방향의 Stack과 가로 방향의 Stack을 활용해서 12개의 버튼을 묶는다
- 그룹을 사면에 붙이기
- Top Space to Safe Area
- Leading Space to Safe Area
- Trailing Space to Safe Area
- Vertical Spacing -> Flip count ( Greater than 0 )
Leading을 Left 대신에 쓰는 이유 👉 히브리어나 특정 언어는 Leading 값이 Right 일 수도 있기 때문입니다.

이제는 UI가 가장자리에서 떨어져 있거나 가려져있는 이슈가 사라진 것을 볼 수 있습니다.
이 기능에 대한 자세한 내용은 앞으로 3주 내에 이 기능을 다룰 예정입니다 🤩

Swift 문법
Floating point CountableRange
🤔 How do you do for (i = 0.5; i <= 15.25; i += 0.3) ?
👉 stride 전역 함수를 사용해서 부동소수점을 CountableRange로 사용할 수 있습니다.
for i in stride(from: 0.5, through: 15.25, by 0.3) {
// do something
}
Tuple
It is nothing more than a grouping of values.
아무런 메서드 없이 값만 있는 다른 언어의 구조체와 비슷한 형태입니다. 이름 설정이 상당히 유연한 게 특징입니다.
보통은 두 번째 방법을 많이 활용한다고 합니다.
let x: (String, Int, Double) = ("hello", 5, 0.85)
let (word, number, value) = x
print(word) // hello 출력
print(number) // 5 출력
print(value) // 0.85 출력
// OR
let x: (w: String, i: Int, v: Double) = ("hello", 5, 0.85)
print(x.w) // hello 출력
print(x.i) // 5 출력
print(x.v) // 0.85 출력
let (wrd, num, val) = x
튜플의 장점
- 메서드가 필요 없는 단순한 값들을 묶을 때
- 함수에서 하나 이상의 값을 리턴할 때
수업 중 어떤 학생이 튜플이 인덱싱 기능을 제공하냐고 물어봤고, 아래처럼 답변해주셨습니다.
"튜플은 인덱싱을 제공하지 않고, 값을 묶어주는 특성 덕분에 제공할 필요도 없습니다."
Tuple VS Dictionary
- 튜플은 함수에서 여러 개의 값을 반환할 수 있습니다.
- 튜플은 이미 선언된 개수의 값만 넣을 수 있습니다.
- 튜플은 다른 데이터 타입의 값들을 포함할 수 있습니다. 즉, 튜플 안에 Int, String, Double 등을 넣을 수 있는 것이죠
Computed Properties
앞선 강의에서 다뤘던 변수들은 stored property입니다. 하지만 변수를 아래와 같이 구현할 수도 있습니다.
이 경우 foo는 어디에도 저장되어 있지 않고, 매번 foo를 요청할 때마다 get 안에 있는 코드를 실행하고, foo의 값을 할당할 때마다 set 안에 있는 코드가 실행됩니다. get과 set 중 set은 optional입니다.
var foo: Double {
get {
// return the calculated value of foo
}
set(newValue) {
// do something based on the fact that foo has changed to newValue
}
}
어떤 상황에서 Computed properties를 쓰는가?
- 속성으로 인식이 된다면 👉 computed property를 활용한다
- get/set 이 많은 일을 하게 될 때 👉 따로 함수를 만든다
Computed properties 예시
앞 강의에서 쓰인 변수 중 var indexOfOneAndOnlyFaceUpCard: Int? 를 computed property로 바꾸면 아래와 같습니다. set의 인자는 생략 가능하고, newValue 변수가 기본입니다.
private var indexOfOneAndOnlyFaceUpCard: Int? {
get {
var foundIndex: Int?
for index in cards.indices {
if cards[index].isFaceUp {
if foundIndex == nil {
foundIndex = index
} else {
return nil
}
}
}
return foundIndex
}
set {
for index in cards.indices {
cards[index].isFaceUp = (index == newValue)
}
}
}
Access Control
"12개 이상의 클래스를 써본 사람 있나요? 🙋♂️"라는 질문으로 시작하십니다. 개발팀별 건물과 소통하는 방법을 비유하면서 접근제어를 소개하셨습니다.

접근제어의 종류
- internal (기본값) - 앱 or 프레임워크 내에 접근하는 것에 제한이 없습니다
- private - 다른 객체로 불러올 수 없습니다
- private(set) - 변수만을 위한 private이며, 외부에서 할당하는 것은 비공개지만 접근하는 것은 가능합니다
- fileprivate - 파일 안에 있는 어떤 것이던 서로 접근할 수 있습니다
- public - 프레임워크 외부에서 접근 가능합니다
- open - 외부뿐 아니라 서브 클래스에서 접근하거나 override가 가능합니다
👉 "Private을 기본으로 두는 것이 좋은 전략입니다."
제대로 배웠는지 과제에서 검사할 예정이라고 하신다.
🤔 init을 private으로 선언하는 객체가 있을까요? (학생 질문)
👉 때로는 자신의 인스턴스만 생성하는 복잡한 객체가 있을 수 있습니다. 예를 들어 정적 메서드 같은 것들이나 혹은 여러 개가 있어서 그중 몇 개는 다른 사용자가 불러올 수 있지만 몇몇의 요소들은 내부적으로 생성된 것일 수 있죠 때로는 공개 설정된 것이 그 구현 과정 중에서 비공개 설정의 것을 불러올 수도 있습니다. 왜냐하면 init은 서로 불러올 수 있기 때문이죠.
Assertion
앱스토어에서는 무시되지만 개발 단계에선 에러를 발생시킵니다. Assertion은 코드를 안전하게 작성하는 좋은 방법 중 하나입니다. 아래와 같이 assert( ) 함수를 활용해서 에러를 검출할 수 있습니다.
func chooseCard(at index: Int) {
assert(cards.indices.contains(index), "Concentration.chooseCard(at:\(index)): chosen index not in the cards")
if !cards[index].isMatched {
if let matchIndex = indexOfOneAndOnlyFaceUpCard, matchIndex != index {
if cards[matchIndex].identifier == cards[index].identifier {
cards[matchIndex].isMatched = true
cards[index].isMatched = true
}
cards[index].isFaceUp = true
} else {
indexOfOneAndOnlyFaceUpCard = index
}
}
}
Extension
매우 강력한 도구로 🛠 확장을 통해서 변수와 함수를 다른 클래스에 추가할 수 있습니다. 그 클래스의 코드가 없거나 UIKit나 다른 프레임워크에 있더라도 말이죠. 어디든 가능합니다. Extension을 활용해서 methods/properties를 class/struct/enum 에 추가할 수 있습니다.
- 제약 사항 - 값을 저장할 수 없습니다
- 주의 사항 - 클래스에 어울리지 않는 함수를 추가하지 말 것
아래는 arc4random() 함수를 Int 클래스에 추가한 것입니다.
extension Int {
var arc4random: Int {
if self > 0 {
return Int(arc4random_uniform(UInt32(self)))
} else if self < 0 {
return -Int(arc4random_uniform(UInt32(abs(self))))
} else {
return 0
}
}
}
Enum
Optional It's an enumeration 👉 enum을 먼저 설명하고 뒤에 Optional 설명이 나옵니다
분리된 값만(descrete value) 가지는 데이터 타입입니다. value type이라 값을 전달할 때 복제됩니다. Swift의 enum은 강력하다고 합니다.
enum FastFoodMenuItem {
case hamburger(numberOfPatties: Int)
case fries(size: FryOrderSize)
case drink(String, ounces: Int)
case cookie
}
enum FryOrderSize {
case large
case small
}
// 값 할당해주는 방법
let menuItem: FastFoodMenuItem = FastFoodMenuItem.hamburger(patties: 2)
var otherItem: FastFoodMenuItem = FastFoodMenuItem.cookie
var otherItem2: FastFoodMenuItem = .cookie
var yetAnotherItem = .cookie // Swift can't figure this out
Checking an enum's state
enum은 등호를 사용하지 않고 switch문을 사용합니다. switch 문은 모든 상태를 확인해야 합니다. 확인하고 싶지 않은 것이 있다면 break을 사용하면 됩니다.
var menuItem = FastFoodMenuItem.hamburget(patties: 2)
switch menuItem {
case .hamburger: break
case .fries: print("fires")
case .drink: print("drink")
case .cookie: print("cookie")
}
Methods yes, (stored) Properties no
enum은 메서드는 되지만 stored properties는 가질 수 없습니다. 하지만 computed property는 가질 수 있습니다.
Modifying self in an enum
enum FastFoodMenuItem {
...
mutating func switchToBeingACookie() {
self = .cookie // this works even if self is a .hamburger, .fries or .drink
}
}
mutating이란 글자를 변경시키는 함수 앞에 두어야 합니다. 왜냐하면 enum은 value type이기 때문입니다.
value type은 복제되면서 전달이 이루어지는데 쓰기할 때 복제가 일어납니다. 그래서 만약 그렇게 되면 쓰기를 실행하기 전에는 복제가 생기지 않습니다. 그렇게 된다면 어떤 함수가 쓰기를 실행하는지 알아야 합니다. 이것이 mutating의 의미입니다.
Optionals
enum Optional<T> {
case none
case some(<T>)
}
옵셔널은 위 코드와 비슷한 enum이라고 설명한다. 그리고 다른 특징들을 캡쳐한 슬라이드들을 빠르게 설명하고 넘어갔다. 그와중에 Optional chaining 이라는 개념을 제일 아래 슬라이드와 함께 설명해주면서 중요하다고 했다.
짧게 요약하면 "?"를 사용해서 unwrap 한 변수가 값을 가지지 않을 경우 nil을 반환한다는 것이었습니다.





Data structure
- class
- 메서드와 데이터 모두에게 단일 상속을 지닌다
- reference type으로 heap에 머문다
- 🤔 언제 힙에서 사라질까?
👉 swift는 가비지 콜렉션을 사용하지 않고 automatic reference counting을 사용한다
- struct
- value type
- 다중 기능 상속
- 배열, 딕셔너리, 문자열, 문자, Double, Uint32 등 여러가지
- enum
- 아까 해서 더이상 하지 않을 것
- 기능 상속은 프로토콜을 통해 이뤄진다.
- protocol
- 다음 강의에서 설명하겠다
ARC (Automatic reference counting)
힙 내의 참조 타입에 포인터를 만들 때마다 Swift는 어딘가에 있는 카운터에 1을 더합니다. 그래서 매번 그 포인터가 가리키는 것이 없어지거나 더 이상 가리키지 않게 되었을 때 마치 옵셔널처럼 nil로 설정됩니다.
👉 예를 들어 그 카운트가 1 줄어들게 되죠. 그리고 카운트가 0이 되면 즉시 힙에서 그것을 꺼냅니다. 가비지 콜렉션처럼 흔적을 쫓거나 마킹해서 쓸어버리는게 아니라 더 이상 가리키는 포인터가 없는 즉시 삭제하는 것이죠.
실제로 그 내면은 꽤 복잡합니다.
Influencing ARC
- strong
👉 normal refrence counting - 어떤 포인터가 strong이라면 가리키고 있는 한 힙 내에 그것을 계속 두는 것 - weak (outlet에서 사용했었다)
👉 weak는 힙 내의 어떤 것을 가리키고 있지만 다른 사람이 흥미를 가져야만 흥미를 가지게 되는 것 입니다. 다른 사용자가 strong 포인터를 가지면 그것을 힙 내에서 유지하고 아무도 흥미가 없게되면 다시 말해서 strong 포인터가 없으면 nil을 받아서 그것을 힙에서 제거합니다. 모든 strong 포인터가 없어졌을 때 nil로 설정됩니다.
그래서 weak은 옵셔널입니다.
그래서 weak은 옵셔널 포인터로 참조 타입을 가리키는 것만 가능합니다. - unowned
👉 참조하지 않는다. 만약 힙 내부의 어떤 것을 가리키고 있을 때 strong 포인터로 인식하지 않는 것이고, 힙에서 사라졌을 때 접근하지 않는 것입니다.
메모리 사이클을 피하기 위해서 사용합니다. (like 순환 참조)
스탠포드 3강 유튜브: https://youtu.be/VIEzNBPmQKk