수행 내용

  • 활동 학습
    • 구조체와 클래스의 이니셜라이저
    • Notification Center
    • Key Value Observing
  • 쥬스메이커 프로젝트 Step 2 진행을 위한 사전 학습
    • UI 요소 배치

학습 내용

일반 사항

코드에 주석을 남기는 것에 대하여

  • 코드만으로도 모든 것이 이해할 수 있도록 작성하자
  • 주석이 필요하면 문서화 주석을 남기자
  • 문서화 주석을 남긴다면 코드를 수정할 때 함께 업데이트를 해주자
  • 코드를 업데이트 할 때 같이 업데이트 할 수 없다면 남기지 말자

지금 학습하는 것들에 대하여

  • 알고 있더라도 말로 표현할 수 있어야 한다.
  • 지금 전부 이해할 수 없더라도 차후에 되돌아 보자

구조체와 클래스의 이니셜라이저

구조체를 혹은 클래스를 설계한 후 실체가 된 것을 인스턴스라 하고, 이러한 타입과 연관된 값을 보관하는 곳 을 프로퍼티라 한다. 구조체와 클래스의 이니셜라이저는 이들의 프로퍼티가 옵셔널 타입이 아닌 한 인스턴스화할 때 값을 채워줄(초기화) 해줄 의무가 있다. 하지만 구조체 타입에 비해 클래스 타입을 설계하여 프로퍼티를 초기화하는 과정에서 에러를 맞닥뜨릴 경우가 많다.

구조체와 클래스는 프로퍼티를 초기화하는 과정 측면에서 보았을 때 어떤 차이가 있을까?

구조체 같은 경우 자동적으로 구조체 내부에서 프로퍼티를 초기화해주는 프로퍼티 이니셜라이저가 있어 커스터마이징이 필요한 이니셜라이저가 필요하지 않는 한 개발자가 별도로 구현하지 않아도 된다. 이것을 멤버와이즈 이니셜라이저라고 한다. 반면에 클래스는 이니셜라이저를 반드시 구현해주어야 프로퍼티가 초기화 된다.

그렇다면 왜 구조체는 멤버와이즈 이니셜라이저가 있고 클래스는 그렇지 않을까?

가장 큰 이유는 상속 가능 여부에 있다. 상속이 불가능한 구조체의 경우 앞으로 확장에 의해 초기화의 대상이 될 프로퍼티가 더 이상 발생하지 않음을 설계가 끝난 시점부터 확신할 수 있다. 반면에 클래스는 상속에 의해 초기화할 프로퍼티가 늘어날 수 있기 때문에 이니셜라이저를 개발자가 직접 작성하는 것이 프로그램 구동 측면에서 더 안전한 방식이라 할 수 있다. 참고자료

// 이니셜라이저가 없어도 문제 없음 (멤버와이즈 이니셜라이저 지원)
struct Person {
  var name: String
  var money: Int
  
//  init(name: String, money: Int)  // 이들이 없어도 문제 없음 (생략해도 됨)
//    self.name = name
//    self.money = money
  }
}

// 이니셜라이저가 있어야 함
class Student {
  var name: String
  var school: String
  
  init(name: String, money: Int) {
    self.name = name
    self.money = money
  }
}

Notification Center

앱 안에서 일어나는 이벤트를 감지하여 관심이 있다고 표현한 대상들에게 알려주는 방송국. 무언가를 구독하고 있는 상황에 빗대어 설명할 수 있다. 관심있는 대상의 행동(이벤트)을 구독한다고 Notification Center에 등록해두면 대상이 해당 행동을 한다고 알릴 때 (post) Notification Center가 해당 대상의 행동을 구독하고 있는 모두에게 알림을 주는 방식이다.

왜 Notification Center를 사용하나요?

한 번에 여러 인스턴스들에게 이벤트를 발송할 수 있기 때문입니다.

앱을 사용하며 시스템과 앱 내 변화, 사용자의 행동까지 발생하는 여러 이벤트를 구독할 수 있다. 사용자의 인터넷 연결 상태가 변하거나, 키보드가 올라오거나, 사용자가 터치를 하거나, 흔들거나, 아이폰이 감지할 수 있는 모든 요소가 구독의 대상이 될 수 있다. 아래는 캠퍼 세 명이 리더인 야곰을 따라가는(..) 예시이다.

import Foundation

