{"id":16279392,"url":"https://github.com/cozzin/ios-study-note","last_synced_at":"2025-04-08T17:42:48.007Z","repository":{"id":101129248,"uuid":"404509517","full_name":"cozzin/ios-study-note","owner":"cozzin","description":"🧐 부스트캠프 iOS 리뷰어 활동 내용 정리","archived":false,"fork":false,"pushed_at":"2021-09-29T13:04:57.000Z","size":47,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-14T13:50:43.952Z","etag":null,"topics":["boostcamp-ios","ios","oop","swift"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cozzin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-09-08T22:10:50.000Z","updated_at":"2023-05-26T03:47:44.000Z","dependencies_parsed_at":"2023-05-24T17:00:18.861Z","dependency_job_id":null,"html_url":"https://github.com/cozzin/ios-study-note","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cozzin%2Fios-study-note","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cozzin%2Fios-study-note/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cozzin%2Fios-study-note/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cozzin%2Fios-study-note/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cozzin","download_url":"https://codeload.github.com/cozzin/ios-study-note/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247895699,"owners_count":21014366,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["boostcamp-ios","ios","oop","swift"],"created_at":"2024-10-10T19:00:55.073Z","updated_at":"2025-04-08T17:42:47.990Z","avatar_url":"https://github.com/cozzin.png","language":null,"readme":"# iOS Study Note\n부스트캠프 iOS 6기 리뷰어 진행하면서 내용 업데이트 중 입니다.\n\n# 공식문서\n* [API Design Guidelines](https://swift.org/documentation/api-design-guidelines/)\n* [About App Development with UIKit](https://developer.apple.com/documentation/uikit/about_app_development_with_uikit)\n* [App and Environment](https://developer.apple.com/documentation/uikit/app_and_environment)\n\n# OOP\n## 명령-쿼리 분리\n- `Query`: 질문해서 답을 요청\n- `Command `: 변경을 요청\n\n코드를 사용하는 입장에서 Query - Command 가 분리되지 않는다면 혼란을 겪을 수 있습니다.\n단순한 예를 들어보면\n\n```swift\nfunc isEmpty() -\u003e Bool {\n    count += 1\n    return myArray.isEmpty\n}\n```\n\nisEmpty()를 호출한 사용자는 count가 증가되는 것을 인지하지 못할 수 있습니다.\n만약에 count가 증가한다는 것을 염두해두고 있더라도\n`1. empty 인가?` + `2. count가 증가하겠군` 으로 생각해야해서 코드를 이해하고 유지보수 하는데 어려움을 겪을 수 있습니다.\n\n위의 코드를 나눠보면 아래와 같이 작성할 수 있습니다.\n```swift\nfunc isEmpty() -\u003e Bool {\n    return myArray.isEmpty\n}\n\nfunc increase() {\n    count += 1\n}\n```\n\n상황에 따라 이게 번거로울 수는 있는데, 유지보수에 큰 도움이 된다고 생각합니다.\n\n# 디자인패턴\n## 팩토리 패턴\n### 심플 팩토리\n파라미터로 주어진 type에 따라 객체를 다르게 생성\nhttp://ufx.kr/blog/191\n\n### 추상 팩토리\n상황에 따라 팩토리를 갈아끼우면서 결과물을 다르게 생성\nhttps://johngrib.github.io/wiki/abstract-factory-pattern/\n\n## Mediator\n\nhttps://ko.wikipedia.org/wiki/%EC%A4%91%EC%9E%AC%EC%9E%90_%ED%8C%A8%ED%84%B4\n\n# Architecture\n## MVC\n![image](https://user-images.githubusercontent.com/11647461/132593358-187369d3-0dd4-4042-9644-043a0f508eae.png)\n\n### 역할분리\n* Model: 비즈니스로직 처리. 데이터/알고리즘/네트워킹\n* View: 디스플레이/이벤트 캡쳐/비주얼 표현\n* Controller: 조합/델리게이션/특이한 작업\n\n### Loose Coupling 지향\n* 메세지를 보낼 때 MVC 계층을 건너띄지 않기\n* 하나의 오브젝트에 MVC 역할을 섞지 않기\n* 뷰 클래스에서 모델 데이터를 선언하지 않기\n\n### 메세지 관리\n* 모델끼리 직접적인 양방향 데이터 전송 X\n* 모델에서 Notify 할 Controller가 여러개일 때 Key-Value Observing 사용\n\n### 출처\n* https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html\n* 코드스쿼드 학습자료\n\n## MVP\n![image](https://user-images.githubusercontent.com/11647461/132594410-2750da4c-7268-4077-a1dd-e9fa277b9dbb.png)\n\n### 역할분리\n* Model: 비즈니스로직 처리\n* View: UIKit 요소들. UIView, UIViewController\n* Presenter: Model, View 사이의 Mediator.\n\n### MVC와 다른점\n* MVC와는 다르게 Presenter가 View를 참조하지 않는다.\n* UIView를 Presenter가 직접 다루지 않고 Delegate를 통해서 View 업데이트가 필요할 때 알려주는 방법\n\n### 출처\n* https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52\n* https://velog.io/@chagmn/iOS-Architecture-iOS-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%ED%8C%A8%ED%84%B4-2-MVP\n\n# Swift\n## Access Control\nhttps://docs.swift.org/swift-book/LanguageGuide/AccessControl.html\n\n## Lazy Stored Properties\nview가 load된 후에 특정 객체를 생성해서 사용하고 싶은 경우 Optional Type 사용 + viewDidLoad 에서 할당해서 사용하는 경우가 많음\n```swift\nfinal class ViewController: UIViewController {\n    private var model: Model?\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        model = Model()\n    }\n\n    func useModel() {\n        model?.use()\n    }\n}\n```\n\n`lazy var`를 사용하면 Optional Type이 아니고 기존 처럼 필요한 시점에 init 됨.\n\n```swift\nfinal class ViewController: UIViewController {\n    private lazy var model: Model = Model()\n\n    func useModel() {\n        model.use()\n    }\n}\n```\n\n## Enum\n### Associated Values\nenum에 값을 담아서 표현할 수 있음\n```swift\nenum Barcode {\n    case upc(Int, Int, Int, Int)\n    case qrCode(String)\n}\n```\n\n패턴 매칭해서 case를 나누고 Associated Values를 가져올 수 있음\n```swift\nswitch productBarcode {\ncase .upc(let numberSystem, let manufacturer, let product, let check):\n    print(\"UPC: \\(numberSystem), \\(manufacturer), \\(product), \\(check).\")\ncase .qrCode(let productCode):\n    print(\"QR code: \\(productCode).\")\n}\n// Prints \"QR code: ABCDEFGHIJKLMNOP.\"\n```\n\nhttps://docs.swift.org/swift-book/LanguageGuide/Enumerations.html\n\n## 고차함수\n* 매칭되는 첫번째 Element 가져오기: https://developer.apple.com/documentation/swift/array/1848165-first\n* 매칭되는 Element 존재 여부: https://developer.apple.com/documentation/swift/array/2297359-contains\n\n## Delegation\n* 이벤트가 발생한 객체를 같이 담아서 보내주면 delegate 구현할 때 편리함\n* `delegate?.doSomething(self, additional:)` 이런식으로 호출함\n\n```swift\nprotocol DiceGame {\n    var dice: Dice { get }\n    func play()\n}\nprotocol DiceGameDelegate: AnyObject {\n    func gameDidStart(_ game: DiceGame)\n    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)\n    func gameDidEnd(_ game: DiceGame)\n}\n```\n\n```swift\nclass SnakesAndLadders: DiceGame {\n    let finalSquare = 25\n    let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())\n    var square = 0\n    var board: [Int]\n    init() {\n        board = Array(repeating: 0, count: finalSquare + 1)\n        board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02\n        board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08\n    }\n    weak var delegate: DiceGameDelegate?\n    func play() {\n        square = 0\n        delegate?.gameDidStart(self)\n        gameLoop: while square != finalSquare {\n            let diceRoll = dice.roll()\n            delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)\n            switch square + diceRoll {\n            case finalSquare:\n                break gameLoop\n            case let newSquare where newSquare \u003e finalSquare:\n                continue gameLoop\n            default:\n                square += diceRoll\n                square += board[square]\n            }\n        }\n        delegate?.gameDidEnd(self)\n    }\n}\n```\nhttps://docs.swift.org/swift-book/LanguageGuide/Protocols.html\n\n## Naming\n\nType, Procotocl은 UpperCamelCase, 나머지는 lowerCamelCase\n![image](https://user-images.githubusercontent.com/11647461/135270908-6eacd780-6f81-4a24-ae14-da9a5c4c57fd.png)\nhttps://swift.org/documentation/api-design-guidelines/\n\n## Weak, Unowned\n\nhttps://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html\n\n# Foundation\n## NotificationCenter\n### [addObserver(_:selector:name:object:)](https://developer.apple.com/documentation/foundation/notificationcenter/1415360-addobserver)\nobject 파라미터의 역할: 특정 객체로부터 발생한 노티만 필터해서 수신. 만약 nil 이면 필터 없이 수신. 필터링이 필요 없으면 nil로 사용 가능.\n\n\u003e `anObject`\nThe object that sends notifications to the observer. Specify a notification sender to deliver only notifications from this sender.\n\u003e\n\u003eWhen nil, the notification center doesn’t use sender names as criteria for delivery.\n\n### [post(name:object:userInfo:)](https://developer.apple.com/documentation/foundation/notificationcenter/1410608-post)\nobject 파라미터의 역할: 노티를 발생시킨 객체를 전달. (sender 개념). 수신하는 쪽에서 구분이 필요 없으면 nil 전달해도 정상작동함.\n\n \u003e `anObject`\nThe object posting the notification.\n\n### object 전달하는 예제\n\n```swift\nextension NSNotification.Name {\n    static let modelDidUpdateTitle: NSNotification.Name = .init(\"didUpdateTitle\")\n}\n```\n\n```swift\nclass ViewController: UIViewController {\n\n    private lazy var model: Model = Model()\n\n    override func viewDidLoad() {\n        NotificationCenter.default.addObserver(self, selector: #selector(didUpdateTitle(_:)), name: .modelDidUpdateTitle, object: model)\n    }\n\n    @objc private func didUpdateTitle(_ notification: Notification) {\n        guard let updatedTitle = notification.userInfo?[\"updatedTitle\"] as? String else {\n            return\n        }\n        // 추가 작업 ...\n    }\n\n    private func updateTitle() {\n        model.update(title: \"testTitle\")\n    }\n}\n```\n\n```swift\nfinal class Model {\n    private var title: String = \"\"\n\n    func update(title: String) {\n        self.title = title\n        NotificationCenter.default.post(name: .modelDidUpdateTitle, object: nil, userInfo: [\"updatedTitle\": title])\n    }\n}\n```\n\n### object에 struct를 넣으면 작동하지 않음\n- 이유: https://stackoverflow.com/a/56826646\n- objective-c 시절에 만들어진 API 인데 `id` 타입을 Swift로 가져오다보니 `Any`로 포팅됨.\n- 실제로는 `class`를 전달해줘야함\n\n# UIKit\n## UITouch\n* 터치한 View 가져오기: https://developer.apple.com/documentation/uikit/uitouch/1618109-view\n\n## UIGestureRecognizer\n* [UIGestureRecognizerDelegate](https://developer.apple.com/documentation/uikit/uigesturerecognizerdelegate) 통해서 이벤트 조작 가능\n\n## IBInspectable\n\nIBDesignable + IBInspectable 사용하면 인터페이스 빌더에서 Layer 속성도 간편하게 변경 가능\n\n![image](https://user-images.githubusercontent.com/11647461/132595579-bdb4d92a-96e9-4564-8cf8-1e5cbfd5b286.png)\n\n```Swift\nimport UIKit\n\n@IBDesignable extension UIView {\n    @IBInspectable var borderColor: UIColor? {\n        set {\n            layer.borderColor = newValue?.cgColor\n        }\n        get {\n            guard let color = layer.borderColor else {\n                return nil\n            }\n            return UIColor(cgColor: color)\n        }\n    }\n    @IBInspectable var borderWidth: CGFloat {\n        set {\n            layer.borderWidth = newValue\n        }\n        get {\n            return layer.borderWidth\n        }\n    }\n    @IBInspectable var cornerRadius: CGFloat {\n        set {\n            layer.cornerRadius = newValue\n            clipsToBounds = newValue \u003e 0\n        }\n        get {\n            return layer.cornerRadius\n        }\n    }\n}\n```\n\nhttps://stackoverflow.com/a/35372610\n\n## UIGraphicsImageRenderer\n* 이미지를 직접 그릴 때 `UIGraphicsBeginImageContextWithOptions` / `UIGraphicsEndImageContext`로 Context를 열고 닫아줘야 함\n\n```swift\nlet rect = CGRect(origin: .zero, size: size)\nUIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)\nUIColor.darkGray.setFill()\nUIRectFill(CGRect(x: 1, y: 1, width: 140, height: 140))\nlet image = UIGraphicsGetImageFromCurrentImageContext()\nUIGraphicsEndImageContext()\n```\n\n* `UIGraphicsImageRenderer`를 사용하면 클로저 안에서 작업을 수행하고 따로 열고 닫는 함수 호출이 필요없는 장점이 있음: 가독성 증가, 실수하지 않을 수 있음\n\n```swift\nlet renderer = UIGraphicsImageRenderer(size: CGSize(width: 200, height: 200))\nlet image = renderer.image { (context) in\n  UIColor.darkGray.setFill()\n  context.fill(CGRect(x: 1, y: 1, width: 140, height: 140))\n}\n```\n\nhttps://developer.apple.com/documentation/uikit/uigraphicsimagerenderer\n\n# Networking\n\n## Http 통신\n* [NSAllowsArbitraryLoads](https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity/nsallowsarbitraryloads): 모든 도메인 HTTP 통신 허용\n* [NSExceptionDomains](https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity/nsexceptiondomains): 특정 도메인만 HTTP 통신 허용\n\n## Throttle\n- 엄밀히 말하면 네트워크는 아니지만 네트워크 기능과 밀접해서 사용하는 경우가 많음\nhttps://eunjin3786.tistory.com/80\n\n# Xcode\n## Debugging\n\n- https://github.com/sujinnaljin/Improving_Productivity\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcozzin%2Fios-study-note","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcozzin%2Fios-study-note","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcozzin%2Fios-study-note/lists"}