본문 바로가기

iOS/Project

[Swift Package] No1. Swift Package 편하게 접근해보기🤩 | #의존성 #Package.swift #ACL

 

안녕하세요. 오랜만에.. 포스트를 다시 작성하네요. 이번 포스트는 Swift Package에 대해서 탐구해보려고 합니다. A Package의 특정 타겟에서 외부 repository 의존성 추가 및 로컬 library 의존성 추가. Package.swift에 대해 파해치려구 합니다.

"An Introduction to Swift Package Manager"( kodeco 링크 )글을 활용해서 제가 아는 지식도 정리할 겸...

 

이게 예전에 올려진 글이라 몇 명령어는 실행이 되지 않을 수 있어서, 제 스타일대로 커스텀을 하며 파해치려구 합니다.

 

오늘의 키워드!

  • Package.swift
  • target
  • Library
  • Product
  • Dependency

[ 주요 개념들 ]

약간 개념을 간단하게 정리하겠습니다. 

 

1. Package.swift

Swift Package들은 각각의 Package.swift를 소유합니다. 하나 또는 여러개의 library를 포함하고, 실행 가능합니다. Package.swift를 manifest file이라고 칭합니다. Product(library, executable)를 정의할 수 있고, target에 대한 any dependencies를 정의할 수 있게 됩니다. Swift 언어로 작성이 가능합니다.

2. Target

SPM에서 target은 Package의 보통 기본 요소입니다. 얘는 하나의 module과 다른 모듈들의 집합을 정의하는데, 코드가 어떻게 구성됬는지 명세서 느낌입니다. 코드에서 필요로되는 dependencies들도 정의됩니다. A target은 a module과 그 모듈이 의존하는 다른 타겟들, 외부 Package, 내부 Package에 의존할 수 있습니다. (dependenceis 배열에서 정의) dependencies란 코드에서 필요로 되는 다른 모듈들,, libraries or packages을 의미합니다. 리소스도 추가 가능합니다. 로컬라이즈 파일들, assets etc... 그리고 이들을 위한 번들(Bundle)이 추가되어 target에서 사용할 수 있게 됩니다. 이때 그 타겟의 module을 번들로 (Bundle.module..) 잘 불러와야 합니다.

 

Package.swift에 타겟을 추가한다는건, Package 내부에서 특정 라이브러리, 테스트 들을 위해서 추가가 가능합니다.

3. Library

라이브러리는 Package에서 디렉터리 내부에 있는 코드 뭉텅이들로 생각해도 좋습니다. 재사용가능한 코드들이 모여있고, 다른 코드, 모듈 Pacakge, Project에서 의존성 추가 후 호출할 수 있게 됩니다. Sources 디렉터리 안에 반드시 위치해야 하고,,,(아무리 빼도 안되더라구요ㅠ) Package.swift의 products에 정의할 수 있게됩니다.

4. Product

Package.swift에서 정의된 product들은 외부에서 접근가능한 libraries or executable 파일입니다. 다른 패키지들은 하나의 패키지에서 정의된 라이브러리들을 각각 의존성으로 추가해서 독립적으로 import해서 사용할 수 있게됩니다.

5. Dependency

Package내부에서 기능을 작성할 때 필요로되는 외부 or local Package들.. 라이브러리들이 있다면 dependencies에서 추가 후 해당 패키지를 빌드할 때 다운받아지거나 참조되서 Dependencies에 추가가되며, 이 각각의 Dependencies에서 제공하는 libraries를 의존성으로 추가해서 활용할 수 있게됩니다.

 


 

이제 Swift Pacakge를  탐구해 보죠!!

 

"An Introduction to Swift Package Manager"( kodeco 링크 )를 바탕으로 탐구해나갈 것이어서 같이 진행하면 좋습니다. kodeco에선 아마 에러가 발생할지도 몰라요..

 

제가 시도하는 목적은 WebBuilder Package를 dependencies로 추가해서, Utils library에 Hello, IdiomResponder 구조체를 선언하고, EntryPoint executable에서 로컬 라이브러리 및 외부 라이브러리 의존성들을 추가해서 kodeco에서의 흐름과 유사하게 실행까지 해볼 것입니다.

 

이번 포스트를 전부 읽으신다면 local library를 다른 library or executable product에 추가하는 방법, 외부 라이브러리를 추가하는 방법 및 dependencies정의 방법, 타겟 선언, ACL 등의 개념을 접할 수 있습니다.

 

[  1. Package 생성 ]

mkdir Utils
cd Utils

swift package init

 

이렇게 명령어들을 쉘에 입력하면 Utils 디렉터리 내부는 Package.swift 및 기본 디렉터리들이 생성됩니다.

 

아! Package.swift를 누르면 바로 xcode가 켜집니다!

 

... generate-xcodeproj

 

이 명령어는 더 이상 사용하지 않고, Package.swift를 누르면 자동으로 xcode가 SPM을 지원하도록 변경된다고 합니다!

( cf. Swift forum: 'generate-xcodeproj' is no longer needed and will be depreciated soon )

 

 

여기서 기본 구성은 Utils 이름을 갖는 Package와 내부에 Utils 라이브러리 및 Utils 타겟, 테스트 타겟이 기본적으로 추가 됩니다.

 

 

테스트는 사용하지 않을것이기에, 19~21라인은 제거하고, Sources 디렉터리만 남겨두도록 Tests 디렉터리는 제거합니다.
기본적으로 .library는 Sources 내부에 선언된 디렉터리 네임을 기준으로 선언할 수 있습니다. 앞으로 이 Package를 의존성으로 추가하는 다른 Package or Proejct에서는 Utils 라이브러리를 import해서 사용할 수 있게 됩니다. "import Utils"

 

[ 2. Reomte repository's package를 Utils Package에 의존성으로 추가 ]

spm-tutorial 레포지토리 ( 링크 바로가기 )에서 Tag를 활용해 release한 Package 버전 중 1.0.0 버전을 받아볼 것 입니다. 여기에는 WebsiteBuilder 라이브러리를 통해 정의된 Websilte 객체를 활용할 수 있게 됩니다.

 


Utils디렉터리 내부에 Package.swift 파일에서 아래 의존성을 추가합니다.

 

dependencies: [
  .package(url: "https://github.com/raywenderlich/spm-tutorial.git", from: "1.0.0")
]

 

Reomte package는 url을 통해 받아올 때 빌드 결과로 Package.resolved에서 현재 사용중인 dependencies 버전 등이 기록됩니다.

 

Local package의 경우 현 패키지의 디렉터리를 기준으로 상대 경로로 다른 경로에 위치한 Package 를 소유한 디렉터리 위치를 선언해서 의존성을 추가할 수 있습니다.

e.g. 상대경로

(위 사진처럼 이렇게 상대경로를 통해서 패키지들을 Feat1 Package 내부에 추가할 수 있습니다: )

 

 

targets: [
  .target(
    name: "Website",
    dependencies: ["WebsiteBuilder"]),
...

 

그리고 kodeco에서 선언항 것처럼 문자열로 의존성을 추가를 할 수도 있고 target dependencies에서는 아래와 같은 방식으로 더 명확하게 선언을 할 수 있습니다.

 

 

사진 속 하단의 "targets" 배열에서 target에 대한 dependencies를 보면,

 

kodeco에서 선언한 방식의 경우 문자열을 통해서 해당 패키지 내 또는 reomte에서 가져온 package내 WebsiteBuilder라는 의존성을 지정할 때 사용됩니다. (이름이 유일하다고 가정할 때)(쉬움)

 

사진속의 경우(.product) 명확하게, 외부 패키지의 product를 명시적으로 지정할 수 있습니다. 내부 Dependencies의 패키지들 또한 마찬가지입니다.

(외부 패키지는 레포지토리명을 인식하는 경우도 있는것 같습니다. 로컬 패키지는 로컬 패키지 이름을 사용해도 되는데,,)

 

그리고 target에 의존성을 추가 해야만 타겟 이름으로 빌드할 때 그 내부에 소스 코드들이 활용할 library import가 가능합니다. 성공적으로 추가 후 빌드를 해보면 or 패키지 리셋하면, 왼쪽 탭(Project Navigator)의 Package Dependencies 아래에 외부 or 로컬 모듈들이 생기고, 해당 모듈들이 의존하는 모듈들도 추가됩니다.


[ 3. Utils내부에 간단한 Hello 구조체 정의 및 패키지 내부에서 실행하기 ]

 

이제 프로젝트 네비게이터 탭에서 Utils 디렉터리 내부 Utils.swift에 오른쪽과 같이 Hello를 정의해주고.. Sources에 또 EntryPoint 디렉터리생성 및 내부에 main 파일 생성 및 위 사진과 같이 cmd+r로 실행 해보기실 바랍니다.

 

 

이왕이면 해결하는 방법도 찾아보시는게,,,

 

 

 

 

A package에서 실행하기 위해서는 main.swift라는 entry point가 필요로되고, 이를 기반으로 executable 파일을 실행하게 됩니다. 그러나 별도로 Package.swift에서 Products에 .executable 을 선언하고, target에서도 EntryPoint 타겟이 사용할 내부 라이브러리인 Utils를 추가해야 합니다.

 

 

그 후에, 

 

EntryPoint 타겟으로 스킴 변경 후 My Mac으로 변경한 후에 실행을 하면 됩니다.

 

 

되지 않는 이유는

Utils의 라이브러리에서 제공한 Hello구조체는 ACL이 internal이기에 해당 (Utils)라이브러리 내부에서만 호출하고 정의할 수 있게됩니다. 그래서 public으로 변경후, init함수 및 내부 멤버변수를 public으로 변경해야 합니다.

짜잔!

 

호호..

 

여기서 Uitls를 import하지 않은 채로하면 EntryPoint target에서는 Utils.Hello 객체를 인식할 수 없고 실행할 컴파일 에러가 발생됩니다.


[ 4. Utils에 idioms, IdiomResponder 객체 추가 ]

위에 언급한 kodeco 포스트에서 1 page IdiomResponder 구조체를 보면, 내부적으로 WebsiteBuilder library's HTTPModels에서 정의된 HTTPResponder를 사용합니다. 이 객체를 Utils의 Utils.swift파일에서 정의할 것입니다.

 

즉 Utils Project는 WebsiteBuilder를 의존하게 됩니다. Package에서 dependencies에 product를 url을 통해서 Package내부에 추가가 됬기에, Utils target이 WebsiteBuilder Module을 import할 수 있게 dependency를 추가하면 됩니다.

 

( 아! 의존성을 추가하지 않고, IdiomResponder를 Utils 디렉터리 -> Utils.swift에 구조체를 선언및 import WebsiteBuilder 선언 후에 build 하면 에러가 발생됩니다. )

 

 

 

( 참고로 빌드할 때 xcode에서 실행할 타겟 스킴은 EntryPoint -> Utils로 변경!! )

 

 

이렇게 추가를 하게된 후에, 빌드가 성공적으로 되는지 cmd + b를 해봐야 합니다!

 

[ 5. Utils에 idioms, IdiomResponder 객체를 main.swift에서 호출하기! ]

 

메인에서 이제 IdiomResponder()를 선언하고 xcode타겟을 EntryPoint로 변경 후 실행하면 에러가 발생됩니다.

 

이유는 ?! 뭘까요... (댓글로 남겨주시면 감사합니다)

 

 

 

Website는 reomte repository에서 받아온 WebsiteBuilder Package 내부 모델 파일안에 선언됬습니다. EntryPoint's target은 WebsiteBuilder.Website 객체를 불러올 수 없습니다. 컴파일러의 역할인 구문분석 시점에서 컴파일 에러를 발생시킵니다. 당연히 링킹 과정에서 심볼 종류들 해석, 재배치과정도 못하고 모듈들, 패키지들을 결합해 실행가능한 파일로도 만들 수 없게 됩니다.

 

말이 길어졌군요. 간단하게.. EntryPoint target에 dependency를 추가해야 합니다.

 

그리고 import WebsiteBuilder 하고 실행하면!

 

실행이 됩니다!! 실행이 안된다면,, Product Navigator탭에서 Pacakge Dependencies.. WebsiteBuilder 버전이 1.0.0인지 확인하셔야합니다.

 

 

이 모듈은 Proejct에 추가해도 되고, Github repository tag와 함께 release로 배포도 가능합니다. 저도.. Firestore Service를 다루는 객체를 하나 만들어서 배포했었는데(링크 바로가기),, 생각보다 재밌습니다! 배포 관련해서 cocopods로 배포 및 SPM으로 배포가 둘다 가능한데 많은 분들이 글을 올려주셔서 참고하면 쉽게 가능합니다.

 

 

지금 예제는 간단하게 살펴봤지만,, Package에서는 번들을 추가해서 에셋이나 이미지들도 모듈화 해서 해당 패키지에서 필요로 되는 resources로 사용이 가능합니다.

 

 

 

그래서 SPM?!!은 CocoaPods처럼 비슷하게.. Package.swift를 통해서~ 타겟 또는 a package내부에서 필요로 되는 dependencies(의존성)(module or library or package or remote repository's package etc...)를 정의 및 관리하고 모듈화하고 빌드 및 테스트 타겟도 만들어서 테스트도 가능하게 해줍니다!(결론: 의존성 관리해준다!)

 

 

잘못된 개념이 있다면 댓글로 알려주시면 감사합니다!!

 

References

Swift forum: 'generate-xcodeproj' is no longer needed and will be depreciated soon

Kodeco: "An Introduction to Swift Package Manager"