Skip to content

Static Framework와 Dynamic Framework 비교 #339

@MUKER-WON

Description

@MUKER-WON

들어가기 앞서 Static Framewor와 Dynamic Framework의 차이와 어떤게 더 올바른 사용인가를 탐구하기 위해 뇌피셜로 고찰했습니다. 제 모든 주장은 틀릴 수 있고, 잘못된 정보가 될 수도 있어서 잘못된 정보라면 제발 태클걸어주세요..

현재 모듈 형태는 다음과 같습니다.

Image

전체적으로 모듈형태를 고쳐나가고 있는 과정이라 형태가 일관되지 않습니다..ㅎ

기본적으로 App은 동적 프레임워크인 Data와 MainFeature를 의존합니다.

Data는 그 밑으로 쭉 동적 프레임워크로 ThirdPartyLibs까지 이어지고, MainFeature는 정적 프레임워크인 각각의 Feature들을 의존하고 있습니다.

정적의 feature들은 동적 MainFeature에 한번의 Code로 올라가 MainFeature에서 쓰인다는 의미로 만드신거 같습니다.

그리고 정적 Feature들은 동적 프레임워크인 FeatureInterface나 Dependency를 보게됩니다. 여기서 부터 조금의 의문이 들었습니다.

StaticFramework는 컴파일 시점에 하나의 오브젝트로서 .o파일로 생성되어야 하는데, 동적프레임워크를 의존하고 있어 그 부분은 마치 비어져있는 부분으로 코드가 컴파일 되어야 하는거 아닌가? 생각이 들었습니다.

그래서 첫번째 의문인 static이 dynamic을 의존하는 형태가 맞는가 였습니다.

지금 빌드가 잘 되고 문제가 없는건 제가 생각하는게 틀렸거나 컴파일 과정에서는 동적프레임워크를 모른채 링커로 넘어가고, 링커가 똑똑하게 필요로 하는 동적심볼을 연결해주는거 아닌가 싶었습니다. 이 부분은 아직 면밀히 파악하지 않았습니다.

두번째 의문이 글의 메인 주제가 되기도 하는데 하나의 App 단위에서는 모두 StaticFramework로 모듈을 생성하면 코드복사가 이뤄져서 중복코드가 생기느냐 입니다.

정적타입이다 하면 의존하는대로 코드가 복사가 된다는게 일반적인 흐름이라고 생각됩니다.

따라서 FeatureInterface도 여러 Feature가 의존할 수 있기에 현재 동적으로 생성됐습니다.

앞서 첫번째 의문과 맞물려 공부를 하던 중 하나의 App단위 즉 Watch나 AppExtension에서는 정적 프레임워크의 코드를 복사하고

저희처럼 하나의 App 단위에서는 정적프레임워크는 컴파일되어 하나의 심볼로서 코드가 복사되지 않고 필요한 곳에 쓰인다는 믿기 힘든 얘기를 봤습니다.

그렇다면 하나의 App에서는 모든 Framework를 다 정적으로 가져가는게 이득 아니야..? 라고 생각이 들 수 밖에 없었습니다.

동적프레임워크가 많아지면 많아질 수록 사용자는 runtime환경에서 사용성 감소가 이어질 확률이 크기 때문이죠

그럼 여기서 코드가 복사가 되는가? static으로 두면 앱의 사이즈가 비대하게 커지는거 아닌가? 에 대한 짧은 검증을 해봤는데

앞서 말했던거 처럼 전혀 정확한 방법이 아닐 수 있고 틀린 주장일 수 있습니다.

linkmap

일단 저희 앱의 모듈을 모두 정적 프레임워크로 바꾸겠습니다.

Image

다음으로 linkmap을 만들 수 있게 buildsetting을 수정 후 빌드 후 liinkmap을 확인해보겠습니다.

linkmap은 하나의 앱이 생성되는 과정을 로그로 남겨놓을거라 생각하시면 될거 같습니다.

이런식으로 많은 log가 포함됩니다.

Image

linkmap을 통해 코드 중복 없음을 확인하기 위해서 .o 파일인 코드 조각들이 중복적으로 생성되어 할당되는지 확인하려고 합니다.

그래프를 보면 Domain은 NetworkService, CoredataService, FeatureDependency가 의존하고 있습니다.

그러면 Domain은 정적프레임워크니 3개의 모듈에 각각 Domain의 코드가 중복되어 올라가는지 보겠습니다.

먼저 Domain에 속한 오브젝트를 출력했을 때 Domain 관련 코드는 오직 원본 Domain 프레임워크에서 생성됨을 볼 수 있습니다.

> grep 'Domain.framework/' App-linkmap.txt

