오늘 = 어제 + a

a >= 1

CS193P

[iOS] 스탠포드 CS193P 4강

Beck 2021. 8. 18. 17:05

Swift

지난 강의에 이어 Swift 문법에 대해 정리하려고 합니다.
다음 강의에서부터는 iOS와 UIKit과 괕은 것들을 살펴볼 예정입니다.


 

Struct

Concentration 클래스를 구조체로 바꿨습니다. 이 때 chooseCard() 함수에서 "self is immutable" 이라는 에러가 나옵니다. 구조체의 변수를 바꾸려고 하면 나오는 에러입니다. 이 에러는 함수 앞에 mutating을 명시하면서 해결할 수 있습니다. 

구조체 함수의 mutating 사용 전후의 모습

🤔 왜 구조체가 필요한가요?
👉 "value type, not reference type" 구조체는 값이 전달될 때마다 복사됩니다. 굉장히 비효율적이죠. 하지만 스위프트는 영리해서 값이 변경될 때만 복제합니다. 이것은 "copy on write" 이라고 합니다. 클래스는 이것이 없습니다. 여러 곳으로 전달될 때 힙 안에 존재하기 때문에 그저 포인터를 전달하는 것이죠. 그래서 한가지 객체에 20개의 포인터가 있을 수 있습니다.


Protocol

Protocols are way to express an API more concisely
Protocol은 API에서 원하는 것을 불러오는 방식입니다. 어떤 구조체든 열거형이든 클래스든 어떤 것이든 전달할 수 있고, 그걸 받는 메소드는 원하는 것이 무엇인지 나타낼 수 있습니다. 그래서 양쪽 모두 원하는 것을 얻게 되는 것이죠. 

프로토콜의 장점

  • Making API more flexibel and expressive
  • Blind, structured communication between View and Controller (delegation)
  • Madating behavior (e.g the keys of a Dictionary must be hashable)
  • Sharing functionality in disparate types (String, Array, CountableRange are all Collections)
  • Multiple inheritance (of functionality, not data)

프로토콜의 특징

  • the protocol declaration
  • a class, struct or enum decalaration that makes the claim to implement the protocol
  • the code in said class, struct or enum(or extension) that implements the protocol

Optional methods in a protocol

objective-c의 protocol은 optional method라는 개념이 존재합니다. 스위프트 프로토콜의 메서드는 필수적으로 모두 구현되어야 하지만 objective-c의 프로토콜은 optional 하게 구현될 수 있습니다. "@objc"를 메서드 앞에 붙여서 사용합니다. iOS의 delegation이 objective-c의 프로토콜을 사용합니다.

프로토콜 선언 방법

Protocol delcaration (pure declaration)

구조체에서 변수의 수정이 필요할 경우 위 슬라이드에 changeIt( ) 함수처럼 mutating을 명시해줘야 합니다. 하지만 프로토콜이 구조체에서 구현될 일이 없고, 클래스에서만 구현된다면 아래와 같이 콜론 다음에 class를 추가해주면 됩니다. 하지만 아래처럼 하는 것은 매우 드문 일이라고 합니다. 

class로만 구현되는 protocol

프로토콜을 아래와 같이 사용할 수 있습니다.

protocol Moveable {
	muatating func move(to pint: CGPoint)
}
class Car: Moveable {
	func move(to point: CGPoint) { ... }
	func changeOil()
}
struct Shape: Moveable {
	mutating func move(to point: CGPoint) { ... }
	func draw()
}

let prius: Car = car()
let square: Shape = Shape()

var thingToMove: Moveable = prius
thingToMove.move(to: ...)
thingToMove.changeOil() // 이 줄은 에러다❗️
thingToMove = square
let thingsToMove: [Moveable] = [prius, square]

func slid(slider: Moveable) {
	let positionToSlideTo = ...
	slider.move(to: protisionToSlideTo)
}
slide(prius)
slide(square)

Use of Protocol

Delegation 사용 방법

  1. A View declares a delegation protocol
  2. This View's API has a weak delegate property whose type is that delegation protocol
  3. The View uses the delegate property to get/do things it can't own or control on its own
  4. The Controller declares that it implements the protocol
  5. The Controller sets delgate of the View to itslef using the property in #2 above
  6. The Controller implements the protocol (probably it has lots of optional methods in it)

👉 위에서 weak을 쓴 이유: 뷰는 컨트롤러를 가리키는 포인터를 가지고, 컨트롤러는 뷰를 가리키는 많은 포인터를 갖고 있습니다. 이 때 뷰는 자신의 will, did, should를 받는 것을 가리키는 것입니다. 만약 그것이 힙을 빠져나가려 한다면 nil로 설정하고 더 이상 메시지를 보내지 않습니다. 그렇게 뷰는 더 이상 컨트롤러를 힙 안에 두지 않게 됩니다. weak라서 가능한 일입니다.

Hashable : Being a key in a Dictionary

protocol Equatable {
	static func ==(lhs: Self, rhs: Self) -> Bool
}
protocol Hashable: Equatable {
	var hashValue: Int { get }
}

스위프트에서 "=="를 쓴다면 스위프트 내장 함수가 아니라 위의 함수를 사용하는 것입니다.

Being a key in a Dictionary

딕셔너리는 다음과 같이 선언됩니다. 키는 Hashable 해야 하지만 Value에는 제한이 없습니다.

Dcitonary<Key: Hashable, Value>

"Multiple inheritance" with protocols

🤔 이렇게 상속 받은 것들을 어디에 구현해야할까요?
👉 앞선 강의에서 Int 클래스에 random 함수를 추가한 것처럼 extension에 구현합니다. 


String

