-
[TIL w3d4] Swift 심화문법 CLI 프로젝트 (Feat. 👩🏻🔧트러블 슈팅)iOS💖 2025. 3. 20. 23:27
[0] About Swift Grammar Master CLI
학습 기간: 3주차 (3월 17일 ~ 3월 20일)
https://github.com/amazingDove367/SpartaCamp-iOS/tree/main/%5BWeek2%5D%20SwiftGrammerMaster_CLI
SpartaCamp-iOS/[Week2] SwiftGrammerMaster_CLI at main · amazingDove367/SpartaCamp-iOS
deep dive ❤️🔥 into iOS. Contribute to amazingDove367/SpartaCamp-iOS development by creating an account on GitHub.
github.com
[1] 프로젝트 개요
1. 주제 : Swift 심화 문법 학습 및 CLI 기반 실습 프로젝트
2. 학습 개념 : 객체지향 프로그래밍(OOP), 프로토콜, 에러 처리, 제네릭, 클로저
[2] 회고🧐 (회고가 벌써부터 나와있는 이유는.. 회고하는걸 좋아해서.. ✍️)
내일배움캠프가 시작하기 전에는 iOS 강의를 들어오면서, 앱을 개발하는데 필요한 필수적인 Swift 문법만 학습했다.
그래서 뭔가 더 심화 문법 (옵셔널 체이닝, 고차함수) 이 이미 효율적이게 내장되어있는데도 제대로 사용하지 못하고 비효율적인 방법으로 직접 구현하고있었다. 기본기에 구멍이 슝슝 뚤려서, 뭔가 어려운것을 계속 쌓아도 계속 빠져나가는 느낌이었다.
지금껏 계속 Swift 문법을 온라인 강의로 들어왔지만, 학습하느라 바빠서, 간단한 예제만 작성하고 지나갔는데,
내배캠 3주차 과제로 심화문법 실습 덕분에, 지금껏 학습하고 아 나중에 써봐야지- 했던 제네릭, 고차함수, 클로저 자유자재 사용, 예외처리 (do-try-catch, throws-try?-try!) 능력을 실제적으로 기르고 훈련할 수 있는!! 너무나 나에게 딱맞는 도움이 되는 시간이었다. 🔥
[3] 학습 내용🤓
✔️ 일급 객체인 클로저 선언부터, 사용까지
➖ 클로저 변수
➖ 반환값으로 전달되는 클로저
➖ 함수의 파라미터로 클로저를 받고, 함수 내부에서 클로저 호출
✔️ 고차함수 자유자제 사용
➖ map, compactMap, filter, allSatify
➖ 고차함수 체이닝
: .enumurate() 함수를 통한, 고차함수 체이닝 속 $0.offset / $0.element 학습
➖ offset : index 와 같은 맥락,
➖ element : index 와 같은 맥락,
➖ 필요한 고차함수 직접 만들기
✔️ 제네릭 사용 컨벤션 재숙지, is / type(of:) 의 차이
➖ 제네릭의 선언과 사용 컨벤션 재숙지
➖ 제네릭 제한 (Numeric 프로토콜 준수)
➖ is / type(of: ) 의 차이와 사용 재숙지
[4] 👩🏻🔧 트러블 슈팅🛠️
1. 제네릭의 잘못된 사용 🙅🏻♀️ → 사용 컨벤션 재숙지후 리팩토링 🙆🏻♀️
2. type(of: ), is 잘못된 사용 🙅🏻♀️ → 개념 재숙지후 리팩토링 🙆🏻♀️
1. 제네릭의 잘못된 사용 🙅🏻♀️ → 사용 컨벤션 재숙지후 리팩토링 🙆🏻♀️
제네릭 함수의 선언과 사용방법에 대해, 다시 숙지하고 코드를 리팩토링했다.
[원인]
- 제네릭 함수를 사용하는 컨셉 자체를 잘못 이해했다.
- [컨셉 1] 제네릭 함수를 사용할때, 컴파일러가 타입을 추론할 수 있게 명시적인 매개변수를 입력해야한다.
- 그러나, 제네릭타입의 매개변수(배열)을 readLine() 으로 문자열로 받았다.
- 예) "[1,2,3,4]" , "["가", "나", "다", "라", "마"]"
- [컨셉 2] 제네릭을 사용한 함수를 작성할 때, 함수의 매개변수 타입을 제네릭으로 사용하는 것이 일반적이다.
- 그러나, 코드에서는 함수의 매개변수 타입이 제네릭이 아닌, String 으로 받았다.
- [컨셉 1] 제네릭 함수를 사용할때, 컴파일러가 타입을 추론할 수 있게 명시적인 매개변수를 입력해야한다.
- 매개변수가 제네릭 타입의 배열일때, Any 로 명시하지 않으면 타입을 혼합하여 매개변수로 줄 수 없다.
- → 컴파일 오류
- 문제에서는 테스트 입력을 '넣고' 확인라고 했으니, Any 타입까지 가지 않아도 된다.
그러나, 내가 학습을 위한 시도로 문자열로 받았을 때, 타입이 혼합된 배열 일 경우의 처리에는 사용 불가능하지는 않은 듯하다. - 그러나, Any 타입으로 주는 것 자체가, 제네릭 사용특성상 아주 잘 맞는다고 생각하지 못했다.
[해결 방법]
- 컴파일러가 타입을 추론할 수 있게 프롬프트 입력을 받지 않고, 명시적인 매개변수를 넣어줬다.
- 명시적 매개변수 삽입
- let intArray = [1, 2, 3, 4, 5]
print("\(intArray) -> \(d(intArray))")
- let intArray = [1, 2, 3, 4, 5]
- 명시적 매개변수 삽입
- 함수의 매개변수를 String 타입 → 제네릭 타입으로 변경했다.
- 변경 전
- c(_ arrayString: String)
- 변경 후
- c<T: Numeric>(_ array: [T])
- 변경 전
[리팩토링 전 코드 : 잘못된 제네릭 사용]
// 🔴 잘못된 사용 부분 : 제네릭 타입 함수 매개변수 // (_ arrayString: String) func c<T>(_ arrayString: String) throws -> [T] where T : LosslessStringConvertible { // String to [T] let array = arrayString.filter { $0.isNumber || $0.isLetter || $0 == "," } .split(separator: ",") .compactMap { T(String($0)) } let evenArray = array.enumerated() .filter { $0.offset.isMultiple(of: 2) } .compactMap { T(String($0.element)) } return evenArray } func runC() { // 제네릭 T 를 사용하여, 사용자 입력을 받을시 타입추론이 불가하여, 직접 입력값을 넣어주는 것으로 c() 를 사용하였습니다. do { let intInput = "[1, 2, 3, 4, 5]" let intArray: [Int] = try c(intInput) print("\(intInput) -> \(intArray)") let strInput = "[\"가\", \"나\", \"다\", \"라\", \"마\"]" let strArray: [String] = try c(strInput) print("\(strInput) -> \(strArray)") } catch { print("Error: runC()") } }[리팩토링 후 코드 : 제네릭 사용 컨벤션 재숙지]
// 🔴 리팩토링 부분 : 제네릭 타입 함수 매개변수 // (_ array: [T]) func c<T>(_ array: [T]) -> [T] { let evenArray = array.enumerated() .filter { $0.offset.isMultiple(of: 2) } .map { $0.element } return evenArray } func runC() { let intArray = [1, 2, 3, 4, 5] print("\(intArray) -> \(c(intArray))") let strArray = ["가", "나", "다", "라", "마"] print("\(strArray) -> \(c(strArray))") }2. type(of: ), is 잘못된 사용 🙅🏻♀️ → 개념 재숙지후 리팩토링 🙆🏻♀️
1번의 기본 제네릭 파라미터를 가진 함수에서, 제네릭 파라미터가 'Numeric 프로토콜'을 준수하는 타입의 배열로 변경하는 문제에서,
요구사항 외,명시적 매개변수 배열을 입력받고, 해당 배열의 제네릭 타입이 Numeric 프로토콜을 준수하는지 체크하고 싶었다.
type(of: )와 is의 정확한 사용목적을 알지 못해, 에러로 약간의 트러블이 있었다. 사용목적과 방법에 대해, 재숙지 후 리팩토링을 진행했다.[원인]
- type(of: ) 와 is 의 쓰임과 사용방법을 정확히 알지 못함
- 두 개념을 바꿔서 사용하고 있었다.
- type(of: ) 는, of: 다음에 들어가는 값에 대한 '타입 확인' 이다.
- is 는, 프로토콜을 준수하는지 확인하는 키워드다.
- 처음 작성할때, if type(of: 배열자체) is any Numeric.Type 이렇게 작성했다. 작성 방법이 잘못 됐다.
- 두 개념을 바꿔서 사용하고 있었다.
[해결]
- is 키워드만 사용
- .allSatisfy( 입력받은배열 is (any Numeric) )
- .allSatisfy() 고차함수를 사용
- 입력받은 배열에 대해, 배열 내부의 모든 값의 타입 (제네릭 T)이 Numeric 프로토콜을 준수하는지 확인
[💡]
근데 생각해보니 제네릭 타입 T 가 Any 로 사용하지 않는 이상, Int / Double / Float 과 같이 한 가지 타입으로만 가능하니
배열 내의 모든 값을 체크하지 않고, 하나만 Numeric 프로토콜을 준수하는지 확인해도 괜찮을 것 같다.
[리팩토링 전 코드 : 잘못된 type(of: ) 사용]
func d<T: Numeric>(_ array: [T]) -> [T] { let evenArray = array.enumerated() .filter { $0.offset.isMultiple(of: 2) } .map { $0.element } if evenArray.allSatisfy({ $0 is (any Numeric) }) { print("\(evenArray) conforms to Numeric") } else { print("\(evenArray) not conforms to Numeric") } // 🔴 잘못된 사용 부분 if type(of: evenArray) is any Numeric.Type { print("\($0) conforms to Numeric") } else { print("\($0) not conforms to Numeric") } }[리팩토링 후 코드 : is 사용으로 변경]
func d<T: Numeric>(_ array: [T]) -> [T] { let evenArray = array.enumerated() .filter { $0.offset.isMultiple(of: 2) } .map { $0.element } if evenArray.allSatisfy({ $0 is (any Numeric) }) { print("\(evenArray) conforms to Numeric") } else { print("\(evenArray) not conforms to Numeric") } // 🔴 리팩토링 한 부분 evenArray.forEach { if $0 is (any Numeric) { if type(of: evenArray) is any Numeric.Type print("\($0) conforms to Numeric") } else { print("\($0) not conforms to Numeric") } } }'iOS💖' 카테고리의 다른 글
[TIL w3d1] Swift 복습 (Optional, 중첩타입, 접근제어자, Protocol) (0) 2025.03.17 [TIL w2d5] Swift 복습 (Enum, Property 🌺) (0) 2025.03.17 [TIL w2d4] Swift CLI 숫자 야구게임 (Feat. 🛠️트러블 슈팅) (0) 2025.03.14 [TIL w2d3] 알고리즘 Swift로 다시시작 / Swift 복습 (Struct) (0) 2025.03.13 [TIL w2d2] 알고리즘 특강 / 야구 게임 만들기 (Lv. 1~3 / 6) (0) 2025.03.11 - 제네릭 함수를 사용하는 컨셉 자체를 잘못 이해했다.