...
본문 바로가기

Xcode

static framework에 Assets.catalog - image 사용하기 ?

 

TCA를 공부하다가, 적당히 문서도 보고, 개념도 알고, 어느정도 사용하는 방법도 배웠다고 생각이들어서

TCA는 그만 공부하고, framework를 공부하고 Tuist를 공부하려고 한다. 

그래서 framework, swiftPackage 등을 공부하고 있고, dynamic, static, embed, do not embed .. 등을 공부하고 있다.

 

 

요약

이미지를 bundle로 읽어들일때, 명시적으로 Bundle(path:)로 지정하면 읽을수 있긴하다. 

하지만 내가봐도 비추. 올바른 방법처럼 안보이기도 하고.. 

굳이한다면 static framework를 곧바로 App 프로젝트에서 사용하는거 아니면 더더욱 비추.. 

 

아니면 그냥 static framework랑 리소스 번들 target 따로 분리해서 사용하는게... 아니면 dynamic프레임워크로 만들어서 하는게 나아보인다. 

 

 

발단

dynamic, static, embed, do not embed 보면서 프레임워크 그래프 상황별 케이스 생각하다보니 

문득 static 프레임워크는 리소스도 사용할수있다고 본것 같아서 궁금증이 들었다. 

 

 

App프로젝트에서 dynamic 프레임워크를 사용하고 있는데, 

dynamic 프레임워크가 또 다른 dynamic 프레임워크를 사용하고있는데, embed로 하게되면 어떻게 될까? 
아카이브할때 nested framework (umbrella framework)로 되어서 에러날것 ! 

 

만약 dynamic 프레임워크가 아니라, static 프레임워크 & embed 를 사용한다면 ? 여기서 의문증 출발..

보통 static 프레임워크는 do not embed인데, (바이너리에 포함되니까) 

static 프레임워크에 리소스등이 있는 경우 (assets 등)에는 embed를 하여, 번들에 포함시켜서 리소스등을 로드하게끔 하기 위하여 embed로 한다고 봤었다.  

https://holyswift.app/frameworks-embed-or-not-embed-thats-the-question/

(위 링크에서는 "media bundle" 이라고 하긴했는데.. )

 

static 프레임워크 관련 코드들은 바이너리에 포함되고, embed를 통해서 번들만 따로 추가되는거니까,  nested framework까진 안가지 않을까??  라고 생각해서 시작했는데, 결과적으로는 요것도 nested framework가 맞다. 아카이브에서 에러발생한다.

 

embed , 번들 

해결방법 코드를 작성하기에 앞서, 번들구조를 보면 왜 해결방법이 나왔는지 이해가 좀더 될듯싶어 적어본다

 

 

ResourceFramework는 위에서 예로든, static 프레임워크이고, Assets에 image를 하나가지고있다.  ( mach-O 에서 static library로 지정)

 

앱빌드후 만들어진 App에 패키지내용보기 하면 

아래처럼 번들구조를 볼수있는데,  embed를 하게되면 번들에 Frameworks 폴더가 생기게되고, 

이안에 ResourceFramework 번들이 생기게된다. 

 

번들구조를 보면알수있듯이 AppProject는 앱 바이너리이고, 나머지들이 앱을 구성하는 요소로보면 되겠다. 

 

그래서 보통 embed하는 케이스는 dynamic 프레임워크처럼 런타임에 로드될수있도록 번들에 Frameworks 폴더를 생성하여

여기서 참조가 가능하게 한다.

 

do not embed하는 케이스는, 

1. 보통 static 프레임워크이거나 ( static이므로 앱 바이너리에 포함되어지니, embed하여 굳이 번들을 만들필요가없기도하고, static library 자체는 소스코드들만 가능하다고 하니 번들이 있을수없을거고)  

 

2. 동일한 static 프레임워크를 여러 다른 프레임워크에서 사용하는 경우,  여러 다른 프레임워크들이 동일한 static 프레임워크를 바이너리에 복사하기 보다는,  static 프레임워크를 dynamic 프레임워크로 만들어서, App 프로젝트가 dynamic 프레임워크를 embed하여 가지게하고,  여러 다른 프레임워크들이 do not embed로 사용하는게 낫지않나 싶다. 

- 근데 요것도, 직접 프레임워크를 넣어주지않고, SPM 을 이용한다면 xcode에서 알아서 dynamic으로 만들어준다고한다 - 민소네블로그 참고링크

 

[Swift][SwiftPM] Swift Package의 라이브러리를 Dynamic Framework로 만들기

일반적으로 Swift Package로 만든 라이브러리의 Mach-O의 기본값은 Static 입니다. Dynamic 으로 변경하려면 type을 .dynamic 으로 변경해야합니다. // FileName : Package.swift let package = Package( name: "MyLibrary", products

minsone.github.io

 

 

 

주저리주저리 했는데, 그래프로 보면.. 간단하다  아래와 같은 상황이라면 L static이 두번 복사될텐데, 

 

아래처럼 L dynamic으로 바꾸고, App이 소유하고있으면 불필요한 복사를 줄일수있을것 같다. 

 

 


