티스토리 뷰
이번 글에서는 Swift 공식 문서의 22번째 단원인 Generics을 읽고 정리한 글을 쓰려고 한다.
Apple Swift 공식 문서 22단원 - Generics
Generics
Generic
(제네릭) 코드를 사용 하면 정의한 요구사항에 따라 모든 타입에서 작동할 수 있는 유연하고 재사용 가능한 함수와 타입을 작성할 수 있다. 이는 중복을 피하고 명확하고 추상적인 방식으로 코드를 작성할 수 있다.
제네릭은 Swift의가장 강력한 기능 중 하니이고 Swift 표준 라이브러리의 대부분은 제네릭 코드로 빌드된다. 사실 지금까지 작성한 모든 글에서 제네릭을 사용하고 있었다. 예를 들어 Swift의 Array
,Dictionary
타입은 모두 제네릭 컬렉션이다. 즉 Array
에 Int
,String
등 모든 타입을 저장할 수 있는 이유가 제네릭 타입이기 때문이다. 따라서 저장되는 타입에 제한이 없는 것이다.
The Problem That Generics Solve
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"
위의 코드는 제네릭을 사용하지 않은 함수 swapTwoInts
를 정의한 것이다. swapTwoInts
함수는 In-Out 매개변수를 사용하여 a
,b
의 값을 바꾼다. 실제 위의 코드에서 사용한 것을 보면 잘 동작하는 것을 볼 수 있다.
swapTwoInts
는 Int
값에만 적용할 수 있다는 한계가 있다. 만약 String
값을 바꾸고 싶다면 새로 함수를 작성해야 하는 것이다. 할 순 있지만 조금 귀찮다. 즉 동일한 동작을 하는 코드를 다양한 타입에서 사용하기 위해선 Generic
코드를 사용하면 된다.
Generic Functions
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
위의 코드에서 구현한 swapTwoValues
함수는 아까 구현했던 swapTwoInts
함수에 Generic
코드를 사용하여 다양한 타입에 적용할 수 있도록 만든 함수이다. 함수를 제네릭으로 만들면 타입 이름 대신 (예를 들어 Int
,Double
) <T>
를 사용한다. 이는 placeholder
라고 하며 개발자는 T가 무슨 타입인지 정하진 않았지만 a
,b
는 모두 같은 타입 T라는 것은 선언한 것이다. 실제 T 대신 사용될 타입은 함수가 호출될 때 정해진다.
제네릭 함수와 기존 함수와의 차이점은 제네릭 함수는 정의할 때 <T>
라는 것을 적어준다는 것인데 여기서 대괄호는 함수 이름과는 상관없다. 또한 Swift는 T의 타입을 알려고 하지 않는다. 타입은 함수 호출 시 Swift가 알아서 유추하여 정하게 된다.
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"
위와 같이 기존의 함수와 같은 방식으로 제네릭 함수를 호출할 수 있다.
Type Parameters
아까 위에서 본 제네릭 함수에서 <T>
를 사용한 것을 봤을 것이다. 이는 placeholder
라고 불리며 type parameter
의 예이다. 타입 매개변수는 플레이스 홀더의 타입을 지정하고 이름을 지정한다. 즉 여기서 지정한 타입 매개변수의 이름은 T
인 것이다. 만약 <B>
라고 함수 뒤에 썼다면 타입 매개변수의 이름은 B
이다.
이러한 타입 매개변수는 실제 사용할 땐 실제 존재하는 타입으로 변하게 된다. Int
로 바뀌거나 String
으로 바뀌며 실제 작업을 수행한다. 만약 타입 매개변수가 여러 개 필요하다면 <T,B>
와 같이 타입 매개변수를 지정하여 2개의 타입 매개변수를 만들 수 있다.
Naming Type Parameters
대부분의 경우 타입 매개변수는 설명이 포함된 이름을 가지고 있다. 예를 들어 Dictionary
의 <Key, Value>
과 같이 이름을 만든 것도 확인할 수 있다. 물론 이는 자유롭게 만들면 되고 아까와 같은 <T>
처럼 단일 문자를 사용하는 것이 일반적이다.
Generic Types
제네릭 함수 외에도 Swift에서 제네릭 타입을 정의할 수 있다. 정의한 제네릭 타입은 클래스, 구조체, 열거형에서 어떤 타입으로도 사용할 수 있으며 비슷한 방법으로 Array, Dictionary에서도 사용할 수 있다.
이번 섹션에서는 Stack
이라는 일반 컬렉션 타입을 작성하는 방법을 알아볼 것이다. Stack
은 Array
와 비슷하지만 LIFO(Last In First Out)의 규칙을 따르는 제한이 있다. 따라서 컬렉션 끝에서만 항목을 추가하고 제거할 수 있게 되며 값을 추가하는 것은 PUSH
, 값을 제거하는 것은 POP
으로 불린다.
위의 그림은 스택의 PUSH
와 POP
을 나타낸 그림이다. 왼쪽부터 설명을 하면 다음과 같다.
- 스택에 3개의 값이 있다.
- 네 번째 값이 스택의 top에 push 된다.
- 가장 마지막에 들어온 네 번째 값이 제일 위에 존재하게 된다.
- 가장 위에 있는 값이 pop 된다.
- pop이 완료되면 스택에는 다시 3개의 값이 존재하게 된다.
이러한 스택을 제네릭을 사용하지 않고 Int
자료형만 가능하도록 만들어 보자.
struct IntStack {
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}
위와 같이 만들면 스택을 만들 수 있지만 오로지 Int
형에서만 사용할 수 있다. 이를 모든 자료형이 사용할 수 있도록 제네릭을 함께 사용하면 다음과 같다.
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
위의 코드에서 타입 매개변수의 이름을 Element
로 지정했다. 때문에 구조체 안에서 타입의 이름으로 Element
를 사용하는 것을 볼 수 있다. Element
타입은 처음부터 타입의 종류가 정해진 것이 아니고 사용될 때 정해진다. 이러한 개념을 위의 Stack
구조체로 이해해보자.
items
프로퍼티를 초기화할 때Element
타입으로 만든다.push(_ :)
메서드에서 사용하는 매개변수의 타입은Element
이다.pop()
메서드에서 반환하는 값의 타입은Element
이다.
제네릭 타입이기 때문에 Stack
을 사용하여 Array
,Dictionary
와 유사한 방식으로 Stack
을 만들 수 있다.
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// the stack now contains 4 strings
위의 코드를 실행하면 위의 그림과 같은 방식으로 실행된다.
let fromTheTop = stackOfStrings.pop()
// fromTheTop에는 "cuatro"가 들어있게 되고 스택에는 3개의 값만 남는다.
스택에서 값을 제거하고 싶다면 위의 코드처럼 사용하면 된다. 그렇게 되면 위의 그림처럼 값이 제거된다.
Extending a Generic Type
제네릭 타입에 익스텐션을 사용할 때 타입 매개변수는 제공하지 않아도 된다. 하지만 제네릭 타입을 정의할 때 정의한 타입 매개변수 목록은 익스텐션 본문 안에서 사용할 수 있으며 타입 매개변수들의 이름도 그대로 사용할 수 있다.
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
if let topItem = stackOfStrings.topItem {
print("The top item on the stack is \(topItem).")
}
// Prints "The top item on the stack is tres."
위의 코드는 아까 위에서 정의한 Stack
구조체에 익스텐션을 사용한 예이다. 위의 코드에서는 topItem
이라는 읽기 전용 계산 프로퍼티를 추가했다. 이 프로퍼티는 스택에서 항목을 제거하지 않고 스택의 최상위 항목을 반환한다. 이때 반환 값의 타입은 아까 정의한 타입 매개변수인 Element
타입이다.
위의 코드에서 볼 수 있듯이 제네릭 타입에 익스텐션을 사용할 땐 타입 매개변수 목록을 정의하지 않는다. 하지만 원래 코드에서 정의한 타입 매개변수 목록을 그대로 사용할 수 있다.
Type Constraints
이 글에서 구현한 swapTwoValues
,Stack
은 모든 타입에서 사용될 수 있었다. 하지만 제네릭 함수와 타입에서 사용할 수 있는 타입에 제약 조건을 주는 것이 유용한 경우가 있다. 타입 제약조건을 만족하기 위해선 타입 매개변수가 특정 클래스를 상속하거나 특정 프로토콜을 준수해야 한다.
예를 들어 Swift의 Dictionary
는 Key
로 사용할 수 있는 타입에 제약을 뒀다. Dictionary
의 Key
에 사용될 수 있는 타입은 반드시 해시할 수 있어야 한다. 즉 고유하게 표현할 수 있는 방법을 제공해야 한다. 이러한 제약조건은 Dictionary
의 Key
타입에 대한 타입 제약조건에 의해 생기며 제약조건을 만족하기 위해선 Hashable
프로토콜을 만족하는 타입을 사용해야 한다. Swift의 기본적인 타입인 String
, Int
, Double
등은 Hashable
프로토콜을 준수하므로 Dictionary
에서 사용할 수 있다.
직접 제네릭 타입을 만들 때 고유한 제약 조건을 정의할 수 있으며 이러한 제약조건은 제네릭 프로그래밍의 많은 기능을 제공할 수 있다.
Type Constraint Syntax
타입 매개 변수를 지정할 때 :
으로 구분된 타입 매개변수 이름 뒤에 단일 클래스나 프로토콜 제약 조건을 작성하여 제약조건을 만들 수 있다.
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// function body goes here
}
예를 들어 위의 코드에는 두 개의 타입 매개변수가 있다. T
는 SomeClass
클래스의 서브 클래스여야 하고 U
는 SomeProtocol
프로토콜을 준수하는 타입 이어야 한다. 위와 같이 타입 매개변수에 제약조건을 만들 수 있다.
Type Constraint in Action
그럼 타입 매개변수에 제약조건을 주는 예를 살펴보자.
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
print("The index of llama is \(foundIndex)")
}
// Prints "The index of llama is 2"
위의 코드에서 정의한 findIndex(ofString:in:)
함수는 제네릭을 사용하지 않은 함수이다. 이 함수의 매개변수는 String
,[String]
타입을 가진다. 하지만 이 함수는 String
타입에만 사용할 수 있으므로 모든 타입에 사용할 수 있는 제네릭 함수로 바꿔보자.
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
위와 같이 제네릭 함수로 만들 수 있다. 이젠 모든 타입에서 이 함수를 사용할 수 있게 되었다. 하지만 조금 문제가 있을 수 있는 코드이다. 왜냐하면 Swift의 모든 타입이 ==
연산자를 사용할 수 있지는 않기 때문이다. 따라서 타입 매개변수로 해당 연산자를 사용할 수 있는 타입만 사용할 수 있도록 제약조건을 줘야 한다.
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex is an optional Int with no value, because 9.3 isn't in the array
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex is an optional Int containing a value of 2
위와 같이 타입 매개변수 T
에 Equatable
프로토콜을 준수하라는 제약조건을 주면 문제를 해결할 수 있다.
Associated Types
프로토콜을 정의할 때 정의의 일부로 associated 타입을 선언하는 경우가 있다. 프로토콜에서 이러한 타입을 정의할 땐 associatedtype
키워드를 함께 사용해줘야 한다.
Associated Types in Action
그럼 associated 타입에 제네릭을 사용하는 예를 살펴보자.
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
위의 코드에서 정의한 Container
프로토콜은 append(_ :)
메서드, count
프로퍼티, Int
인덱스 값을 사용하는 서브 스크립트를 요구사항으로 정의했다. 요구사항을 만족하기 위해서 Item
이라는 associated 타입을 정의했다.
struct IntStack: Container {
// original IntStack implementation
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// conformance to the Container protocol
typealias Item = Int
mutating func append(_ item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
위의 코드는 Container
프로토콜을 준수하는 제네릭을 사용하지 않은 구조체이다. IntStack
구조체는 프로토콜에서 요구한 Item
타입의 실제 타입으로 Int
를 사용하게 된다. 하지만 이렇게 구현하면 다른 타입에 대해서는 사용할 수 없기 때문에 제네릭을 사용하여 다시 구현해보자.
struct Stack<Element>: Container {
// original Stack<Element> implementation
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// conformance to the Container protocol
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
위와 같이 제네릭을 사용한 Stack
구조체를 정의할 수 있다. 여기서는 타입 매개변수로 Element
라는 이름을 사용했다. 이젠 Container
프로토콜을 준수하는 Stack
구조체에 다양한 타입을 사용할 수 있다.
Extending an Existing Type to Specify an Associated Type
Associated 타입을 사용한 프로토콜도 익스텐션을 사용하여 기존 타입에 프로토콜을 준수하도록 추가할 수 있다.
예를 들어 Array
타입에 아까 만든 Container
프로토콜을 추가로 채택해보자.
extension Array: Container {}
위와 같이 Array
타입에 Container
프로토콜을 추가로 채택할 수 있다. Array
타입에는 이미 append(_ :)
메서드, count
프로퍼티, Int
인덱스를 사용하는 서브 스크립트가 존재하기 때문에 Container
가 요구하는 것을 모두 준수한다.
Adding Constrints to an Associated Type
프로토콜에 정의된 Associated 타입에도 타입 제약조건을 추가할 수 있다.
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
위와 같이 Item
에 Equatable
프로토콜을 준수해야 한다는 제약조건을 추가할 수 있다.
Using a Protocol in Its Associated Type's Constraints
프로토콜은 요구사항의 일부로 나타날 수 있다.
protocol SuffixableContainer: Container {
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}
위의 코드는 Container
프로토콜에 요구사항을 추가한 SuffixableContainer
프로토콜을 정의한 예이다. SuffixableContainer
프로토콜 Suffix
라는 associated 타입과 suffix(_ :)
메서드를 추가로 요구한다. Suffix
타입은 SuffixableContainer
프로토콜을 준수해야 하고 Item
타입은 Container
프로토콜의 Item
타입과 동일해야 한다.
extension Stack: SuffixableContainer {
func suffix(_ size: Int) -> Stack {
var result = Stack()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
// Inferred that Suffix is Stack.
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix contains 20 and 30
위의 코드는 Stack
타입에 SuffixableContainer
프로토콜을 추가로 채택하는 코드이다. 이 코드에서 Suffix
의 associated 타입도 Stack
이므로 suffix(_ :)
메서드는 Stack
타입을 반환한다.
extension IntStack: SuffixableContainer {
func suffix(_ size: Int) -> Stack<Int> {
var result = Stack<Int>()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
// Inferred that Suffix is Stack<Int>.
}
위의 코드는 제네릭을 사용하지 않은 IntStack
구조체에 익스텐션을 사용하여 SuffixableContainer
프로토콜을 추가로 채택하는 코드이다. Suffix
의 타입으로 IntStack
대신 Stack<Int>
를 사용한 것을 볼 수 있다.
Generic Where Clauses
타입 제약 조건을 사용하면 제네릭 함수, 서브 스크립트, 타입에서 associated 타입 매개 변수에 대한 요구사항을 정의할 수 있다.
Associated 타입에 대해 요구사항을 정의하는 것도 유용할 수 있다. 이러한 정의는 제네릭 where 절을 정의하여 할 수있다. 제네릭 where절을 사용하면 associated 타입이 특정 프로토콜을 준수해야 하거나 특정 타입 매개변수와 동일해야 한다고 요구할 수 있다. 제네릭 where 절은 where
키워드로 시작하고 그 뒤에 associated 타입에 대한 제약조건, 동등관계를 써주면 된다.
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
// Check that both containers contain the same number of items.
if someContainer.count != anotherContainer.count {
return false
}
// Check each pair of items to see if they're equivalent.
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// All items match, so return true.
return true
}
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
var arrayOfStrings = ["uno", "dos", "tres"]
if allItemsMatch(stackOfStrings, arrayOfStrings) {
print("All items match.")
} else {
print("Not all items match.")
}
// Prints "All items match."
위의 코드에서 allItemsMatch
함수의 본문을 여는 중괄호 바로 앞에 있는 where
절에서 associated 타입에 대한 제약조건을 정의하는 것을 볼 수 있다. 위에 코드에 정의된 함수는 두 개의 Container
인스턴스에 모든 항목이 같은 순서로 존재하는지에 대한 여부를 알려주는 함수이다. 이 함수의 두 가지 타입 매개변수에 대한 요구사항은 다음과 같다.
C1
은Container
프로토콜을 준수해야 한다.C2
는Container
프로토콜을 준수해야 한다.C1
의 항목과C2
의 항목은 동일해야 한다.C1
의 항목들은Equatable
프로토콜을 준수해야 한다.
이러한 요구사항을 통해 allItemsMatch
함수는 Container
타입이 다른 경우에도 비교할 수 있게 된다. 실제 함수를 사용한 예를 보면 타입이 다른 두 개의 인스턴스를 비교하는 것을 볼 수 있다.
Extensions with a Generic Where Clause
제네릭 where
절을 익스텐션에서도 사용할 수 있다.
extension Stack where Element: Equatable {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}
if stackOfStrings.isTop("tres") {
print("Top element is tres.")
} else {
print("Top element is something else.")
}
// Prints "Top element is tres."
위의 코드는 제네릭을 사용한 Stack
구조체에 익스텐션을 사용할 때 where
절도 사용한 코드이다. 익스텐션으로 추가한 isTop(_ :)
메서드는 매개변수로 받은 값이 현재 인스턴스의 제일 위에 존재하는 값인지를 확인해주는 메서드이다. 여기서 타입 매개변수로 사용할 Element
는 Equatable
프로토콜을 준수해야 한다. 만약 그렇지 않다면 컴파일 에러를 발생한다.
제네릭 where
절은 프로토콜에 익스텐션을 사용할 때도 사용할 수 있다.
extension Container where Item: Equatable {
func startsWith(_ item: Item) -> Bool {
return count >= 1 && self[0] == item
}
}
if [9, 9, 9].startsWith(42) {
print("Starts with 42.")
} else {
print("Starts with something else.")
}
// Prints "Starts with something else."
위의 코드와 같이 프로토콜에서 제네릭 where
절을 사용할 수 있다.
Contextual Where Clauses
제네릭 타입을 정의할 때 where
절로 일부에만 제약조건을 줄 수 있다.
extension Container {
func average() -> Double where Item == Int {
var sum = 0.0
for index in 0..<count {
sum += Double(self[index])
}
return sum / Double(count)
}
func endsWith(_ item: Item) -> Bool where Item: Equatable {
return count >= 1 && self[count-1] == item
}
}
let numbers = [1260, 1200, 98, 37]
print(numbers.average())
// Prints "648.75"
print(numbers.endsWith(37))
// Prints "true"
위와 같이 average()
메서드와 endsWith(_ :)
메서드에 서로 다른 제약조건을 줄 수 있다. 이렇게 한 번에 추가해도 되지만 이전에 배운 방식대로 따로따로 추가할 수도 있다.
extension Container where Item == Int {
func average() -> Double {
var sum = 0.0
for index in 0..<count {
sum += Double(self[index])
}
return sum / Double(count)
}
}
extension Container where Item: Equatable {
func endsWith(_ item: Item) -> Bool {
return count >= 1 && self[count-1] == item
}
}
Associated Types with a Generic Where Clause
Associated
타입에 제네릭 where
절을 사용할 수 있다.
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
func makeIterator() -> Iterator
}
위의 코드처럼 Associated
타입에 제네릭 where
절을 사용할 수 있다. Iterator
타입에 where
절로 제약조건을 준 것을 볼 수 있다.
protocol ComparableContainer: Container where Item: Comparable { }
만약 어떤 프로토콜을 상속하려는 경우 where
절을 사용하여 상속된 associated
타입에 제약조건을 추가할 수 있다.
Generic Subscripts
서브 스크립트에도 제네릭을 사용할 수 있고 where
절을 사용하여 제약조건을 줄 수도 있다.
extension Container {
subscript<Indices: Sequence>(indices: Indices) -> [Item]
where Indices.Iterator.Element == Int {
var result = [Item]()
for index in indices {
result.append(self[index])
}
return result
}
}
위와 같이 서브 스크립트에 제네릭과 where
절을 사용할 수 있다. 위의 코드에서 서브 스크립트는 다음과 같은 제약조건이 있다.
- <>로 묶인 매개변수
Indices
는Sequence
프로토콜을 준수하는 타입 이어야 한다. - 서브 스크립트는 매개변수로
Indices
타입의 인스턴스를 사용한다. - 매개변수
indices
에 전달된 값은Int
타입이다.
'Swift > Swift_Documents' 카테고리의 다른 글
[Swift 문법] Swift 공식 문서 정리 - 24 - Automatic Reference Counting (ARC) (3) | 2020.08.27 |
---|---|
[Swift 문법] Swift 공식 문서 정리 - 23 - Opaque Types (0) | 2020.08.26 |
[Swift 문법] Swift 공식 문서 정리 - 20 - Extensions (익스텐션) (0) | 2020.08.15 |
[Swift 문법] Swift 공식 문서 정리 - 19 - Nested Types(중첩 타입) (0) | 2020.08.15 |
[Swift 문법] Swift 공식 문서 정리 - 18 - Type Casting(타입 캐스팅) (0) | 2020.08.14 |
- Total
- Today
- Yesterday
- Xcode
- Swift
- DP
- mac
- 코딩테스트
- dfs
- 문법
- operator
- document
- BFS
- 코테
- Combine
- 자료구조
- pattern
- System
- design
- OS
- 앱개발
- operating
- 스위프트
- Publisher
- 백준
- IOS
- Apple
- 알고리즘
- 아이폰
- 테이블뷰
- 동시성
- 프로그래밍
- OSTEP
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |