UIKit을 활용하여 앱을 만들다보면 심심치 않게 아래와 같은 코드를 사용하게 된다.
[view1, view2, view3].forEach {
view.addSubView($0)
}
위 코드는 어느 ViewController의 뷰 내부에 SubView들을 한 번에 삽입하기 위해 사용하는 코드이다.
forEach를 사용하지 않으면 아래처럼 반복해서 코드를 호출해야 하기 때문에 편의를 위해 반복문을 활용해서 많이 사용하는 편이다.
view.addSubView(view1)
view.addSubView(view2)
view.addSubView(view3)
...
그런데, 서브 뷰를 삽입하기 위해 배열을 만드는 것이 과연 옳은 행동일까? 라는 의문이 들었다.
서브뷰를 삽입하는 동작은 어쩌면 단 한번만 발생할지도 모르는 동작이다. 그런데 그 동작을 위해 별도의 배열을 생성하는 것이 메모리 성능상 과연 올바른 행동인가 하는 의문이 생긴 것이다.
실제로 우리가 위의 예시와 같이 [view1, view2, view3]과 같이 리터럴 배열을 생성하면, 메모리 내부에서는 스택이나 힙에 임시적인 배열 객체가 생성된다고 한다. 그리고 이 때, 이 배열은 view1, view2, view3이라는 UIView 객체의 참조를 담고 있다.

메모리에 임시 배열이 만들어지긴 하지만, UIView 객체 자체는 배열에 복사되지 않는다고 한다. 따라서 UIView 객체에 대한 추가적인 메모리 할당은 없고, 배열이 점유하는 메모리는 UIView 참조 n개를 저장할 공간과 배열 자체의 메타데이터 뿐이다.
또, forEach 루프가 끝나는 즉시 메모리에 추가된 임시 배열은 스코프를 벗어나 메모리에서 자동 해제가 이루어진다.
따라서 3개, 10개 또는 100개의 뷰를 배열에 담는 것은 현대 스마트폰의 풍부한 메모리 환경에서는 무시해도 될만큼 미미한 비용이다.
이로 인해 앱의 성능이 저하되거나 메모리 부족을 겪을 가능성은 거의 없는 것이다.
forEach를 사용하는 방식은 직관적이고 가독성이 뛰어난 좋은 방식이다. 실제로도 많이 쓰이는 방식이기도 하다.
굳이 이 방식에서 벗어날 필요는 없지만, 조금 더 Swift스러운 방식을 선호한다면 for-in 루프를 사용할 수 있다.
for view in [view1, view2, view3] {
view.addSubView(view)
}
그러나 forEach나 for-in 모두 메모리 효율성 측면에서는 큰 차이가 없으므로, 가독성이 좋은 방식을 선택하는 것이 최선이라고 본다.
위에서는 배열을 만들고 forEach를 사용하는 방식을 예시로 활용했지만, 실제로는 더 다양한 방식이 있다.
그 중에서 내가 자주 사용하는 방식은 UIView를 확장 시켜서 한번에 뷰를 추가하는 방식이다.
extension UIView {
func addSubViews(_ views: UIView...) {
views.forEach {
self.addSubview($0)
}
}
}
위처럼 UIView를 확장시킨 뒤, addSubViews(_:)라는 함수를 만드는 것인데, 이 때 매개변수로 UIView...라는 가변인자 타입을 받도록 되어있다. 이는 여러 UIView 타입의 객체를 매개변수로 받을 수 있어 편리한데, 이 방식은 배열을 사용한 방식과 비교했을 때 어떤 차이가 있을까?
사실 UIView...와 같은 가변 인자 형태는 Swift 컴파일러가 내부적으로 배열로 처리한다.
즉, 타입은 가변 인자 타입(UIView...)일 수 있지만, 실제로는 배열([UIView])형식으로 처리된다는 의미이다.
그렇기 때문에 배열을 만들어서 처리하는 것과 가변인자를 만들어서 처리하는 두 방식은 메모리 효율성만 놓고 보면 큰 차이가 없다.
가변인자를 사용하는 것은 배열 리터럴([])을 명시적으로 작성할 필요가 없어 코드가 더 간결해보일 뿐, 실제로는 배열로 처리되기 때문이다.
배열을 만들거나, 가변인자를 통해 서브뷰를 삽입하는 두 방식은 메모리 효율성에 차이가 없기 때문에 어떤 방식을 선택해도 상관이 없다.
다만, 코드의 재사용성과 가독성 측면에서는 UIView를 확장시켜서 구현하는 것이 더 좋다.
한번 확장 함수를 구현해두면 뷰를 추가할 때마다 해당 함수를 호출하여 간결하게 코드를 작성할 수 있기 때문이다.
평소에 별 생각 없이 사용하던 방법들인데, 메모리 효율성을 고민해볼 수 있는 유익한 시간이었다.
지금껏 사용하던 방식이 메모리 효율성에 큰 영향을 미치는 것은 아니지만, 메모리 효율성을 헤치진 않는지 고민했던 것만으로 큰 도움이 되었던 것 같다.
앞으로도 자주 사용하는 코드나 새롭게 사용하는 코드에 대해 메모리 영향을 고민할 수 있는 개발자가 되고 싶다.