class Stalker {
  let name: String
  init(name: String) {
    self.name = name
  }
  
  @objc func follow(notification: Notification) {
    print("\(name)이 yagom을 따라가요...")
    if let userInfo = notification.userInfo {
      print("부가정보: " + userInfo.description)
    }
    NotificationCenter.default.removeObserver(self) // 한 번 이벤트 구독하고 다음 번부터 안 받을래
  }
}

let ryan = Stalker(name: "라이언")

enum YagomNotification {
  static let didGoOutHome = NSNotification.Name.init("didYagomGoOutHome")
  static let willReturnHome = NSNotification.Name.init("willReturnHome")
}

NotificationCenter.default.addObserver(ryan, //누가 받을거야?
                                       selector: #selector(Stalker.follow(notification:)), // 받으면 뭐할거야?
                                       name: YagomNotification.didGoOutHome, // 어떤 이벤트를 구독할거야?
                                       object: nil)

NotificationCenter.default.post(name: YagomNotification.didGoOutHome,
                                object: nil,
                                userInfo: ["name": "yagom",
                                           "address": "Cheongju",
                                           "time": "2021-03-14 10:47:43"]) 
                                         // 이벤트 발생, userInfo로 부가정보를 딕셔너리 형태로 함께 보내줄 수 있음.
// prints "라이언이 yagom을 따라가요..."

Key Value Observing (KVO)

Notification Center와 달리 능동적으로 관심 대상이 어떻게 변했는지 지켜보는 방식. 비유하자면Notification Center에 알려달라고 하기보다 속성의 변화가 있는지 망원경으로 지켜보고 있는 방식이라 할 수 있다. Notification Center는 발신(post)을 해주어야 볼 수 있는데, KVO는 철저하게 능동적으로 동작하므로 이런 과정 없이 볼 수 있다.

import Foundation

class Idol: NSObject {
  var name: String
  @objc dynamic var money: Int
  
  init(name: String, money: Int) {
    self.name = name
    self.money = money
  }
}

class Fan: NSObject {
  var name: String
  
  init(name: String) {
    self.name = name
  }
}

let pengsu = Idol(name: "펭수", money: 1000000000)
let summer = Fan(name: "썸머")

pengsu.money = 0
pengsu.money = 10000
pengsu.money = 100

pengsu.observe(\.money, options: [NSKeyValueObservingOptions.new,
                                  NSKeyValueObservingOptions.old,
                                  NSKeyValueObservingOptions.initial]) { (idol,value) in
  print(idol)
  print(value)
}

고민한 점 / 문제점

UI 요소들을 등간격으로 배치하고 싶다.

화면 안의 요소들을 예쁘게 정렬하고 싶은데 화면 크기에도 대응하면서 등간격이 되게 만들 수 없을까하다 StackView라는 것을 활용해보았다. 자세한 내용은 다음 프로젝트에서 공부해도 된다고 하니 당장은 등간격 기능을 사용해서 UI 요소 배치에 들이는 시간을 줄이는데 활용했다.

같은 속성의 UI 요소에 동일한 스타일을 부여하는 방법은 없을까?

이번 프로젝트에서는 유독 버튼이 많은데, 버튼의 크기는 한정되어 있는 만큼 버튼에 들어갈 글자수가 많은 경우에 어쩔 수 없이 줄바꿈하여 글을 작성하여야할 경우가 있다. 줄바꿈되었을 때 버튼 안의 글자를 가운데 정렬하는 기능을 아직 storyboard에서 찾지 못하였기에 ViewController에서 각 버튼을 @IBOutlet 변수로 생성한 후 titleLabel?.textAlignment 프로퍼티를 .center로 적용하여 사용하고 있는데, 동일한 스타일 적용을 위해 동일한 코드를 반복하는게 맞나 하는 생각이 든다. (코드를 보면 전혀 아닌 것 같단 말이지 🧐...)

해결 방법

  • UI Library에서 Stack View를 스토리보드의 View에 드래그 앤 드랍 방식으로 놓고 Stack View에 넣어 정렬하고 싶은 요소들을 마찬가지로 드래그 앤 드랍하여 Stack View에 넣는다.
  • Stack ViewAttributes Inspector에서 Distribution 방식을 Equal Spacing으로 변경한 후 요소 간 간격을 Spacing에 기입해준다. 끝.
profile
합리적인 해법 찾기를 좋아합니다.

0개의 댓글