3. 물론, UIKit과 같은 os단에서 제공하는 프레임워크들또한 do not embed로 하는게 맞다. 내앱은 번들로 안가지고있어도, os에서 가지고있을테니... 

 

공식 문서 

https://developer.apple.com/documentation/xcode/creating-a-static-framework#Add-resources-to-your-framework-target

 

Creating a static framework | Apple Developer Documentation

Configure your project to build a new static framework.

developer.apple.com

 

 

공식문서에서도 볼수있듯이, static framework또한 embed로 하고,  resource도 추가할수있다고 한다!

( 근데왜 방법은 안알려주니?????? ㅜ)

 

찾아보면, 정석 ? 은 static framework,  리소스번들 target 두개로 나눠서, staticframework도 가지고, app타겟도 리소스 target가지고....  사실상 이건 target을 두개로 분리하는거니까, static framework에서 제공하는게 아니지않나 ? 라는 생각이 든다. 

 

해결방법

( 아래코드는 간단한 예시코드로, ImageLoder는 ResourceFramework에 작성된 코드다. 

AppProject에서 ResourceFramework를 사용하고, 해당 프레임워크의 ImageLoader를 사용한다. )

 

 

그래서 Bundle안에 불러와서 하는데 이미지가 로드가안되길래, 경로를 프린트해보니까, 

ResourceFramework의 경로가아닌, AppProject의 경로로 나온다.  

( bundle도 만들어보고 넣어보기도 해봤는데, 안됐다.  - _  - ; ) 

 

static이므로,  바이너리에 포함되어지니 AppProject의 경로로 나오는듯하다

AppProject ->  OtherFramework -> ResourceFramework 이런 구조라면, OtherFramework경로로 나온다.

public class ImageLoader {
    
    public init () {}
    
    public func getSampleImage() -> UIImage? {
        
        let imageLoaderBundle = Bundle(for: ImageLoader.self)
      
        print("Bundle RsourceFraemwork :", imageLoaderBundle.bundlePath)

		// Bundle RsourceFraemwork : 
		///AppProject.app
        
        
        let image = UIImage(named: "sample", in: imageLoaderBundle, compatibleWith: nil)
        return image
    }
}

 

AppProject경로가 아래 빨간색에 위치해있다. 

기대하기로는 Frameworks/ResourceFramework.framework/로 나와야할것 같은데! 

 

ResourceFramework를 mach-O를 dynamic library로 바꾸면 원하는 경로로 나오긴한다.

// Bundle RsourceFraemwork : 
// /AppProject.app/Frameworks/ResourceFramework.framework

 

 

암튼, 번들구조를 알았으니..   Bundle()에 경로를 내가직접 넣어주면 읽을수있지 않을까? 하는 생각으로

Bundle(path:) 를 이용하니 읽어진다! 

let imageLoaderBundle = Bundle(for: ImageLoader.self)
if let frameworkBundle = Bundle(path: imageLoaderBundle.bundlePath + "/Frameworks/ResourceFramework.framework") {
	if let image = UIImage(named: "sample", in: frameworkBundle, with: nil) {
		return image
	}
}

 

 

번들구조가 변경되지않는다면... 이렇게라도 사용해볼수있을것 같긴한데, 

그냥 dynamic으로 쓰는게 낫지않나 싶다..ㅎ 

대신 App 프로젝트에서는 embed로 해야할거고,  ResourceFramework에서 또 따른 dynamic 프레임워크를 사용한다거나 한다면 do not embed하고, 그래프 잘 그려서 판단해야할것 같다. 

 

 

 

번외편 

이글의 주제랑은 관련없긴하지만, 그래프 그려보면서 케이스 생각하다보니, 

 

아래와 같은 케이스는 안된다. 

 

예제는,  Alamofire-Dynamic을 가지는 static 프레임워크(NetworkFramework)를 만들고, App프로젝트(BProject)에서 이 프레임워크를 사용하는 케이스로 해봤다. 

링크과정에서 Undefiend symbol 이라고 뜨면서 빌드자체가안된다. 

 

NetworkFramework가 static이므로 BProject의 바이너리에 포함되어야하는데, 이때 NetworkFramework에서 사용하는 Alamofire에 대한 심볼을 해석할수없다는 뜻 같다. 

 

그러면, BProject가 Alamofire 심볼을 해석할수있도록, 추가해주면 된다.

 

그래프는 요렇게 되겠다! 

 

GMarket 기술블로그 가 이런 Mach-O & framework에 대해 굉장히 자세히 기술해주셨다. objective-c 관련코드들도 있어서 꽤 이슈가 있는걸로 보이는데, 대단하시다..  

 

iOS Framework 에서 다른 Framework 사용시 Mach-O type 에 따른 주의 사항

안녕하세요 Mobile Application Team 에서 iOS 개발을 하고 있는 강수진입니다. 이번 글에서는 지마켓 iOS 프로젝트에서 사용하고 있는 프레임워크들의 관계와, Framework 에서 다른 Framework 를 사용할 때 Mac

dev.gmarket.com