스팸 문자/전화좀 그만해라 (3)

2024년 12월 15일
5

스팸 문자/전화좀 그만해라스팸 문자/전화좀 그만해라

들어가며

이전 포스트를 통해, 일부 070전화번호를 차단하는 방법을 포스팅했다.

이전 방법은, 50개의 extension target을 하나씩 수동으로 추가해아한다는 아주아주 귀찮은 문제점이 존재한다.
이번에는 tuist를 사용해, 비교적 간단하게 프로젝트를 생성해보려고 한다.


기본구조

...
├── Projects
   ├── App
   │   ├── Project.swift
   │   ├── Resources
   │   │   ├── Assets.xcassets
   │   │   └── LaunchScreen.storyboard
   │   ├── Sources
   │   │   ├── Application
   │   │   │   ├── AppDelegate.swift
   │   │   │   └── SceneDelegate.swift
   │   │   └── Presentation
   │   │       ├── Base
   │   │       │   └── BasePresentation.swift
   │   │       └── Block070
   │   │           ├── Block070ViewController.swift
   │   │           └── Block070ViewModel.swift
   │   ├── Support
   │   │   ├── Info.plist
   │   │   └── app.entitlements
   │   └── Tests
   │       └── TargetTest.swift
   ├── AppExtension
   │   └── CallDirectory
   │       ├── Project.swift
   │       └── Sources
   │           ├── CallDirectoryHandler.swift
   │           └── Support
   │               └── CallBlocker.entitlements
...
   ├── Shared
   │   ├── Config
   │   │   ├── Config.xcodeproj
   │   │   ├── Project.swift
   │   │   └── Sources
   │   │       ├── AppConfig.swift
   │   │       └── CallBlockerConfig.swift
   │   ├── GlobalThirdPartyLibrary
   │   │   ├── GlobalThirdPartyLibrary.xcodeproj
   │   │   ├── Project.swift
   │   │   └── Sources
   │   │       └── ThirdPartyLibrary.swift
   │   └── Log
   │       ├── Log.xcodeproj
   │       ├── Project.swift
   │       └── Sources
   │           └── LogExtension.swift
...
├── Workspace.swift
  • tuist 설치방법은 여기를 참조
  • 템플릿를 사용해, 프로젝트를 구성함
  • 기존에 AppExtension은 Core Layer에 있었으나, 별도의 AppExtension Layer로 변경함
  • 아래 설명하는 코드는 여기에서 확인가능합니다.

간편설정

  1. Tuist 설치
  2. 소스코드 Clone 후 해당폴더 이동
  3. organizationName, DEVELOPMENT_TEAM 변경
vim ./Plugin/EnvironmentPlugin/ProjectDescriptionHelpers/ProjectEnvironment.swift
  1. AppGroup 아이디 변경
vim ./Projects/AppExtension/Sources/Support/CallBlocker.entitlements
vim ./Projects/App/Support/app.entitlements
  1. 프로젝트 생성
tuist generate
  1. Xcode Run - 전화번호 발신자 확인에서 CallBlocker extension On으로 변경
  2. 차단하기 버튼을 누르고, 약 20분정도 저장 완료될때까지 기다리기
  3. 끝~!

Package 설정

App Layer

App - Package.swift
var extensionTargets: [Target] {
    (1 ... 50).map { idx in
        let productName = "CallBlocker\(String(format: "%02d", idx))"
        let identifier = "\(env.organizationName).Block070.\(productName)"
 
        var spec = TargetSpec(
            name: productName,
            bundleId: identifier,
            infoPlist: .extendingDefault(with: [
                "CFBundleDisplayName": "$(PRODUCT_NAME)",
                "NSExtension": [
                    "NSExtensionPointIdentifier": "com.apple.callkit.call-directory",
                    "NSExtensionPrincipalClass": "$(PRODUCT_MODULE_NAME).CallDirectoryHandler",
                ],
            ]),
            sources: ["../AppExtension/CallDirectory/Sources/**"],
            entitlements: .file(path: .relativeToAppExtension("CallDirectory/Sources/Support/CallBlocker.entitlements")),
            dependencies: [
                .shared(target: .Log),
                .shared(target: .Config),
            ]
        )
 
        return .appExtension(spec: spec)
    }
}
 
...
 
let project = Project(
    name: env.name,
    organizationName: env.organizationName,
    settings: settings,
    targets: targets + extensionTargets,
    schemes: schemes
)
  • 각 extension을 별도의 모듈로 만들어서, 사용하는게 원칙임
  • 하나의 소스를 공유하는 50개 패키지를 만들지 않고, package에서 생성하는 형식으로 변경
App - Package.swift
var extensionDependencies: [TargetDependency] {
    (1 ... 50).map { idx in
        let productName = "CallBlocker\(String(format: "%02d", idx))"
        return TargetDependency.target(name: productName)
    }
}
 
let targets: [Target] = [
    .target(
        name: env.name,
        destinations: env.destinations,
        product: .app,
        bundleId: "\(env.organizationName).\(env.name)",
        deploymentTargets: env.deploymentTargets,
        infoPlist: .file(path: "Support/Info.plist"),
        sources: ["Sources/**"],
        resources: ["Resources/**"],
        entitlements: .file(path: .relativeToCurrentFile("Support/app.entitlements")), scripts: scripts,
        dependencies: [
            .shared(target: .GlobalThirdPartyLibrary),
            .shared(target: .Log),
            .shared(target: .Config)
        ] + extensionDependencies
    ),
]
  • 추가된 타겟을 불러와서 depencies에 추가함

App Extension Layer

  • 모듈이 될 수 있는 타입은 static library 또는 framework인데, appExtension는 host로 app 타입만 가질 수 있어서 실제로 모듈을 생성해도 의미가 없음
  • 단순 소스 및 설정 파일들을 가지고 있는 저장소 개념

Shared Layer

Log - Package.swift
let project = Project.module(
    name: ModulePaths.Shared.Log.rawValue,
    targets: [
        .implements(module: .shared(.Log))
    ]
)
Config - Package.swift
let project = Project.module(
    name: ModulePaths.Shared.Config.rawValue,
    targets: [
        .implements(module: .shared(.Config))
    ]
)
GlobalThirdPartyLibrary - Package.swift
let project = Project.module(
    name: ModulePaths.Shared.GlobalThirdPartyLibrary.rawValue,
    targets: [
        .implements(
            module: .shared(.GlobalThirdPartyLibrary),
            product: .framework,
            dependencies:
                [
                    .SPM.RxCocoa,
                    .SPM.RxSwift,
                    .SPM.SnapKit
                ]
        )
    ]
)
  • 공용으로 사용할 모듈 정의

결과

  • 이전 포스트에 비해, 프로젝트 설정이 훨씬 간편하게 되었다.
  • 현재는 한개의 간단한 앱으로 구성했지만, 실제 프로젝트일 경우에는 App Layer를 Feature로 옮기면 된다.
  • 드디어 070 전화로부터 해방이다!!