diff --git a/README.md b/README.md index af78fa5..30fce97 100644 --- a/README.md +++ b/README.md @@ -1 +1,249 @@ -## 내일배움캠프 2주차 과제입니다. + +# [내배캠] Ch2. 프로그래밍 기초 주차 과제 풀이 + +## main.swift +문제에서 구현한 함수를 호출하는 파일입니다. + +각 과제는 대부분 클래스로 구현된 경우를 제외하고 구조체로 작성되었습니다. 해당하는 구조체를 생성하여 함수를 호출합니다. + +## 필수 문제 +### 4) 여러 타입에 "자기소개" 기능 부여하기 +각 객체의 고유 메서드를 호출하는 함수의 경우, 처음에는 `introducibleArr.enumerated().forEach`를 활용하였습니다. + +```swift +introducibleArr.enumerated().forEach { index, element in + switch index { + case 0: + let instance = element as? Robot + instance?.batteryCharge() + case 1: + let instance = element as? Cat + instance?.meow() + case 2: + let instance = element as? Dog + instance?.bark() + default: + break + } +} +``` + +그러나 위 방법은 인스턴스 자체의 타입보다 배열의 인덱스에 의존한다고 판단했습니다. +따라서 for-in 반복문과 `is` 연산자를 활용하여 인스턴스의 타입을 매칭하는 방법으로 수정하였습니다. + +## 도전 문제 +### 1) '자동차' 개념으로 객체 지향 설계하기 +**1️⃣ 커스텀 Engine 타입** + +처음에는 열거형 타입으로 정의했으나 제시문에서 'ElectricEngine 타입의 Engine 을 사용해야합니다.' 라는 언급이 있어 클래스로 바꾸었습니다. + +각 자식 클래스(`ElectricEngine` 등)는 엔진 타입을 내부에서 주어진 기본값으로 초기화합니다. + +열거형 `EngineType`은 `switchEngine(to:)` 함수에서 인자를 안전하게 입력받기 위해 남겨두었습니다. + +**2️⃣ HydrogenEngine** +```swift +class HydrogenEngine: Engine { + // 현재 작동 중인 엔진 + private var running: Engine = GasolineEngine() { + didSet { + print("현재 작동 중인 엔진이 \(oldValue.type)에서 \(running.type)으로 변경되었습니다.") + } + } +``` + +다른 엔진 자식 클래스와 달리 현재 작동중인 엔진을 의미하는 `running` 변수를 가집니다. + +`HybridCar`는 `HydrogenEngine`을 가지는데, `switchEngine(to:)`를 한다고 해서 `HybridCar.engine`이 `HydrogenEngine`이 아닌 완전히 다른 엔진으로 바뀌어서는 안된다고 생각했기 때문입니다. (그러면 더이상 하이브리드 차가 아니지 않을까라고 생각했습니다.) + +따라서 작동 중임을 나타내는 변수를 따로 정의하였습니다. + +`running` 변수는 엔진의 동작으로만 변경될 수 있도록 `private` 접근자를 사용하였습니다. +프로퍼티 옵저버를 통해 어떻게 변경되었는지도 알 수 있도록 하였습니다. + +```swift + // 엔진 전환 + func switchRunningEngine(to type: EngineType) { + if running.type == type { + print("이미 \(type) 엔진으로 작동 중입니다.") + return + } + + switch type { + case .electric: + running = ElectricEngine() + case .gasoline: + running = GasolineEngine() + case .hydrogen: + print("가솔린 / 전기 엔진 중 하나로만 전환 가능합니다.") + } + } +} +``` + +(자동차의 실제 작동 원리는 모르지만) +작동 중인 엔진을 실질적으로 변경시키는 것은 `HybridCar`가 아닌 `HybridEngine`이라고 생각했기 때문에 본 클래스 내에서 `running`을 변경시키는 `func switchRunningEngine(to type:)`을 구현하였습니다. + +**3️⃣HybridCar** + +```swift +class HybridCar: Car { + func switchEngine(to type: EngineType) { + let hybridEngine = engine as! HydrogenEngine + hybridEngine.switchRunningEngine(to: type) + } +} +``` + +문제에서 요구한 `func switchEngine(to:)`는 위에서 언급했듯이 실질적인 동작은 엔진이 해야한다고 생각했으므로 `HybridEngine.switchRunningEngine(to:)`를 동작시키도록 구현하였습니다. + +`Car` 클래스의 `engine` 변수는 `Engine`타입이므로 메소드를 사용하기 위해 `HydrogenEngine`으로 다운캐스팅 해주었습니다. + +### 2) 제네릭 구조체 +기존에는 구조체 자체에서 제네릭 타입에 프로토콜을 제한했습니다. + +```swift +struct SortableBox { + var items: [T] + + mutating func sortItems() { + items.sort(by: <) + } +} +``` + +그러나 위 방법은 `SortableBox` 인스턴스 생성 당시 제네릭 타입이 `Comparable` 프로토콜을 준수하고 있지 않으면 바로 오류가 발생하게 됩니다. + +문제에서는 '타입 T가 `Comparable`을 준수할 때만 메서드 사용 가능', '`Comparable`을 따르지 않을 경우 `sortItems()` 호출 시 컴파일 오류 발생'이라는 조건을 제시합니다. + +즉, 위 코드는 컴파일 오류 시점이 '생성 당시'이므로 문제의 '호출 시'와는 상이합니다. + +튜터님의 조언으로 where 조건을 사용하여 해결되었습니다. + +```swift +struct SortableBox { + var items: [T] +} + +extension SortableBox where T: Comparable { + mutating func sortItems() { + items.sort(by: <) + } +} +``` + +→ 타입 T가 `Comparable`을 준수하지 않으면 `sortItems()` 메소드는 보이지 않습니다. + +위 패턴은 매우 다양하게 사용될 수 있으므로 꼭 기억해두겠습니다! + +### 4) 순환 참조 +**⚠️ 순환 참조 생성시 발견한 사소한(?) 오류** + +```swift +var a: A? +var b: B? + +a = A(name: "personA", home: nil) +b = B(name: "apartmentB", tenant: nil) + +a?.home = b +b?.tenant = a + +a = nil +b = nil +``` +→ 의도한 대로 순환참조 발생 + +```swift +var a: A? = A(name: "personA", home: b) +var b: B? = B(name: "B", tenant: a) + +a = nil +b = nil +``` +→ `a`와 `b` 모두 메모리에서 해제됨 + +**🧐 둘다 동일한 방법으로 생성한 객체인데 왜 아래 방법은 메모리에서 해제되는가?** + +➡︎ ❗️ 두 번째 방법은 애초에 잘못된 코드였어요!! + +```swift +var a: A? = A(name: "personA", home: b) +``` + +→ `a`를 생성하는 시점에 `b`는 없습니다. 그래서 원래는 여기에서 오류가 나야하는데,, 탑레벨에서는 이유는 모르겠지만 오류가 나지않고 빌드가 됩니다... (함수에 넣어보면 오류가 발생합니다.) + +이때 `a.home`을 찍어보면 `nil`값으로 출력됩니다. + +즉, 애초에 `a`는 `b`를 참조하고 있지 않았던 것이죠. + +다음으로 생성된 `b`에서는 `a`를 참조하지만 `a`가 `nil`값으로 먼저 해제되기 때문에 `a`와 `b` 사이에 순환 참조가 발생하지 않았던 것이었습니다. + +실무에서는 이런 코드를 작성할 일 자체가 없을 것이라고 하셨지만.. 잘 살펴봐야겠습니다😵 + +**⚠️ 순환 참조 생성시 있었던 이상한 점..** + +위에서 있었던 작은 해프닝을 뒤로 하고 클로저의 순환 참조를 만들고 있었는데 제대로 구현한 것 같은데 또 메모리에서 해제가 되었습니다. + +```swift +var a: A? +var b: B? + +func circularRef() { + a = A(name: "personA", home: nil) + b = B(name: "apartmentB", tenant: nil) + + a?.home = b + b?.tenant = a + + b?.closure = { + print("\(a?.name)") + } +} + +circularRef() + +a = nil +b = nil +``` + +위에서 언급한 오류를 피하기 위해 인스턴스 선언과 `nil` 할당 외에는 함수 내에서 구현하였는데, 순환 참조가 생성되지 않고 계속 메모리에서 해제되었습니다. + +다만 `nil`할당을 없애고 코드 내에서 인스턴스를 선언 하였더니 순환 참조가 생성되었습니다. + +```swift +func circularRef() { + var a: A? + var b: B? + + ... + +} + +circularRef() +``` +→ 함수 내에서 인스턴스를 선언했기 때문에 함수의 실행이 종료되면 자동적으로 두 인스턴스가 사라집니다. 따라서 따로 `nil`값을 할당해줄 필요가 없습니다. + +~~이렇게 해결하긴 했지만.. 튜터님 말씀으로는 탑레벨에서 인스턴스에 직접 `nil`값을 할당해줄 때 컴파일러 내부에서 ARC에 관한 동작이 우리가 생각하는 것과는 다르게 흘러가는 듯하다고 합니다.~~ + +⇒ 탑레벨에서 전역변수로 선언될 때는! `a`가 아니라 (예시)Global이라는 전역변수가 담긴 무언가를 참조한다고 합니다! (실제로는 어디에 담기는지는 모르겠지만) 그래서 `a` 인스턴스 자체에는 참조 카운트가 올라가지 않기 때문에 `a = nil`을 할당해주었을 때 메모리에서 해제가 된다고 합니다!! + +```swift +var a: A? +var b: B? + +func circularRef() { + a = A(name: "personA", home: nil) + b = B(name: "apartmentB", tenant: nil) + + a?.home = b + b?.tenant = a + + + b?.closure = { [a] in // a를 참조하도록 명시 + print("\(a?.name)") + } + +} +``` +➡︎ 그래서 이렇게 클로저 내에서 `[a]`를 추가하여 `a` 인스턴스를 참조하도록 직접 명시하면 순환 참조가 발생합니다. diff --git "a/\353\263\200\354\230\210\353\246\260/.gitignore" "b/\353\263\200\354\230\210\353\246\260/.gitignore" new file mode 100644 index 0000000..ba8673d --- /dev/null +++ "b/\353\263\200\354\230\210\353\246\260/.gitignore" @@ -0,0 +1,67 @@ +.DS_Store +._.DS_Store +**/.DS_Store +**/._.DS_Store + +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output diff --git "a/\353\263\200\354\230\210\353\246\260/ch.2/ch.2.xcodeproj/project.pbxproj" "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2.xcodeproj/project.pbxproj" new file mode 100644 index 0000000..e9e257b --- /dev/null +++ "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2.xcodeproj/project.pbxproj" @@ -0,0 +1,296 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXCopyFilesBuildPhase section */ + F90B55B82F0CB945006420D1 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + F90B55BA2F0CB945006420D1 /* ch.2 */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ch.2; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + F90B55BC2F0CB945006420D1 /* ch.2 */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = ch.2; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + F90B55B72F0CB945006420D1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + F90B55B12F0CB945006420D1 = { + isa = PBXGroup; + children = ( + F90B55BC2F0CB945006420D1 /* ch.2 */, + F90B55BB2F0CB945006420D1 /* Products */, + ); + sourceTree = ""; + }; + F90B55BB2F0CB945006420D1 /* Products */ = { + isa = PBXGroup; + children = ( + F90B55BA2F0CB945006420D1 /* ch.2 */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + F90B55B92F0CB945006420D1 /* ch.2 */ = { + isa = PBXNativeTarget; + buildConfigurationList = F90B55C12F0CB945006420D1 /* Build configuration list for PBXNativeTarget "ch.2" */; + buildPhases = ( + F90B55B62F0CB945006420D1 /* Sources */, + F90B55B72F0CB945006420D1 /* Frameworks */, + F90B55B82F0CB945006420D1 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + F90B55BC2F0CB945006420D1 /* ch.2 */, + ); + name = ch.2; + packageProductDependencies = ( + ); + productName = ch.2; + productReference = F90B55BA2F0CB945006420D1 /* ch.2 */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + F90B55B22F0CB945006420D1 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1640; + LastUpgradeCheck = 1640; + TargetAttributes = { + F90B55B92F0CB945006420D1 = { + CreatedOnToolsVersion = 16.4; + }; + }; + }; + buildConfigurationList = F90B55B52F0CB945006420D1 /* Build configuration list for PBXProject "ch.2" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = F90B55B12F0CB945006420D1; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = F90B55BB2F0CB945006420D1 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + F90B55B92F0CB945006420D1 /* ch.2 */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + F90B55B62F0CB945006420D1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + F90B55BF2F0CB945006420D1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = M6HT23N3TY; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + F90B55C02F0CB945006420D1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = M6HT23N3TY; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + F90B55C22F0CB945006420D1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = M6HT23N3TY; + ENABLE_HARDENED_RUNTIME = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + F90B55C32F0CB945006420D1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = M6HT23N3TY; + ENABLE_HARDENED_RUNTIME = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + F90B55B52F0CB945006420D1 /* Build configuration list for PBXProject "ch.2" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F90B55BF2F0CB945006420D1 /* Debug */, + F90B55C02F0CB945006420D1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F90B55C12F0CB945006420D1 /* Build configuration list for PBXNativeTarget "ch.2" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F90B55C22F0CB945006420D1 /* Debug */, + F90B55C32F0CB945006420D1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = F90B55B22F0CB945006420D1 /* Project object */; +} diff --git "a/\353\263\200\354\230\210\353\246\260/ch.2/ch.2.xcodeproj/project.xcworkspace/contents.xcworkspacedata" "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2.xcodeproj/project.xcworkspace/contents.xcworkspacedata" new file mode 100644 index 0000000..919434a --- /dev/null +++ "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2.xcodeproj/project.xcworkspace/contents.xcworkspacedata" @@ -0,0 +1,7 @@ + + + + + diff --git "a/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/main.swift" "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/main.swift" new file mode 100644 index 0000000..0a5a5c4 --- /dev/null +++ "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/main.swift" @@ -0,0 +1,191 @@ +// +// main.swift +// ch.2 +// +// Created by 변예린 on 1/6/26. +// + +import Foundation + +//MARK: 필수 문제 + +// 1. +// - sum 호출 코드 +print(Required1().sum(3, 5)) + +// - calculate 호출 코드 +Required1().calculate(3, 5, Required1().sum) + + +// 2. +// - 고차함수 체이닝 호출 코드 +print(Required2().mapChain([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])) + +// - myMap 호출 코드 +print(Required2().myMap([1, 2, 3]) { String($0) }) + + +// 3. +// - 함수 a 호출 코드 +print(Required3().a([1, 2, 3, 4, 5])) + +// - 함수 b 호출 코드 +print(Required3().b(["가", "나", "다", "라", "마"])) + +// - 함수 c 호출 코드 +print(Required3().c([1, 2, 3, 4, 5])) +print(Required3().c(["가", "나", "다", "라", "마"])) + + +// - 함수 d 호출 코드 +// print(answer.required3.d(["가"])) // 오류 +print(Required3().d([1.3, 2.5, 3.0, 4.6, 5.6])) + + +// 4. +// - Robot.name 프로퍼티 옵저버 확인 +var robot = Robot(name: "로봇") +robot.name = "로보트" +robot.name = "로보트" // 문자열 출력 X + +// - [Introducible] 타입 배열 정의, 배열을 순회하며 고유 메서드 호출 +var introducibleArr: [Introducible] = [] + +introducibleArr.append(robot) +introducibleArr.append(Cat(name: "고양이")) +introducibleArr.append(Dog(name: "강아지")) + +// 기존 풀이 +//for element in introducibleArr { +// if element is Robot { +// let instance = element as? Robot +// instance?.batteryCharge() +// } else if element is Cat { +// let instance = element as? Cat +// instance?.meow() +// } else if element is Dog { +// let instance = element as? Dog +// instance?.bark() +// } else { +// continue +// } +//} + +// 수정 - 타입캐스팅과 옵셔널 바인딩을 한번에 작성하여 코드 수를 줄임 +// - 옵셔널 바인딩을 사용하여 옵셔널 없이 타입을 사용 가능 +introducibleArr.forEach { + if let robot = $0 as? Robot { + robot.batteryCharge() + } else if let cat = $0 as? Cat { + cat.meow() + } else if let dog = $0 as? Dog { + dog.bark() + } +} + +// - 튜터님 피드백 switch문: 옵셔널 바인딩하면서 다운캐스팅 -- 바로 고유메서드 사용 가능 +// default case를 통해 추후 새로운 타입 추가시 적절한 처리 가능 +introducibleArr.forEach { + switch $0 { + case let robot as Robot: + robot.batteryCharge() + case let cat as Cat: + cat.meow() + case let dog as Dog: + dog.bark() + default: + break + } +} + + + +// 5. +// - throwing function 호출 +do { + try print(Required5() + .predictDeliveryDay(for: "우리집", status: .notStarted)) +} catch DeliveryError.notstarted { + print("아직 배송이 시작되지 않았습니다.") +} catch DeliveryError.invalidAddress { + print("유효하지 않은 주소입니다. 주소를 다시 입력해주세요.") +} catch DeliveryError.systemError(reason: let reason) { + print("시스템 에러입니다. 사유: \(reason)") +} + + + +//MARK: 도전 문제 + +// 1. +// - ElectricCar +let electric = ElectricCar(brand: "테슬라", model: "Y", modelYear: "2026") + +electric.drive() +electric.stop() +electric.park() +print(electric.engine.type) + +// - HybridCar +let hybrid = HybridCar(brand: "기아", model: "셀토스", modelYear: "2026") + +hybrid.drive() +hybrid.switchEngine(to: .electric) +hybrid.switchEngine(to: .electric) +hybrid.switchEngine(to: .hydrogen) + +// - 상속을 사용하여 기능을 추가하는 것과, 프로토콜 채택을 통해서 기능을 추가하는 것의 장단점, 차이를 고민하고 주석으로 서술해주세요. +/* + A. 상속의 경우, 부모 클래스에 정의된 기능을 모두 사용할 수 있으며 재정의를 통해 커스텀할 수 있다는 장점이 있다. 그러나 상속 받은 클래스에는 필요 없는 부분까지 강제적으로 가져야한다는 단점이 있다. + 반면, 프로토콜은 프로토콜에 정의된 기능만 구현하면 된다는 장점이 있다. 클래스처럼 필요없는 기능까지 가지지 않으므로 공통된 몇 가지 기능(혹은 프로퍼티)만 가지길 원한다면 프로토콜이 유리하다. + 즉, 대부분 비슷하면서도 조금씩 차이를 갖는 경우라면 상속이, 공통적인 몇 가지만 여러 객체에서 가지길 원하는 경우라면 프로토콜을 사용하는 것이 좋다. + */ + + +// 2. +// - sortItems() +var comparableBox = SortableBox(items: [1, 60, 3, 8, 24]) +comparableBox.sortItems() +print(comparableBox.items) + +var incomparableBox = SortableBox(items: [electric, hybrid]) // T: Car +// incomparableBox.sortItems() // 컴파일 에러 발생 + + +// 3. +// - 기본 introduce() 동작 +let test = Test() +print(test.introduce()) + +// - Robot, Cat, Dog 타입의 커스텀 동작 +print(""" +\(Dog(name: "뽀삐").introduce()) +\(Cat(name: "삐용").introduce()) +\(Robot2(name: "메칸더V").introduce()) +""") + + +// 4. +// - 순환 참조 발생 및 해결하기 +func circularRef() { + var a: A? + var b: B? + + a = A(name: "personA", home: nil) + b = B(name: "apartmentB", tenant: nil) + + a?.home = b + b?.tenant = a + + // 클로저로 인한 순환 참조 발생 +// b?.closure = { +// print("\(a?.name)") +// } + + // 클로저 순환 참조 해결 + b?.closure = { [weak a] in + print("\(a?.name)") + } +} + +circularRef() diff --git "a/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\353\217\204\354\240\204 \352\263\274\354\240\234/\353\217\204\354\240\204 \353\254\270\354\240\234 1.swift" "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\353\217\204\354\240\204 \352\263\274\354\240\234/\353\217\204\354\240\204 \353\254\270\354\240\234 1.swift" new file mode 100644 index 0000000..d3c66be --- /dev/null +++ "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\353\217\204\354\240\204 \352\263\274\354\240\234/\353\217\204\354\240\204 \353\254\270\354\240\234 1.swift" @@ -0,0 +1,119 @@ +// +// 도전 문제 1.swift +// ch.2 +// +// Created by 변예린 on 1/8/26. +// + +import Foundation + +//MARK: 자동차 정의 +// Car 클래스 설계 +class Car { + let brand: String + let model: String + let modelYear: String + let engine: Engine + + init(brand: String, model: String, modelYear: String, engine: Engine) { + self.brand = brand + self.model = model + self.modelYear = modelYear + self.engine = engine + } + + // 접근 제어자와 함께 동작 정의 + internal func drive() { + print("[\(brand) \(model)] 주행 중...") + } + + internal func stop() { + print("[\(brand) \(model)] 정지") + } + + internal func park() { + print("[\(brand) \(model)] 주차") + } +} + +// Car를 상속한 ElectricCar 설계 +class ElectricCar: Car { + // ElectricEngine 타입의 Engine 사용 + init(brand: String, model: String, modelYear: String) { + super.init(brand: brand, model: model, modelYear: modelYear, engine: ElectricEngine()) + } +} + +// Car를 상속한 HybridCar 설계 +class HybridCar: Car { + // HydrogenEngine 타입의 Engine 사용 + init(brand: String, model: String, modelYear: String) { + super.init(brand: brand, model: model, modelYear: modelYear, engine: HydrogenEngine()) + } + + func switchEngine(to type: EngineType) { + let hybridEngine = engine as! HydrogenEngine + hybridEngine.switchRunningEngine(to: type) + } +} + +//MARK: 엔진 정의 +// 엔진 열거형 타입 정의 - switch 기능의 편리성을 위해 +enum EngineType { + case electric, gasoline, hydrogen +} + +// Engine 커스텀 타입 정의 +class Engine { + let type: EngineType + + init(_ type: EngineType) { + self.type = type + } +} + +// Electric Engine 타입 정의 +class ElectricEngine: Engine { + init() { + super.init(.electric) + } +} + +// Gasolin Engine 타입 정의 +class GasolineEngine: Engine { + init() { + super.init(.gasoline) + } +} + +// HydrogenEngine 정의 +class HydrogenEngine: Engine { + // 현재 작동 중인 엔진 + private var running: Engine = GasolineEngine() { + didSet { + print("현재 작동 중인 엔진이 \(oldValue.type)에서 \(running.type)으로 변경되었습니다.") + } + } + + init() { + super.init(.hydrogen) + } + + // 엔진 전환 + func switchRunningEngine(to type: EngineType) { + if running.type == type { + print("이미 \(type) 엔진으로 작동 중입니다.") + return + } + + switch type { + case .electric: + running = ElectricEngine() + case .gasoline: + running = GasolineEngine() + case .hydrogen: + print("가솔린 / 전기 엔진 중 하나로만 전환 가능합니다.") + } + } +} + diff --git "a/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\353\217\204\354\240\204 \352\263\274\354\240\234/\353\217\204\354\240\204 \353\254\270\354\240\234 2.swift" "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\353\217\204\354\240\204 \352\263\274\354\240\234/\353\217\204\354\240\204 \353\254\270\354\240\234 2.swift" new file mode 100644 index 0000000..508875e --- /dev/null +++ "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\353\217\204\354\240\204 \352\263\274\354\240\234/\353\217\204\354\240\204 \353\254\270\354\240\234 2.swift" @@ -0,0 +1,18 @@ +// +// 도전 문제 2.swift +// ch.2 +// +// Created by 변예린 on 1/8/26. +// + +import Foundation + +struct SortableBox { + var items: [T] +} + +extension SortableBox where T: Comparable { + mutating func sortItems() { + items.sort(by: <) + } +} diff --git "a/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\353\217\204\354\240\204 \352\263\274\354\240\234/\353\217\204\354\240\204 \353\254\270\354\240\234 3.swift" "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\353\217\204\354\240\204 \352\263\274\354\240\234/\353\217\204\354\240\204 \353\254\270\354\240\234 3.swift" new file mode 100644 index 0000000..32d3ddb --- /dev/null +++ "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\353\217\204\354\240\204 \352\263\274\354\240\234/\353\217\204\354\240\204 \353\254\270\354\240\234 3.swift" @@ -0,0 +1,45 @@ +// +// 도전 문제 3.swift +// ch.2 +// +// Created by 변예린 on 1/9/26. +// + +import Foundation + +extension Introducible { + func introduce() -> String { + return "안녕하세요, 저는 \(name)입니다." + } +} + +struct Test: Introducible { + var name = "Test" +} + +class Robot2: Introducible { + var name: String { + didSet { // 변경될 때마다 변경 이전값, 이후값 출력 + if oldValue != name { + print(""" + name 변경 알림 + 변경 이전 값: \(oldValue) + 변경 이후 값: \(name) + """) + } + } + } + + init(name: String) { + self.name = name + } + + func introduce() -> String { + return "삐빅- 저는 \(name)입니다." + } + + // 고유 메서드 + func batteryCharge() { + print("배터리 충전중...") + } +} diff --git "a/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\353\217\204\354\240\204 \352\263\274\354\240\234/\353\217\204\354\240\204 \353\254\270\354\240\234 4.swift" "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\353\217\204\354\240\204 \352\263\274\354\240\234/\353\217\204\354\240\204 \353\254\270\354\240\234 4.swift" new file mode 100644 index 0000000..d766da3 --- /dev/null +++ "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\353\217\204\354\240\204 \352\263\274\354\240\234/\353\217\204\354\240\204 \353\254\270\354\240\234 4.swift" @@ -0,0 +1,37 @@ +// +// 도전 문제 4.swift +// ch.2 +// +// Created by 변예린 on 1/9/26. +// + +import Foundation + +class A { + let name: String + var home: B? + + init(name: String, home: B?) { + self.name = name + self.home = home + } + + deinit { + print("\(name)가 메모리에서 해제되었습니다.") + } +} + +class B { + let name: String + weak var tenant: A? // var tenant: A? 로 작성 시 순환참조 발생 + var closure: (() -> Void)? + + init(name: String, tenant: A?) { + self.name = name + self.tenant = tenant + } + + deinit { + print("\(name)가 메모리에서 해제되었습니다.") + } +} diff --git "a/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 1.swift" "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 1.swift" new file mode 100644 index 0000000..c7a8f38 --- /dev/null +++ "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 1.swift" @@ -0,0 +1,20 @@ +// +// 필수 문제 1.swift +// ch.2 +// +// Created by 변예린 on 1/6/26. +// + +import Foundation + +struct Required1 { + // 두 개의 Int값을 파라미터로 받고, 하나의 String 값을 반환하는 클로저 설계 + let sum: (Int, Int) -> String = { + return "두 수의 합은 \($0 + $1) 입니다." + } + + // sum과 동일한 타입의 클로저를 파라미터로 받고, 반환 값이 없는 함수 + func calculate (_ num1: Int, _ num2: Int, _ operation: (Int, Int) -> String) { + print(operation(num1, num2)) + } +} diff --git "a/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 2.swift" "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 2.swift" new file mode 100644 index 0000000..1ed8c44 --- /dev/null +++ "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 2.swift" @@ -0,0 +1,30 @@ +// +// 필수 문제 2.swift +// ch.2 +// +// Created by 변예린 on 1/6/26. +// + +import Foundation + +struct Required2 { + // for-in 반복문 map 변환 + let numbers = [1, 2, 3, 4, 5] + lazy var result = numbers.map { String($0) } + + // 고차함수 체이닝 + func mapChain(_ arr: [Int]) -> [String] { + return arr.filter { $0 % 2 == 0 }.map { String($0) } + } + + // 고차함수 생성 + func myMap(_ arr: [Int], _ convert: (Int) -> String) -> [String] { + var result: [String] = [] + + for num in arr { + result.append(convert(num)) + } + + return result + } +} diff --git "a/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 3.swift" "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 3.swift" new file mode 100644 index 0000000..af77354 --- /dev/null +++ "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 3.swift" @@ -0,0 +1,47 @@ +// +// 필수 문제 3.swift +// ch.2 +// +// Created by 변예린 on 1/6/26. +// + +import Foundation + +struct Required3 { + // func a (_ arr: [Int]) -> [Int] { << 기존 코드 + // return arr.enumerated() + // .filter { $0.offset % 2 != 0) } + // .map { $0.element } + // } + + // Int 배열 짝수번째 요소 제거하여 반환 + // - filter 조건 수정 : 조건을 좀더 명확히 보이게 하기 위해 + func a (_ arr: [Int]) -> [Int] { + return arr.enumerated() + .filter { !$0.offset.isMultiple(of: 2) } + .map { $0.element } + } + + // String 배열 짝수번째 요소 제거하여 반환 + func b (_ arr: [String]) -> [String] { + return arr.enumerated() + .filter { !$0.offset.isMultiple(of: 2) } + .map { $0.element } + } + + // 두 함수 a, b를 대체하는 함수 + func c (_ arr: [T]) -> [T] { + return arr.enumerated() + .filter { !$0.offset.isMultiple(of: 2) } + .map { $0.element } + } + + // Numeric 프로토콜을 준수하는 타입을 파라미터로 받는 함수 + // - 굳이 다른 타입 이름을 사용할 필요가 없어 T로 수정 + func d (_ arr: [T]) -> [T] { + return arr.enumerated() + .filter { !$0.offset.isMultiple(of: 2) } + .map { $0.element } + } + +} diff --git "a/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 4.swift" "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 4.swift" new file mode 100644 index 0000000..f12cfe0 --- /dev/null +++ "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 4.swift" @@ -0,0 +1,80 @@ +// +// 필수 문제 4.swift +// ch.2 +// +// Created by 변예린 on 1/6/26. +// + +import Foundation + +// 프로토콜 정의 +protocol Introducible { + var name: String { get set } + + func introduce() -> String +} + +// Robot 타입 정의 +class Robot: Introducible { + var name: String { + didSet { // 변경될 때마다 변경 이전값, 이후값 출력 + if oldValue != name { + print(""" + name 변경 알림 + 변경 이전 값: \(oldValue) + 변경 이후 값: \(name) + """) + } + } + } + + init(name: String) { + self.name = name + } + + func introduce() -> String { + return "안녕하세요, 저는 \(name)입니다." + } + + // 고유 메서드 + func batteryCharge() { + print("배터리 충전중...") + } +} + +// Cat 타입 정의 +class Cat: Introducible { + var name: String + + init(name: String) { + self.name = name + } + + func introduce() -> String { + return "야옹, 저는 \(name)입니다." + } + + // 고유 메서드 + func meow() { + print("야옹") + } +} + +// Dog 타입 정의 +class Dog: Introducible { + var name: String + + init(name: String) { + self.name = name + } + + func introduce() -> String { + return "멍멍! 저는 \(name)입니다." + } + + // 고유 메서드 + func bark() { + print("멍멍!") + } +} + diff --git "a/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 5.swift" "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 5.swift" new file mode 100644 index 0000000..95254fe --- /dev/null +++ "b/\353\263\200\354\230\210\353\246\260/ch.2/ch.2/\355\225\204\354\210\230 \352\265\254\355\230\204 \352\263\274\354\240\234/\355\225\204\354\210\230 \353\254\270\354\240\234 5.swift" @@ -0,0 +1,44 @@ +// +// 필수 문제 5.swift +// ch.2 +// +// Created by 변예린 on 1/7/26. +// + +import Foundation + +struct Required5 { + // 요구 throwing function + func predictDeliveryDay(for address: String, status: DeliveryStatus) throws -> String { + // 주소가 잘못된 경우 + if address.isEmpty { + throw DeliveryError.invalidAddress + } + + // DeliveryStatus에 따른 분기 처리 + switch status { + case .notStarted: // 배송이 아직 시작되지 않은 경우 + throw DeliveryError.notstarted + + case .error: // 시스템 서버 에러로 예측이 불가능한 경우 + throw DeliveryError.systemError(reason: "서버 에러") + + case .inTransit(dayRemaining: let day): // 정상 배송 상태 + return "배송까지 \(day)일 남았습니다." + } + } +} + +// 배송 상태 열거형 +enum DeliveryStatus { + case notStarted + case inTransit(dayRemaining: Int) + case error +} + +// 에러 타입 열거형 +enum DeliveryError: Error { + case invalidAddress + case notstarted + case systemError(reason: String) +}