[TIL] 프로그래밍 기초 주차 과제 : 계산기 앱 만들기 day.02

Emily·2024년 10월 29일
1

CalculatorApp

목록 보기
2/11

Playground : 필수 구현기능 Lv.2~3

  1. Lv.1에서 만든 Calculator 클래스에 나머지 연산(%) 추가하고 결과를 출력
  2. 오류가 날 수 있는 예외처리 상황에 대해 고민해보고, 구현하기
  3. AddOperation, SubstractOperation, MultiplyOperation, DivideOperation 클래스를 만들고 클래스 간의 관계를 고려하여 Calculator 클래스와 관계 맺기
  4. Calculator 클래스의 내부 코드를 변경 (관계를 맺은 후 필요하다면 별도로 만든 연산 클래스의 인스턴스를 Calculator 내부에서 사용)
  5. 클래스의 단일 책임 원칙에 대해 생각하기

Calculator 클래스에 나머지 연산 추가하기

어제 코드에 나머지 연산을 추가했다. 메소드를 호출할 때는 firstNumber, secondNumber 파라미터에 숫자를 받지만, 메소드 내에서 연산 시 ab로 간결하게 정의하고 싶어 전달인자 레이블을 사용했다.

나머지 연산의 경우 정수여야만 하기 때문에, 파라미터와 리턴 타입을 Int로 정의해야 했다.

Class의 단일 책임 원칙(SRP)

객체 지향 프로그래밍에서 단일 책임 원칙(Single Responsibility Principle)이란 모든 클래스는 하나의 책임(기능)만 가지도록 설계해야 함을 일컫는다. 클래스가 제공하는 모든 기능들이 해당 클래스의 책임에 부합해야한다는 것이다. 이는 설계의 응집력을 높이고자 함이다.

이것을 고려했을 때, 과제의 의도는 각 연산 클래스가 해당 연산 만을 담당하도록 하라는 것 같다.

다만, 3번 항목의 Calculator 클래스와 관계를 맺으라는데 이게 정확히 무슨 의미인지 잘 모르겠다. 4번에 관계를 맺은 후 인스턴스를 생성하여 사용하라고 하는데, 나는 인스턴스 생성하여 사용하는 자체가 관계 형성이라고 생각했는데 글은 그 외의 별도 조치가 필요하다는 말로 읽혀 그게 뭔지 모르겠어서 혼란스럽다. 일단 내가 할 수 있는 범위 내에서 코드 수정을 진행했다.

Calculator 내의 사칙연산 함수는 일단 주석처리 하고, 각 연산 클래스로 메소드들을 옮긴 뒤 Calculator에서 인스턴스를 통해 각 연산 메소드에 접근하여 계산 기능을 수행했다.

구현 요구사항에 대해 100% 이해한 게 아니라서 그런지, 코드를 업데이트 하고도 뭔가 부족한 기분이 계속 든다. Lv.4까지 진행하다보면 생각이 더 확장되면서 이해할 수 있을지도 모르겠다. 오늘은 일단 여기까지.

Xcode

[Chapter. 3] Auto Layout 적용하기

  1. 버튼 19개를 넣을 영역인 UIView ButtonArea의 높이를 전체 높이의 60%로 지정
  2. Buttonwidth=height = (화면 전체 너비 - 양 끝으로부터의 간격 16.0 x 2 - 버튼 간 간격 8.0 x 3) / 4

Combine을 활용해 SceneDelegate에서 windowScene의 화면 size를 Publisher에 주입한다.
Publisher인 화면 size 변수를 구독하여 각 UI의 size를 지정한다. 코드는 아래와 같다.

import Combine

// view model에 생성한 Publisher
final class MainViewModel {
	// shared 선언
    static let shared = MainViewModel()

	// screen size를 Tuple로 갖는 Subject(Combine Publisher)
	// default size : iPhone 16 Pro : 402 * 874
    let screen = CurrentValueSubject<(width: CGFloat, height: CGFloat), Never>((402, 874))
}
// SceneDelegate 내 willConnectTo 함수 코드
guard let windowScene = scene as? UIWindowScene else { return }

// screen 크기 전송 : send 메소드
let vm = MainViewModel.shared
vm.screen.send((width: windowScene.screen.bounds.width, height: windowScene.screen.bounds.height))
// MainViewController, Button class에서 view model의 screen을 구독해 각 UI 크기 지정
class MainViewController: UIViewController {
    
    private var cancellables = Set<AnyCancellable>()
    
    let vm = MainViewModel.shared
    
    // ... //
    
    // view did load에 호출되도록 한다.
    private func setButtonAreaHeight() {
    	// ButtonArea 높이 = 화면 전체 높이의 60%
        vm.screen
            .sink { [weak self] screen in
                self?.buttonArea.heightAnchor.constraint(equalToConstant: screen.height * 0.6).isActive = true
            }
            .store(in: &cancellables)
    }
}

class Button: UIButton {
	private setButtonSize() {
    	// button width = (screen.width - 16 * 2 - 8 * 3) / 4
        vm.screen
            .sink { [weak self] screen in
                self?.widthAnchor.constraint(equalToConstant: (screen.width - 56) / 4).isActive = true
                self?.heightAnchor.constraint(equalToConstant: (screen.width - 56) / 4).isActive = true
            }
            .store(in: &cancellables)
    }
}


size가 적용되어 button이 적절하게 배치된 것을 확인할 수 있다.

트러블 슈팅

오토레이아웃이 잘 적용되었는지 iPhone 16 Pro Max로 확인했는데, 제대로 적용되지 않아 있었다.

원인은 Publisher TypeCurrentValueSubject로 하여 기본값에 iPhone 16 Pro 크기를 지정해둔 데에 있었다.
send 동작 시점과 sink 동작 시점이 맞지 않나보다. (이걸 100% 이해하기 위해서는 Combine에 대한 공부가 더 필요하겠다.

문제 해결

기본값이 없는 PassthroughSubject로 바꾸어 windowScene을 통해 화면 크기가 send된 뒤에 레이아웃이 잡히도록 하였다.

let screen = PassthroughSubject<(width: CGFloat, height: CGFloat), Never>()


Pro Max에도 오토레이아웃이 제대로 적용된 것을 확인할 수 있다.
버튼이 완전한 원이 아닌데, 추후에 cornerRadius 값도 구독을 통해 지정해야겠다.

profile
iOS Junior Developer

1개의 댓글

comment-user-thumbnail
2024년 10월 29일

나머지 연산 저렇게도 되는구나... 머시ㄸr

답글 달기