티스토리 뷰
안녕하세요 :) Zedd입니다.
프로토콜의 마지막글!! 벌써 4번째 시간이네요 :)키키
시작할게요!
Class-Only Protocols
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
// class-only protocol definition goes here
}
이렇게 프로토콜에 AnyObject를 상속받으면, 이제 SomeClassOnlyProtocol은 클래스에서만 채택할 수 있게 된답니다 :)
SomeClassOnlyProtocol을 구조체나 열거형에서 채택하는것은 컴파일 에러를 일으킵니다.
이렇게 Class-Only Protocol은 정의된 동작이 값타입(Value Type)이 아닌 참조타입(Reference Type)이라고 가정하거나 요구할 경우에 클래스 전용 프로토콜을 사용하세요.
Protocol Composition
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
간단한 프로토콜 2개를 만들었어요! 각각 String타입인 name과 Int타입인 age를 만들었습니다. 둘다 get만 요구하네요.
struct Person: Named, Aged {
var name: String
var age: Int
}
자!!! Person이라는 구조체를 만들었어요!!! 그리고 Named와 Aged라는 방금 만든 프로토콜을 채택해주었습니다.
그럼 이 프로토콜들이 요구하는 걸 만들어줘야겠죠? name과 age가 둘다 get만 요구했으니 어떠한 종류의 프로퍼티로도 요구사항을 만족할 수 있습니다.
예제에서는 저장프로퍼티로 만들어줬네요.
이까지는 이해가 잘가시죠?
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
위 함수는 Person 구조체 안에 있는 게 아니라 밖에 있는 함수에요. 그런데 신기한게 보이네요
Named & Aged
아까 위에서 Protocol Composition의 설명을 봐서 눈치 채셨겠지만 &로 나타낸다고 그랬죠?
Named & Aged은 Protocol Composition을 사용한 것 같네요.
근데 이 Named & Aged가 함수의 파라미터로 들어가 있네요.
눈치 채신분들도!!!계시겠지만 뭔가 감이 오지 않으세요!?
바로 저 wishHappyBirthday라는 함수의 celebrator라는 파라미터에 올 수 있는 타입은!!!!!!
Named 프로토콜과 Aged 프로토콜 둘 다를 준수하는 타입이어야 한다!!!!!!!!!!라는 것을 의미합니다.
우오앙ㅇ앙
그럼 저 wishHappyBirthday라는 함수를 사용해봅시다.
아까 우리 Person이라는 구조체를 하나 만들었었죠? 이 구조체는 Named와 Aged라는 방금 만든 프로토콜을 채택해주었습니다.....
즉!!!!!!
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// Prints "Happy birthday, Malcolm, you're 21!"
이 birthdayPerson은 Person의 인스턴스이므로 아주 자동적으로 Named와 Aged라는 프로토콜을 준수하고 있다고 볼 수 있고!!!!!!!!1111 wishHappyBirthday의 파라미터에 들어갈 수 있게 되는 것이죠.
왜냐???Named와 Aged라는 프로토콜을 둘다!!!!!둘다!!!준수하고 있기 때문이죠.
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
이렇게 파라미터의 name프로퍼티와 age프로퍼티를 바로 불러올 수 있는 이유도 이 파라미터로 들어올 애들은 Named와 Aged라는 프로토콜이 요구한 요구사항을 다 만들었을거거든 ㅇㅇ 그러므로 확신할 수 있죠. name과 age프로퍼티가 있다는 것을요.
아하~~~~~~~~~
ㅇㅋㅇㅋㅇ
예제를 하나만 더 볼까요?
protocol Named {
var name: String { get }
}
class Location {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
아까 우리가 만들었던 Named라는 프로토콜과 Location이라는 클래스를 만들어봤어요.
이까지는 별거없죠?
예제를 하나만 더 볼까요?
class City: Location, Named {
var name: String
init(name: String, latitude: Double, longitude: Double) {
self.name = name
super.init(latitude: latitude, longitude: longitude)
}
}
자...그리고 City라는 클래스를 하나 만들었는데!!!!!!!!Location이라는 클래스를 상속받고, Named라는 프로토콜을 채택한 것을 볼 수 있습니다.
그러면 이제 City라는 클래스는 Named프로토콜이 요구하는 name을 구현해줘야합니다. 저장프로퍼티로 만들어줬네요.
class고, 프로퍼티에 기본값이 없으니 init을 꼭 만들어줘야하죠? City는 init안에서 부모인 Location의 이니셜라이져를 호출했네요.
이까지도 별거없습니다...
func beginConcert(in location: Location & Named) {
print("Hello, \(location.name)!")
}
자.. beginConcert라는 함수를 하나 만들었어요! 근데 위에서 했던 예제랑 조금 다르네요? 뭐가 다르죠?
네!!! 저~~위에서는 Named와 Aged가 둘다 프로토콜이었죠? 하지만 지금은 Named는 프로토콜이지만, Location은 클래스죠!!!
Protocol Composition설명에서 말씀드렸죠?
"프로토콜의 목록 외에도, Protocol Composition에는 필요한 슈퍼 클래스를 지정하는데 사용할 수 있는 클래스 타입이 포함될 수 있습니다."
라구요.
클래스 타입이 포함 될 수 있어요!!!!
그럼 Location & Named은 무슨 의미일까요?
"Location의 하위 클래스 이며, Named프로토콜을 준수하는 모든 타입"을 의미합니다.
이 경우에 City클래스는 모든 것을 만족하죠? Location을 상속받고있고, Named프로토콜을 채택하고 준수하고 있으니까요.
Protocol Composition에서 주의해야할 건 Protocol Composition목록들에 있는 것들을 모두!! 하나도 빠짐없이 만족시킨 타입만이 허락된다는것을 아셔야해요.
Location을 상속받고 있지않거나, Named프로토콜을 준수하고 있지 않다면, 저 beginConcert의 파라미터로 들어오지 못하게 됩니다.
아시겠죠????
let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// Prints "Hello, Seattle!"
City인스턴스는 beginConcert의 파라미터가 될 수 있겠죠?
이제 Protocol Composition이 어떤 느낌인지 아시겠죠?
Checking for Protocol Conformance
protocol Named{
var name : String {get}
}
위에서 봤던 Named프로토콜 ~.~
class A : Named {
var name : String
init(name:String) {
self.name = name
}
}
class B {
var name : String
init(name:String) {
self.name = name
}
}
그리고 클래스를 만들어줬습니다. A는 Named프로토콜을 채택하고, 준수하도록 만들어줬고,
B는 Named프로토콜을 채택하지 않았습니다.
let instanceA = A(name: "Zedd")
let instanceB = B(name: "Martin Garrix")
var arr = [AnyObject]()
arr.append(instanceA)
arr.append(instanceB)
그리고 AnyObject타입의 배열을 하나 만들어서 클래스 A의 인스턴스와 클래스 B의 인스턴스를 넣어줬습니다.
for index in arr{
if let index = index as? Named{
print(index.name)
}
else{ print("Wrong") }
}
배열이니 for문을 돌 수 있겠죠? as?구문을 통해 다운캐스팅을 해주겠습니다. 지금 현재 배열에 있는 인덱스가 Named라는 프로토콜로 다운캐스팅 할 수 있는지요.
자..클래스 A는 Named프로토콜을 채택하고 준수했었지만, B는 아니었습니다.
그럼 결과가 어떻게 나올지 예상가시죠?
클래스 A인스턴스. 즉 Zedd라는 이름을 가지게 된 인스턴스는 Named프로토콜을 채택하고 준수하기때문에 언래핑이 잘 된 것을 볼 수 있습니다
하지만 클래스 B인스턴스. 즉 Martin Garrix라는 이름을 가지게 된 인스턴스는 Named프로토콜을 채택조차 하고 있지 않기때문에 as? 다운캐스팅으로 nil이 반환되므로 else구문에서 Wrong이 출력된 것을 볼 수 있습니다.
Optional Protocol Requirements
자.. 무슨소리인지 모르겠으니 예제를 봅시다. 하지만 그전에! iOS에서 TableView를 한번이라도 만들어봤다면, 이 프로토콜에서의 Optional이 뭔지 감이 올 거에요!
TableView가 아니라도.. CollectionView나 PickerView라도 한번이라도 만들어보셨으면, 네가 해당 프로토콜을 준수하지 않았다고 에러가 났었죠? 예를들어 UITableViewDataSource프로토콜에는 다양한 메소드가 있는데, 우리는 여기서 딱 메소드 2개만 구현해주면 에러가 사라졌습니다.
그 이유는 반드시 구현해줘야만 하는 메소드는 optional이 아니라는 아주 간단한 이유죠.
그러니까!!!! 이렇게 앞에 optional이 붙어있으면, 제가 이 프로토콜을 채택해도 해당 메소드를 구현하지 않아도 되는것이죠.
그럼 우리도 optional프로토콜을 하나 만들어봅시다.
@objc protocol CounterDataSource {
@objc optional func increment(forCount count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
저 @objc를 붙혀주지 않으면 오류가 난답니다.
위에서 그랬죠?
"프로토콜과 선택적 요구사항은 모두 @objc 속성으로 표시되어야합니다."라구요.
자..우리는 메소드하나랑 프로퍼티 하나를 요구하고 있네요. 하지만 이 CounterDataSource가 요구하는 요구사항들은 필수가 아닙니다. 왜죠?!? optional이 붙어있기 때문입니다.
class Counter {
var count = 0
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.increment?(forCount: count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
}
}
}
자..그리고 Counter라는 클래스를 만들어주었는데요,
위에서 우리가 방금만든 CounterDataSource라는 프로토콜을 "채택"하진 않았어요.
하지만!!! dataSource라는 저장프로퍼티를 하나 만들어주었는데, CounterDataSource프로토콜 타입이네요!!! 우리 타입으로서의 프로토콜도 배웠었죠? 그러면 뭐라고 그랬죠?
이제 dataSource라는 저장프로퍼티는 CounterDataSource에 "접근"할 수 있게 됩니다.
더 봅시다.
@objc protocol CounterDataSource {
@objc optional func increment(forCount count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
func increment() {
if let amount = dataSource?.increment?(forCount: count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
}
}
위의 Counter클래스의 increment()메소드 부분만 가져온건데요.
자, 차근차근 보면 하나도 어렵지 않습니다.
dataSource는 우리가 만든 CounterDataSource프로토콜 타입의 저장프로퍼티였어요.
그럼 그말은!!!!!!!! CounterDataSource에서 우리가 정의한 메소드와 프로퍼티에 접근이 가능하게 된다는 것이죠.
increment메소드 안을 볼까요?
if let amount = dataSource?.increment?(forCount: count) {
count += amount
}
우리가 CounterDataSource에서 정의했던 increment(forCount:)메소드를 호출하고 있네요. 파라미터로 들어가는 count는 Couter에서 정의했던 저장프로퍼티입니다.
잠깐 보고 가야할 것이, dataSource?.increment? 이렇게 ?가 붙어있네요..왜그럴까요??
왜 그런지는 감이 오시죠? 먼저 dataSource는 애초에 CounterDataSource? 타입이었어요!
클래스에서 옵셔널 타입은 nil로 초기화되는 것 다들 아시죠? 일단 dataSource가 nil이 아닌 경우에만 increment(forCount:)를 호출해야하기 때문에 ?가 붙게 된거고, 이 dataSource 가 존재하더라도, increment(forCount:)를 구현한다는 보장이 없습니다. 왜냐면 optional 이었거든!! 구현해도 되고 안해도되는!!!!그러니까, increment(forCount:)이 존재하는 경우에만 이 if let구문이 실행되게 됩니다. 이것이 increment에 물음표(?)가 붙은 이유에요.
위에서 말한 "someOptionalMethod?(someArgument)와 같이 메소드가 호출될때 물음표를 작성하여 Optional 메소드의 구현을 확인합니다."가 이소리에요 :)
자 만약 위의 옵셔널 바인딩에서 nil을 받게 되면 다음 else구문으로 넘어가게 됩니다.
else if let amount = dataSource?.fixedIncrement {
count += amount
}
이번엔 CounterDataSource에 정의되어있던 프로퍼티를 호출하네요. get속성을 요구했던 fixedIncrement요! 여기서는 연산프로퍼티로 요구사항을 구현해주었습니다.
조금 이제 Counter라는 클래스가 이해가 가시죠?
그럼 Counter클래스가 하는일은 increment()메소드를 호출해서 자신의 count프로퍼티를 "증가"시키는 것 같네요.
class ThreeSource: NSObject, CounterDataSource {
let fixedIncrement = 3
}
그리고, ThreeSource라는 클래스를 정의했습니다. 일단 NSObject를 상속받고, CounterDataSource를 채택했네요!!!그럼 뭘 해야할까요. 일단 CounterDataSource가 optional들만 요구했으므로 ThreeSource는 CounterDataSource를 채택했음에도 불구하고 하나도 구현해주지 않아도 됩니다. 하지만 fixedIncrement라는 것을 구현했네요. 그것도 저장프로퍼티로요.
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
counter.increment()
print(counter.count)
}
자....여기서 잘 보셔야 합니다. counter라는 Counter클래스의 인스턴스를 하나 만들었어요.
Counter클래스에는 count라는 저장프로퍼티와(0으로 set되어있는) dataSource라는 CounterDataSource?타입의 저장프로퍼티와, increment라는 메소드가 있었어요.
근데, counter.dataSource = ThreeSource()라는 것을 해주었네요.
Counter의 클래스의 dataSource는 분명 CounterDataSource?타입이었죠? 근데 아까 만든 ThreeSource 클래스의 인스턴스로 만들어주었습니다. 이게 가능하냐? 가능합니다.
왜냐하면 ThreeSource는 CounterDataSource를 채택했기 때문이죠.
이제 for문을 돌게 되는데, increment()메소드를 호출합니다.
자.. increment()가 어떻게 이루어져있었죠?
func increment() {
if let amount = dataSource?.increment?(forCount: count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
}
}
만약 dataSource가 nil이 아니고, ==> 방금 dataSource를 ThreeSource()로 초기화 해주었기 때문에 nil이 아님.
increment(forCount :)가 구현이 되어있으면 count에 += amount를 해줍니다.
하지만 지금 CounterDataSource를 채택한 ThreeSource에, increment(forCount :)가 구현이 되어있나요? 아니었죠!!!
class ThreeSource: NSObject, CounterDataSource {
let fixedIncrement = 3
}
fixedIncrement만 구현이 되어있었어요. 그럼 increment(forCount :)가 없다는 소리이고, 이 if let구문은 nil을 반환하게 되어 다음으로 넘어가게 됩니다.
즉
else if let amount = dataSource?.fixedIncrement {
count += amount
}
이 else if let 구문을 실행하게 되는것이죠.
만약 dataSource가 nil이 아니고, ==> 방금 dataSource를 ThreeSource()로 초기화 해주었기 때문에 nil이 아님.
fixedIncrement가 있다면 count에 amount를 더해주는 것이었죠.
자..이 else if let구문은 실행되나요!!?
네 실행됩니다. 왜냐하면 fixedIncrement는 구현이 되어있거든요
fixedIncrement는 3이었으니 amount에는 3이 들어가게 됩니다.
그리고 최종적으로 count에 3을 더해주죠. count는 현재 0으로 set되어 있으니 이제 count의 값은 3이 될것입니다.
for _ in 1...4 {
counter.increment()
print(counter.count)//3 6 9 12
}
즉, counter의 count값은 3 6 9 12가 되겠죠.
이해가시죠?!?!?
자..이젠 조금 바꿔볼게요.
class ThreeSource: NSObject, CounterDataSource {
func increment(forCount count: Int) -> Int {
if count == 0 {
return 0
} else if count < 0 {
return 1
} else {
return -1
}
}
}
ThreeSource클래스는 아까 fixedIncrement만 구현해줬죠?
이번엔 fixedIncrement를 지우고, increment(forCount:)메소드를 구현해줬습니다.
var counter = Counter()
counter.count = -4
counter.dataSource = ThreeSource()
for _ in 1...5 {
counter.increment()
print(counter.count)
}
우리 방금 배웠으니까, 뭐가 print될지 생각해보세요.
생각해보셨나요?뭐가 print될까요?
잘 모르겠어도 괜찮습니다. 어떤값이 나오는지 차근차근 같이 봐요.
이번에는 counter의 count값을, 즉 Counter클래스의 count프로퍼티를 -4로 set해줬네요.
그리고 역시나 dataSource를 ThreeSource로 초기화해줍시다.
또 똑같이 for문을 돌면서 counter의 increment()메소드를 호출합니다.
자...
func increment() {
if let amount = dataSource?.increment?(forCount: count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
}
}
딱 처음 if let을 만납니다.
dataSource는 현재 nil이 아니죠? 우리가 ThreeSource()로 초기화시켜줬으니까요. 그리고 이제 여기에 increment(forCount:)메소드가 있는지 확인합니다. 근데!!!이번엔
class ThreeSource: NSObject, CounterDataSource {
func increment(forCount count: Int) -> Int {
if count == 0 {
return 0
} else if count < 0 {
return 1
} else {
return -1
}
}
}
이번엔 increment(forCount:)가 구현이 되어있네요!!!
그럼 실행하겠죠? 파라미터에는 Counter클래스의 count 프로퍼티가 들어갔었죠? 우리가 아까 -4로 set해준 그 프로퍼티요!!!
그럼 위 increment(forCount:)에서 count가 현재는 -4이게 됩니다.
count가 0보다 작죠?(-4<0)그럼 1을 리턴하네요.
그러면!!!!!!!!!!!!!!!!!
func increment() {
if let amount = dataSource?.increment?(forCount: count) {
count += amount
}
}
최종적으로 저 amount에는 방금 increment(forCount:)메소드가 반환한 1이 들어가게 됩니다.
그리고 현재 count에 1을 더해주네요.
어라 근데 현재 count가 -4였죠? 거기에 1을 더하면??
네! -3이 되겠네요.
이제 조금 어떻게 돌아가는지 아시겠죠?
다음도 현재 count가 -3이니 1을 반환할테고, -3+1인 -2가 출력되겠네요
이렇게 계속 하면!
for _ in 1...5 {
counter.increment()
print(counter.count)//-3 -2 -1 0 0
}
이러한 값들이 출력되게 됩니다. :)
조금 이해가 가시죠!?!?!??!!?!?!?!
다음으로 ~.~
Protocol Extensions
protocol Named{
var name: String { get }
}
자..Named라는 프로토콜을 정의했습니다. optional이 아닌 gettable 프로퍼티를 요구하므로 이 Named를 채택하는 클래스, 구조체, 열거형은 이 name이라는 프로퍼티를 꼭 구현해주어야 합니다.
그렇죠?!!?!?
근데..우리는 이 Named라는 프로토콜을 "확장(Extension)"할 수 있습니다.
extension Named{
var name: String {
return "Hello"
}
}
struct SomeStruct: Named{ }
그리고 구조체를 하나 만들어줬는데!!! Named라는 프로토콜을 채택했습니다.
원래같으면 여기서 에러가 나야죠. 아직 이 구조체에 Named가 요구하는 name이라는 것을 구현해주지 않았기 때문이죠.
하지만!!!!에러가 나지 않습니다. 왜냐하면 우리는 Named를 확장해서 이미 name을 구현해줬기 때문이죠 ㅎ
즉, 프로토콜 자체에서 동작을 정의할 수 있습니다.
오오
struct SomeStruct: Named{ }
var value = SomeStruct()
value.name//"Hello"
이렇게 아무 문제없이 name이라는 프로퍼티를 접근하고 값을 받아올 수 있습니다.
신기신기
Providing Default Implementations
Adding Constraints to Protocol Extensions
protocol Concatenate{
var name: String{ get }
}
프로토콜을 하나 만듭니다. 이름은 Concatenate. 뭔가를 합쳐주는 것 같아요.
name이라는 gettable프로퍼티를 요구합니다.
class SomeClass: Concatenate{
var nameStorage: String = ""
var name: String{
get{
return nameStorage
}
set{
nameStorage = newValue
}
}
}
그리고 클래스를 하나 만들어서 Concatenate를 채택해줍니다. 그러면 이 클래스 안에서는 name을 구현해줘야겠죠?
get만 요구했을 때는 원한다면 set도 될 수 있었죠? get과 set둘다 만들어주었습니다. set은 저장소가 있어야 하니 nameStorage라는 저장프로퍼티도 만들어주었습니다.
그리고 대망의 extension이 나옵니다.
extension Collection where Iterator.Element : Concatenate {
var concatenateString: String {
let itemsAsText = self.map { $0.name }
return itemsAsText.joined(separator: " ")
}
}
이미 Swift에 정의되어있는 Collection타입을 extension해주네요 근데!!!!!!!!!!!!!!!!!
근데 그 각각의 원소는 Concatenate프로토콜을 따르는 원소입니다. 이렇게 Constraint를 줄 수 있어요. where절을 이용해서요 :)
Q : Concatenate프로토콜에서 요구한 name 구현해줘야함?
A : ㄴㄴ. 채택이 아니라 그냥 제약사항을 준 것이기때문에 안해도댐
자..그래서 저는 concatenateString라는 연산프로퍼티를 하나 정의해줬어요. 그리고 이 concatenateString는 Collection의 원소들을 합쳐주는 역할을 합니다.
하지만 반드시 그 Collection은 Concatenate프로토콜을 채택하고 준수하는 프로토콜이어야 합니다.
예제를 볼까요?
var someName = SomeClass()
someName.name = "Martin"
var someName2 = SomeClass()
someName2.name = "Garrix"
var arr = [someName,someName2]
arr에 이 SomeClass의 인스턴스를 담아주었어요 :) 그럼 이 arr에 들어있는 원소들은 모두 Concatenate프로토콜을 따르고 있죠?
그러므로!!!!!
var arr = [someName,someName2]//"Martin Garrix"
arr.concatenateString
concatenateString이라는 연산프로퍼티를 호출할 수 있게 됩니다.
그럼 someName이 Martin이었고, someName2가 Garrix였으니 두 String을 합친 "Martin Garrix"가 리턴됩니다.
var anotherArray = ["David","Getta"]
anotherArray.concatenateString
이러면 뭐가 리턴될까요???
네!!! 실행이 되지 않고 에러가 나게 되죠!
왜냐하면 anotherArray에 들어있는 원소의 타입은 Concatenate프로토콜을 따르고 있지 않기 때문이에요!!!
조금 아시겠나요?
위 코드가 돌아가도록 하려면,
extension String: Concatenate{
var name: String{
return self
}
}
var anotherArray = ["David","Getta"]
anotherArray.concatenateString//"David Getta"
String을 extension해서 Concatenate프로토콜을 따르도록 하면 된답니다 XD
extension에 제약사항을 준다는게 무슨소리인지 아시겠나요? :)
이렇게!!!!!!!프로토콜이 끝났습니다..아 ㅎㅎㅎㅎㅎ
프로토콜글을 쓰면서 정말 많이 공부가 됐어요 :)
그리고!!! 애플예제도 사용했지만..제가 만든 예제도 많아요 그래서!!!! 틀린 부분이 있을 수 있습니다. 또 적절하지 않은 예도 있을 수 있겠네요..
혹시 발견하신다면 댓글이나 PC화면 오른쪽 하단에 있는 채널서비스를 이용해서 메세지 주시면 정말정말 감사하겠습니다 ㅠㅠㅠ
아무튼 오늘도 도움이 되었길 바래요 XD..
'Swift' 카테고리의 다른 글
Swift ) 왕초보를 위한 Codable / JSON Encoding and Decoding (9) | 2018.01.06 |
---|---|
Swift4 ) Swap / Law of Exclusivity (0) | 2017.12.21 |
Swift ) Protocols (3) (1) | 2017.12.15 |
Swift ) Unicode to Int/Int to Unicode (2) | 2017.12.14 |
Swift ) Nested Types (0) | 2017.12.06 |
- 제이슨 파싱
- IOS
- actor
- 피아노
- np-complete
- WidgetKit
- 회고
- ios 13
- 스위프트 문법
- iOS delegate
- WWDC
- SwiftUI
- Git
- FLUTTER
- swift delegate
- github
- Swift
- np-hard
- Xcode
- fastlane
- swift3
- swift sort
- UIBezierPath
- Accessibility
- swift tutorial
- swift array
- Combine
- WKWebView
- 스위프트
- swift 공부
- Total
- Today
- Yesterday