[Swift] 프로퍼티가 없는 객체? Singleton Pattern, Static function 그리고 Struct

JISU LEE·2021년 11월 28일
1

Swift

목록 보기
1/3
post-thumbnail

부스트캠프 마지막 프로젝트로 카카오버스 앱 클론을 결정했다. 지금은 사실 개발 막바지 무렵이고, 테스트나 리팩토링만을 남겨두고 있다.

미리 고민했어야 했던 부분이지만 테스트 가능한 구조에 대해 뒤늦게나마 고민하기 시작하면서, NetworkService 객체가 너무 많은 역할을 하고 있다는 것을 깨달았다.

그 과정에서 RequestFactory라는 객체를 만들기로 했다. RequestFactory는 아래와 같은 특징을 가지고 있었다.

하나의 함수만을 가지며 프로퍼티(=상태)를 가지지 않는다.

이러한 특징을 가진 객체를 구현할 시 가장 적절한 형태가 무엇일까에 대해 1시간 반동안 대화를 나눴다.

아래 내용은 팀간에 논의되었던 내용일 뿐, 이게 정답이라는 것은 절대 아니다.
긴 시간 논의했던 만큼 누군가에게는 도움이 될 수 있을 것 같아 정리한 것이니 참고만 해주셨으면 좋겠다.
혹시나 틀린 부분이 있다면 언제든 댓글로 남겨주시면 너무 감사할 것 같다🙏🏻

Singleton Pattern

맨 처음에 의논되었던 방식은 Singleton이였다. 객체를 굳이 여러개 생성할 필요 없이 하나의 객체를 공유하면 되지 않냐는 것이다. 하지만 나는 Singleton Pattern이 안티패턴이라는 점에서 Singleton Pattern의 사용을 지양해야한다고 생각했다.

Singleton Pattern을 사용하지 않은 이유

그래서 Singleton이 안티패턴인 이유, 그리고 단점에 대해 찾아보았다. [Structure] 싱글톤 패턴과 문제점 이라는 글을 많이 참고했다.

위 글에서는 주의해야할 점으로 상태를 가진 객체를 Singleton 으로 만들면 안된다는 것을 꼽는데, 우리는 상태가 없는 객체를 만드는 것이기 때문에 이에는 해당되지는 않았다.

그럼에도 Singleton Pattern을 사용하지 말아야할 이유는 명백했다.

  1. Singleton을 사용하는 곳과 Singleton Class 사이에 의존성이 생기게 된다. 이는 결합도를 높여 단위테스트의 어려움을 초래한다.
  2. 아무 객체나 자유롭게 접근하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 지양되어야 할 모델이다.

Singleton Pattern을 사용하는 경우?

그렇다면 든 의문은 어느 경우에 Singleton Pattern을 사용하는가 였다.

Swift에서 Singleton Pattern을 사용하는 대표적인 예는 NotificationCenter이다. 전체 프로젝트에서 하나의 객체가 공유되어야 하는 상황인 경우 Singleton을 사용했다.

우리의 상황과 비슷하면서도 Singleton을 사용하지 않는 예로는 JSONDecoder가 있다. Combine에서는 JSONDecoder는 주로 아래 형태로 사용된다.
.decode(type: A.self, decoder: JSONDecoder())
대부분 선언만하고 다른 프로퍼티의 변경이 없으니 URLSession처럼 하나쯤은 static으로 둘 법한데, 왜 항상 새로운 객체를 생성하게끔 한걸까?

우리가 도달한 결론은 하나의 객체를 프로젝트 전체에서 공유할 필요가 없기 때문이다였다.

실제로 DateForamtter 같은 경우는 생성 시 많은 cost가 들기 때문에 Singleton으로 래핑해 사용되기도 한다. 하지만 cost가 크지 않은 경우는 굳이 static 객체를 생성할 필요가 없는 것이다.

참고) How to use DateFormatter in Swift, How expensive is DateFormatter

프로젝트 전체가 하나의 객체를 공유해야할 필요성이 있는 경우 -> Singleton
굳이 프로젝트 전체가 하나의 객체를 공유할 필요성이 없는 경우 -> Singleton X

우리가 위에 근거해서 Sinlgeton 패턴을 사용한 예로는 카카오버스 앱의 하차 알림, 승차 알림이 있다. 이 기능을 하는 객체는 프로젝트 내 모든 화면에서 하나의 객체에 접근 가능해야하므로 Singleton 패턴를 사용했다.

Static Function

그 다음으로 이야기된 것은 enum의 static function이였다. 객체를 생성할 필요도 없고 생성자 또한 구현하지 않아도 되며, 그저 그 함수만 사용하면 된다.

프로퍼티를 가지지 않는 RequestFactory의 특성을 미루어 보았을 때 굳이 struct나 class여야 할 이유가 없었다. 또한 Factory를 구현할 때는 enum의 static func 형태로 많이들 구현하는 듯 했다.

하지만 우리가 이 방식을 사용하지 않았던 이유는 주입이 불가능하다는 것이다. 해당 객체와 결합도가 높아져 RequestFactory를 사용하는 함수의 경우 무결한 테스트가 힘들다.

5/24 추가

static function 또한 프로토콜을 사용해서 의존 관계를 독립시킨다면 테스트가 가능하다. 상태와 연관되어 있지 않은 경우, 그리고 타입 그 자체와 연관된 메소드인 경우 static function으로 구현되곤 한다. 따라서 RequestFactory 구현의 경우 static function을 사용하는 것도 좋은 방법 중 하나이다.

하지만 static function은 override가 불가능하며 객체에 메시지를 전달하는 것이 아니므로 객체지향과 거리가 멀다는 점에서 사용에 유의해야한다.

참고) [Swift] static과 class method, property 효과적으로 사용하기, 정적 메소드, 너 써도 될까?

Struct

그래서 최종적으로 struct를 사용하기로 했다. struct 내에 함수를 두어 주입받은 객체를 한번 사용하고 버리는 형태로 사용하면, 테스트 가능하면서도 메모리 상의 이점을 얻을 수 있을 것이라 생각했다.

class는 상태의 변경이 필요없고, struct에 비해 상대적으로 메모리 관리가 어렵기 때문에 고려하지 않았다.
참고) [Swift] 스위프트 성능 이해하기 (1) - struct와 class의 성능 차이

Conclusion

Singleton - 프로젝트 전체가 하나의 객체를 공유해야할 필요성이 있는 경우 사용
Static Function - 프로퍼티가 필요 없고 함수만 선언해야 하는 경우, 타입 자체와 연관된 메소드인 경우
Struct - 프로퍼티가 필요한 경우, 객체 자체와 연관된 메소드인 경우
Class - 상태의 변경이 필요한 경우

Wrapping up

의견을 주고 받으면서 협의점을 찾는 과정이 진심으로 즐거웠다. "사소한 것 하나를 정할때도 이유를 찾으라는 것이 이런 것이였구나.."하는 생각이 들었다. 설득하고 설득 당하는 과정이 (서로 표현만 조심한다면) 정말 흥미진진한 것 같다!! 😁

profile
iOS / 🌊

0개의 댓글