[120] /Users/muker/Library/Developer/Xcode/DerivedData/WhereMyBus-난수코드/Build/Intermediates.noindex/ArchiveIntermediates/App/BuildProductsPath/Release-iphoneos/Domain.framework/Domain[3](Alert.o)
[121] /Users/muker/Library/Developer/Xcode/DerivedData/WhereMyBus-난수코드/Build/Intermediates.noindex/ArchiveIntermediates/App/BuildProductsPath/Release-iphoneos/Domain.framework/Domain[4](ForceUpdate.o)
[122] /Users/muker/Library/Developer/Xcode/DerivedData/WhereMyBus-난수코드/Build/Intermediates.noindex/ArchiveIntermediates/App/BuildProductsPath/Release-iphoneos/Domain.framework/Domain[5](AddRegularAlarmRequest.o)
[123] /Users/muker/Library/Developer/Xcode/DerivedData/WhereMyBus-난수코드/Build/Intermediates.noindex/ArchiveIntermediates/App/BuildProductsPath/Release-iphoneos/Domain.framework/Domain[6](ArrivalInfoRequest.o)
[124] /Users/muker/Library/Developer/Xcode/DerivedData/WhereMyBus-난수코드/Build/Intermediates.noindex/ArchiveIntermediates/App/BuildProductsPath/Release-iphoneos/Domain.framework/Domain[7](RemoveRegularAlarmRequest.o)
[125] /Users/muker/Library/Developer/Xcode/DerivedData/WhereMyBus-난수코드/Build/Intermediates.noindex/ArchiveIntermediates/App/BuildProductsPath/Release-iphoneos/Domain.framework/Domain[8](AppVersionInfoResponse.o)
[126] /Users/muker/Library/Developer/Xcode/DerivedData/WhereMyBus-난수코드/Build/Intermediates.noindex/ArchiveIntermediates/App/BuildProductsPath/Release-iphoneos/Domain.framework/Domain[9](BusStopArrivalInfoResponse.o)
[127] /Users/muker/Library/Developer/Xcode/DerivedData/WhereMyBus-난수코드/Build/Intermediates.noindex/ArchiveIntermediates/App/BuildProductsPath/Release-iphoneos/Domain.framework/Domain[10](BusStopInfoResponse.o)
[128] /Users/muker/Library/Developer/Xcode/DerivedData/WhereMyBus-난수코드/Build/Intermediates.noindex/ArchiveIntermediates/App/BuildProductsPath/Release-iphoneos/Domain.framework/Domain[11](BusStopRegion.o)
[129] /Users/muker/Library/Developer/Xcode/DerivedData/WhereMyBus-난수코드/Build/Intermediates.noindex/ArchiveIntermediates/App/BuildProductsPath/Release-iphoneos/Domain.framework/Domain[12](FavoritesBusResponse.o)
[130] /Users/muker/Library/Developer/Xcode/DerivedData/WhereMyBus-난수코드/Build/Intermediates.noindex/ArchiveIntermediates/App/BuildProductsPath/Release-iphoneos/Domain.framework/Domain[13](FavoritesBusStopResponse.o)
[131] /Users/muker/Library/Developer/Xcode/DerivedData/WhereMyBus-난수코드/Build/Intermediates.noindex/ArchiveIntermediates/App/BuildProductsPath/Release-iphoneos/Domain.framework/Domain[14](LocationStatus.o)

...

그러면 Domain이 가지고 있는 오브젝트로 NearMapUseCase가 하나만 생성되는지 보겠습니다.

> grep '(NearMapUseCase.o)' App-linkmap.txt

[153] /Users/muker/Library/Developer/Xcode/DerivedData/WhereMyBus-난수코드/Build/Intermediates.noindex/ArchiveIntermediates/App/BuildProductsPath/Release-iphoneos/Domain.framework/Domain[36](NearMapUseCase.o)

Domain에서 단 하나의 NearMapUseCase.o만 생성됨을 확인할 수 있습니다.

결과적으로는 최종앱에서는 정적프레임워크인 Domain을 여러곳에서 의존한다고 해도 중복 없이 단 한 번만 포함되었단 의미로 받아드렸습니다.

AI가 lnkmap을 분석한 결과입니다. 중복 되는 정적 프레임워크는 없다고 하지만 역시나 AI의 주장은 맹신하면 안되니 참고만 하겠습니다.

Image

앱의 사이즈

이번에는 두 가지의 모듈 형태로 각각 배포된 앱의 사이즈를 측정해봤습니다.

밑의 결과를 보시다시피 유의미하게 차이가 안났고

오히려 static Framework로만 이루어진 모듈구성이 코드복사가 이루어지지 않았다는 일말의 반증과 함께 더 사이즈가 작게 측정됐습니다.

거의 다 Static Framework로 바꿔 배포된 앱의 사이즈: 11.9MB

Image

기존 형태(dynamic Framework가 섞여있는) 상태의 배포된 앱의 사이즈: 13MB

Image

결론

모든 검증방법은 정확하지 않을 수 있습니다. 그냥 고찰에 대한 과정 정도라고만 생각해주셔도 될거 같습니다.

앞으로의 개발 과정에서 더 나은 모듈 형태를 위해 참고해서 읽어주시면 좋을거 같고 다른 의견이나 잘못된 점 있으면 무한 태클을 해주시면 감사하겠습니다.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions