스위프트 개발을 처음 시작할 때 들었던 생각
주저리
2017-09-07 정식으로 Swift 프로젝트를 시작했습니다. 2주 정도가 지나고…
처음 오브젝티브씨(Objective-C)에서 스위프트(Swift)로 전환하면서 제일 신경쓰이던게 구조체(Struct)와 열거형(Enumuration)이였습니다. Objective-C 를 할때는 구조체로 활용한 적이 거의 없으니.. 왜 구조체를 써야될까? 꼭 써야되는 것인가? 어떤 때 사용해야 바람직한 코딩이 될 것인가? 열거형은 기능이 엄청 확장 되었더군요. 마치 이 것을 프로그래머 들이 반드시 사용해 주세요!!! 하는 것 처럼…
고민이 많이 됬습니다.
단독 프로젝트의 경우 Objective-C는 경험을 통한 기본 설계된 프로젝트에서 꾸며나가면서 개발을 하는 데, swift는 스터디를 하면 할수록 함수형 프로그래밍 패러다임에 맞추어 설계를 잘 !!! 해야된다는 압박감이 생겨가네요.
다른 사람?개발자에게? 소스를 보여주고 욕?을 덜 먹고 싶은 마음에 설계에 대한 욕심의 고민도 생기고.. 뭐 처음이니 그렇겠죠?
구조체와 열거형을 잘 활용해야 될 것 같았습니다. 프로토콜(Protocol)도 그렇고.. 클로저(Closure)도 그렇고.. 그런데 구조체와 열거형은 왜 사용을 해야되지 라는 생각이 들더군요.
MVC에서 모델을 설계할때 모델을? 혹은 다른 유용한 패턴은..
그때 당시의 결론
0번째 : 코딩 효율성, 개발 중에 한번 수정하면 여러 곳에 같이 반영될 수 있도록, 유지보수가 쉽게 만드는 게 그 다음으로 중요하다고 생각이 드네요. 1번째 : 성능, 가능하다면!!!! 우선 앱이 빠르게 구동하게 만드는 게 최우선이고 2번째 : 그 다음으로 개으른 저를 위해………. 무언가 1,2번을 무시하고 … 쉽게 무언가.. 짱구를 굴리는 것이라 생각하네요~. .. 등등등…
0번째는 스위프트 프로젝트 or 개발을 하면서 리펙토링, 여러번의 개발, 이슈 대응을 하다보면 점점 좋아질 것이라고 생각하고..
1번째에 해당하는 이유를 찾았어요. Realm 아카데미의 메모리 강의를 듣고…
성능!! 메모리!! 컴파일러가 메모리 관리를 쉽게 할 수 있도록 코드로 구현해 주는 것 입니다.
스위프트 Swiftc 컴파일 진행은 LLVM 과 유사하나 최적화를 2단계 더 진행합니다.
swift 프론트엔드 -> SIL 최적화 -> LLVM IR 최적화 -> 코드 생성기
- LLVM과 거의 유사하나 최적화를 하는 2단계 더 있음
- 프론트엔드는 swift 파일을 읽에서 토큰 처리와 의미분석, 중간언어 생성 및 타입 검사를 함
- 중간 언어 최적화는 SIL 수준에서 분석과 변환, ARC 처리와 Generic 코드 타입 지정
- 이후 LLVM IR 수준을 최적화하고 타깃 기계에 맞는 binary 생성
효율적으로 메모리 관리를 할 수 있게 하기 위해 구조체와 열거형을 사용하면 속도는 두말할 것 없고, 시스템 성능이 좋아진답니다.. 컴파일러가 런타임 상태를 고민하지 않게 만들어 준다면!! 컴파일러가 빌드시 효율적으로 앱을 만들어 줍니다. 구조체와 열거형은 값 타입 중 하나입니다.
다시 말해서 값 타입을 최대한 사용하면 컴파일러가 소스를 이해하기 쉽기 때문에 효율적으로 처리할 수 있어서 성능이 보다 좋은 앱을 만들 수 있는 가장 좋은 방법입니다. 반드시 값타입인 구조체와 열거형을 사용할 수 있다면 반드시 사용하고, 그렇치 않다면 클래스 타입과 같은 참조 타입을 사용해서 원하는 기능을 구현하면 됩니다. 당연히!! 객체지향언어(OOP)의 엄청난 장점이 있습니다. 개발을 하게 되면 당연히 Class 위주로 개발하게 되겠지요..
그렇다면 보다 개발 중에 구조체와 열거형을 잘 쓰기 위해서는 구조체와 열거형에 대해 기본적인 이해도를 확 높이면 응용하기 쉽고 소스 중 많은 부분에 반영하여 개발 할 수 있을 것이라 생각됩니다.
그러기 위해 기본적인 값 타입과 참조 타입을 알아야겠죠?
Swift에서 크게 값 타입과 참조 타입이 있습니다. 메모리는 어렵지요? Memory Management 파트인것 같습니다. 이리 적으면 좀 많이 어려워 보입니다.
개발에서 기본을 잘 알고 나면 나중에 편합니다.
최소한 알아 두어야 되는 용어가 있습니다.
- 스택과 힙, 그리고 메모리가 복사된다 란 의미와 메모리가 참조 된다란 의미
- 값 타입과 참조 타입의 용어
- 참조의 관리에서 RC, ARC
- ARC에서 강한참조, 약한참조, 미소유 참조
스택(Stack)과 힙(Heap)
우선 같은 점.. 둘다 iOS 운영체제의 메모리(RAM)에 저장됩니다. 메모리에 저장됩니다. 그래서 스텍 메모리 영역, 힙 메모리 영역 이렇게 표현합니다. 같은 점은 .. 이것 뿐인가..
스택은 저장(할당)되는게 빠릅니다! 힙은 할당되는 과정이 단순하지 않습니다. 길어지니 참고 링크를 확인해주세요.
참조 링크 : https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap
값 타입과 참조 타입의 용어
두개의 명확한 차이점은 메모리 값이 전달 될 때 값(Value) 타입은 항상 복사되어 전달되고, 객체(Object) 타입은 인스턴스가 참조를 통해서 전달 됩니다. 객체타입은 좀더 상위의 개념으로 참조(Referance) 타입이랑 같은 표현이죠. 객체(참조) 타입은 해제 시점, 값을 사용 할 때 공유되어 사용되니 설계할 때 유념하고 사용될 것 입니다.
참조 링크
- 감사합니다. : https://academy.realm.io/kr/posts/letswift-swift-performance/?
- http://seorenn.blogspot.kr/2016/04/swift-class-struct.html
참조의 관리에서 RC, ARC
Swift에서는 ARC(Memory Management)가 자동으로 처리해 주니 크게 신경 쓸 부분은 적습니다.
소스로 구현한 참조 타입(Class..)은 힙 메모리(Heap)에 저장이 됩니다.
(참고 : 반대로 값 타입은 예를 들어 구조체 데이터라면 스택에 값 전체가 저장되고, 변수에 할당 될 때 전체 값이 복사됩니다. 하나를 변경해도 다른 것에 영향이 없고 힙을 안쓰니 RC(참조카운트)를 사용하지 않습니다.)
그 메모리 들은 자동으로 관리가 됩니다. 그것을 ARC (Automatic Reference Count) 라고 합니다. 참조 횟수를 세어가면서 관리하는 기법입니다. 어떤 것을 가리키는 포인트를 계속해서 탐지하고 있다가 참조횟수가 0이 되면 즉시 그것을 놓어버리는 것입니다.
ARC를 보다 잘 활용하기 위해서는
- Strong (강한 참조) : Default!!! 설정이 없다면 기본값~
- Weak (약한 참조)
- Unowned (미소유 참조) 이 3가지를 알고 있어야 됩니다.
ARC에서 강한참조, 약한참조, 미소유 참조
Strong (강한 참조)
Strong은 어떠한 설정도 없을 때의 기본값입니다. 보통 볼 수 없죠. 보통은 힙 안에 있는 것 들을 힙 안에 머무르게 합니다. 힙에 머물도록 강제한다고 생각하면 됩니다. 그 포인트가 더 이상 가리키지 않을 때 까지 입니다. 그래서 Strong 포인트는 힙 안에서 강하게 붙들고 있는 역활을 합니다. 예를 들어 포인터를 만들었는 데 그것이 모두 Strong 이라면, 깨끗이 청소하기 위해서는 모든 포인트를 지워야 합니다.
Weak (약한 참조)
반대로 Weak 가 있습니다. Weak 포인터가 있다는 말은 힙에 있는 것에 아무도 관심이 없다면 힙에서 제거할 수 있다는 … 말입니다. 그리곤 nil로 설정이 됩니다. 이건 마치, 힙에서 어떤 메모리를 가리키고는 있지만!! 그 안에 들었는 것에는 그만큼 관심이 없어서 사라져 버리면 그대로 nil로 설정하겠다는 것 입니다. nil로 설정하겠다는 것은 Optional 포인터여야 합니다. 그래서 Weak 포인터는 오직 Optional 포인트에만 적용이 된다는 사실~ 기본적으로 클래스에 대한 옵셔널 포인터 입니다. Weak 포인트는 힙에 어떠한 것도 저장하지 않습니다. 힙에 저장할 포인터는 Strong 포인터에 달려있습니다. 예시로 Weak 타입의 포인터로는 스토리보드를 사용할 때 UI와 연결해서 사용하는 Outlet이 있습니다. 문자열을 표시하기 위해 사용하는 UILabel을 스토리보드에서 만들고 소스를 연결 “@IBOutlet weak var label:UILabel!” 하면 Weak로 연결이 되죠. 왜? Weak 일까요. 왜냐하면 뷰(MVC V) 계층에서는 그 상위 뷰가 메모리를 가지고 있고 UILabel을 사용하다가 제거해 버리면 더 이상 뷰의 일부가 아니게 되어 아마 Oultet의 관심이 없어지게 되겠지요. 그땐 Outlet이 nil로 만들어버립니다. 정말 필요해서 제거(뷰계층에서 제외해도)를 해도 힙 메모리에 유지해서 넣었다 뺏다 해야되면 .. Strong으로 만들고 사용하면 됩니다. 제거 되고 난 이후 에 다시 넣고 싶을 일이 있을 것이라면요. Outlet에서 이게 Strong 포인터를 쓰는 이유 입니다. 변경한다면 말이지요~ nil로 할당해도 되는 Optional 포인터가 Weak 입니다. 변수 앞에 붙여서 사용합니다.
unowned (미소유 참조)
unowned는 참조 카운트를 세지 말라는 의미입니다. 매우 위험한 방법입니다. 만약 unowned 포인트가 있다면 참조 횟수를 세지 말라는 추적하지 않겠다는 의미입니다. 그래서 이 포인트는 항상 메모리의 작은 공간을 가리킬 것이고, 포인터로 가리키는 것이 거기 있다는 것을 확인하는 것이 좋을 것입니다. 더이상 이 포인터를 이용하지 않을 때 까지 입니다. 만약 unowned 포인터가 있는 데 참조를 나중에 하고, 참조한 것이 힙 밖으로 버려지게 되면.. 더 이상 Strong 포인트가 존재 하지 않기 때문에 앱은 충돌나게 될 것입니다. 메모리 참조 에러가 날 것 입니다. 사용 되는 것을 예로 들어보면, 메모리간에 순환 참조가 일어나는 데, 직접/간접 적으로 서로를 가리키게 되는 데, 메모리 안에 둘다 보관되는 경우? unowned로 그런 연결을 깰 수있다고 합니다. 보통은 Weak로 할 것입니다. 거의 사용하지 않을 것입니다. (이정도로 알면 되지 않을까… 합니다.)
참고 링크
- https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html
- http://kka7.tistory.com/21
- https://outofbedlam.github.io/swift/2016/01/31/Swift-ARC-Closure-weakself/
- http://wlaxhrl.tistory.com/22