유니코드로 이뤄져있으며, Character라는 개념도 있습니다. Character"single lexical chracter"이며, 이것도 여러개의 유니코드로 이뤄져있습니다. 예를들어 café4 Unicodes (c-a-f-é) 이거나 5 Unicodes (c-a-f-e-') 가 될 수 있습니다.

👉 그래서 String의 인덱스는 Int 일 수 없습니다

인덱스를 Int로 받을 수 없기 때문에 Swift는 String.Index 라는 타입을 제공합니다. startIndex, index(_, offsetBy), index(of), components(separatedBy) 등의 함수로 사용할 수 있습니다.

let pizzaJoint = "café pesto"
let firstCharacterIndex = pizzaJoint.startIndex // of type String.Index
let fourthChracterIndex = pizzaJoint.index(firstCharacterIndex, offsetBy: 3)
let fourthCharacter = pizzaJoint[fourthChracterIndex] // é

if let firstSpzce pizzaJoint.index(of: " ") { // return nil if " " not found
	let secondWordIndex = pizzaJoint.index(firstSpace, offsetBy: 1)
	let secondWord = pizzaJoint[secondWordIndex..<pizzaJoint.endIndex]
}

/*
	..<
	This is a Range of String.Index
*/

유용한 String method

아래 코드는 Concentration에서 쓰인 emojiChoices 배열을 String으로 만들어서 처리하는 예시입니다.

private var emojiChoices = "🦇😱🙀👿🎃👻🍭🍬🍎"

private var emoji = [Card:String]()

private func emoji(for card: Card) -> String {
    if emoji[card] == nil, emojiChoices.count > 0 {
        let randomIndex = emojiChoices.index(emojiChoices.startIndex, offsetBy: emojiChoices.count.arc4random)
        emoji[card] = String(emojiChoices.remove(at: randomIndex))
    }
    return emoji[card] ?? "?"
}

NSAttributedString

문자열 데이터와 딕셔너리 형태의 attribbutes를 갖고 있는 데이터 타입입니다. 앞에 붙은 "NS"는 오래된 API임을 나타냅니다. String이 아닌 클래스입니다. 값을 바꾸고 싶다면 NSMutableAttributedString을 써야합니다.
❗️ 자료구조에서 절대 Any를 쓰지 말아야 합니다. 예시로 썼을 뿐 따라하지 말라고 합니다.

let attributes: [NSAttributedStringKey: Any] = [
	.strokeColor: UIColor.orange
	.strokeWidth: 5.0
]
let attribText = NSAttributedString(string: "Flips: 0", attributes: attributes)
flipCountLabel.attributedText = attribText // UIButton has attributedTitle

Fucntion Types

Functions are people too!
변수를 "function" 타입으로 선언할 수 있습니다.

var operation: (Double) -> Double
operation = sqrt
let result = opration(4.0)

Closures

Closure를 활용하면 함수를 한줄에 빠르게 구현할 수 있습니다. 아래 예시는 클로저 적용 전후를 보여줍니다.

func changeSign(operand: Double) -> Double {return -operand }
var operation: (Double) -> Double
operation = chagneSign
let reslt operation(4.0)
var operation: (Double) -> Double

// 아래 3줄은 모두 같은 의미를 같습니다
operation = { (operand: Double) -> Double in return -operand }
operation = { (operand) in -operand }
operation = { -$0 }

let reslt operation(4.0)

Closure 언제 쓸까?

배열이 map을 호출할 때 아래와 같이 호출 할 수 있습니다. trailing closure 개념을 이용하면 훨씬 편하게 구현할 수 있습니다. Trailing closure는 어떤 함수의 마지막 인자가 closure라면 closure를 괄호 밖으로 내놓아도 됩니다.

let primes = [2.0, 3.0, 5.0, 7.0, 11.0]
let negativePrimes = primes.map({ -$0 })
let invertedPrimes = primes.map() { 1.0/$0 }
let primeStrings = primes.map { String($0) }

속성 초기화할 때도 클로저를 사용할 수 있습니다. lazy property를 초기화 할 때 유용합니다.

var someProperty: Type = {
	return <the constructed value>
}()

Closure 주의사항

클로저는 주변 변수를 포착합니다. 지역 변수나 인스턴스 변수가 클래스 안에 있다면 클로저 안에서 사용할 수 있습니다. 클로저는 reference type 입니다. 그래서 클로저는 힙에 쌓입니다. 클로저가 주변 코드로부터 변수를 받으면 그 변수도 힙에 쌓이게 됩니다.

아래의 경우 ltuae는 힙에 들어가고 클로저가 없어질 때까지 유지됩니다. 만약 ltuae가 아니라 연산의 배열이 있는 클래스 변수를 넣는다면 클로저가 그 클래스를 힙에 넣습니다. 그리고 배열 안에 있는 클래스가 또 클로저를 힙에 넣게 됩니다. 배열 안에서 서로를 가리키게 되는 것이죠. 이것은 메모리 사이클을 발생시킵니다. 그리고 힙에 머무르게 됩니다. unowned를 사용하면 이런 메모리 사이클을 해결할 수 있다고 합니다. 이에 대해서는 지금 당장 다루지는 않습니다.

var ltuae = 43
opration = { ltueae * $0 }
arrayOfOperations.append(operation)

 

강의 영상: https://youtu.be/Y8ss6118RQY

 

 

'CS193P' 카테고리의 다른 글

[iOS] 스탠포드 CS193P 5강 - 2  (0) 2021.08.20
[iOS] 스탠포드 CS193P 5강 - 1  (0) 2021.08.20
[iOS] 스탠포드 CS193P 3강  (0) 2021.08.18
[iOS] 스탠포드 CS193P 2강  (0) 2021.08.15
[iOS] 스탠포드 CS193P 1강  (0) 2021.08.10