안녕! 난 너의 가상 친구야!
Overview
바탕화면 장난감은 컴퓨터 바탕화면에서 걸어다니는 캐릭터 프로그램이다. 사용자가 지정한 대사를 내뱉거나 Macro를 실행해준다. 어릴 적에 바탕화면에 캐릭터를 대여섯개 띄워두고 좋아했던 추억이 되살아나서 만들었다. 이 프로그램은 사용자의 바탕화면을 의미있게 채울 수 있다.
이 프로그램은 당연히 MacOS에서만 동작한다. dotnet
의 WinForm API를 주로 사용해 만든 Window용 바탕화면 장난감은 회사에서 배포했고, 다시 만들 계획은 없다.
처음 Swift를 사용하며 겪은 우여곡절과 함께 간단한 프로젝트 설명을 하고자 한다.
개발 환경 & 빌드 세팅
MacOS 13.0.1에서 Xcode 14.2를 이용해 개발했다.
Xcode 포맷팅은 범위 선택 -> Control + i
로 할 수 있다. Xcode -> Settings... -> Text Editing
에서 포맷팅 옵션을 수정할 수 있다.
…는 매우 불편하다. 매번 범위 선택 후 포맷팅을 한다는 동작은 믿음직스럽지 못하다.
project-name.xcodeproj
의 Build Phase
에 빌드 액션을 추가할 수 있다. 미리 매 빌드에 실행할 셸 스크립트를 추가하는 방식이다. 이 프로젝트에서는 formatting과 lint 체크를 한다. 사용한 formatter는 apple에서 제공하는 swift-format이다.
빌드 방법 및 사용 방법은 해당 레포에 상세히 나와있다. 별도로 해줄 것은 프로그램 Path를 zsh에 등록하여 Xcode가 swift-format을 사용할 수 있게 해주는 거다. ~/.zshrc
에 Path를 추가해주면 된다.
# swift
export PATH="$PATH:$HOME/YOUR_PATH/swift-format/.build/release" # swift-format
# end swift
이제 Xcode에서 정상적으로 프로젝트를 빌드할 수 있다. 아래는 Build Phase 중, format Phase의 스크립트다.
# Import all env vars in zshrc
. ~/.zshrc
if which swift-format >/dev/null; then
swift-format format --configuration ${SRCROOT}/swift-format-configuration.json --in-place --recursive --parallel ${SRCROOT}
else
echo "warning: swift-format not installed"
exit 1
fi
아까 등록한 swift-format을 이용해서 전체 프로젝트를 포맷팅한다.
CI/CD 세팅
당연히 매번 빌드하는 것은 너무 귀찮기 때문에 GitHub Action을 이용한 CI/CD 세팅을 해줬다. 이 과정에서 정말 헤맸다. workflow 파일은 아래와 같다.
// ...
- name: Install the Apple certificate
env:
BUILD_CERTIFICATE_BASE64: $
P12_PASSWORD: $
KEYCHAIN_PASSWORD: $
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
- name: Install the swift-format
run: |
brew install swift-format
- name: Build project
run: |
xcodebuild build \
-project background-toy.xcodeproj \
-scheme background-toy \
-destination 'platform=OS X,arch=x86_64' \
-configuration Release \
-derivedDataPath build
- name: Zipping build output
run: |
cd build/Build/Products/Release
zip -r background-toy.zip background-toy.app
- name: Upload Build
uses: actions/upload-artifact@v3
with:
name: backgroud-toy app
path: build/Build/Products/Release/background-toy.zip
- name: Clean up keychain
if: ${{ always() }}
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
가장 먼저, Apple certificate 세팅이다. Installing an Apple certificate on macOS runners for Xcode development에 매우 자세히 설명이 나와있다. Secret으로 보안이 필요한 항목들을 뺄 수 있다. 로그를 찍을 때도 알아서 걸러주기 때문에 믿고 사용해도 괜찮다.
Build Phase에 formatting이 등록되어있기 때문에 당연히 CI 세팅에도 swift-format이 필요하다. 처음에는 Local에 설치한 것과 같은 방법으로 clone하려고 했으나…10분 이상 걸리는 어마어마하게 느린 속도에… brew로 설치하기로 했다. brew는 기본으로 있기 때문에 별도의 설치없이 사용할 수 있다.
Build 자체는 Local에서 하는 것과 동일하다. xcodebuild 커맨드를 이용하면 되고, 설정은 위와 같게 해줬다. Building from the Command Line with Xcode FAQ를 참고한다.
-derivedDataPath
을 지정해야 원하는 디렉토리에 빌드할 수 있다. 원래 Build 로그에 찍힌 path를 이용했는데 비정상적이라는 이야기를 듣고 수정했다..
이제 빌드한 파일을 업로드한다. actions/upload-artifact
을 사용한다. *.app은 사실상 디렉토리이기 때문에 파일처럼 지정해서 업로드할 수 없다. 이를 피하기 위해서 애플리케이션을 zip한 뒤에 업로드한다.
모든 과정이 끝났으면 keychain을 정리한다.
깃헙 액션은 커밋이 되어야 동작을 확인할 수 있기 때문에, 별도 브랜치를 따서 타겟에 넣어줬다. 수없이 많은 실패 후에 드디어 빌드가 올라갔다. 녹색 동그라미가 정말 반가웠다. 모두가 이 기쁨을 알았으면 좋겠다. 오늘 하루 참 좋다 대신에 오늘 왠지 녹색 동그라미가 더 와닿을 것 같다.
커스텀 가이드
프로젝트 문서에 적어뒀지만 여기에도 첨부한다.
JSON 파일 수정 방법
Finder로 Json 파일을 찾을 수 있다. Json 파일 편집기 추가 예정은 없다. (그래도 언젠가는…?)
맥 OS 애플리케이션은 기본적으로 폴더이다. 애플리케이션을 우클릭하고 패키지 콘텐츠 보기
를 선택하면 해당 콘텐츠 내용물에 액세스할 수 있다. ./Contents/Resources/
경로에 편집할 수 있는 Json 파일이 있다.
커스텀하고 싶다면 아래 가이드를 참고하면 된다.
커스텀 애니메이션
animation.json
파일을 편집하여 애니메이션을 커스텀할 수 있다. spriteFolderPath
를 애니메이션 이미지가 저장된 폴더의 경로로 변경한 후 프레임 수나 애니메이션 플레이 방식을 자신의 어셋에 맞게 수정하면 된다.
- 애니메이션 이미지 이름은 {animation name}_{index}.png 형식이어야 한다.
- idle, walk, grab, touch 및 playingcursor에 대해 각각 하나 이상의 이미지가 있어야 한다.
다음은 샘플 animation.json
파일이다.
{
"spriteFolderPath": "default",
"clips": {
"idle": {
"count": 3,
"playType": "pingpong"
},
"walk": {
"count": 7,
"playType": "restart"
},
"grab": {
"count": 3,
"playType": "pingpong"
},
"touch": {
"count": 2,
"playType": "restart"
},
"playingcursor": {
"count": 6,
"playType": "restart"
}
}
}
playType
은 애니메이션이 재생되는 방식을 말하며, “pingpong”(애니메이션이 끝나면 반대로 재생) 또는 “restart”(애니메이션이 끝나면 다시 시작)로 설정한다. count
는 애니메이션에 해당하는 이미지 개수다.
커스텀 매크로
macro.json
파일을 편집하여 커스텀 매크로를 설정할 수 있다. 매크로는 프로세스를 실행하고, 웹 URL을 열고, 채팅 메시지를 출력할 수 있다.
다음은 샘플 macro.json
파일이다.
{
"test1": [
{
"type": "process",
"payload": "/Applications/Discord.app"
},
{
"type": "web",
"payload": "https://www.google.com/"
},
{
"type": "chat",
"payload": "Hey! Let's play game."
}
],
"test2": [
{
"type": "web",
"payload": "https://www.naver.com/"
}
]
}
커스텀 대화
chat.json
파일을 편집하여 커스텀 대화 메시지를 설정할 수 있다. 채팅 메시지는 시간에 따라 오전, 점심, 오후, 저녁, 밤으로 분류하여 지정할 수 있다.
다음은 샘플 chat.json
파일이다.
{
"morning" : [
"morning data 1",
"morning data 2"
],
"lunch" : [
"launch data 1",
"launch data 2"
],
"afternoon" : [
"afternoon data 1",
"afternoon data 2"
],
"evening" : [
"evening data 1",
"evening data 2"
],
"night" : [
"night data 1",
"night data 2"
]
}
후기
꽤 재밌는 토이 프로젝트였다. 쉬엄쉬엄해서 개발 기간이 꽤 길었던 점이 아쉽지만 이것 저것 많이 배울 수 있었던 것 좋은 시간이었다. Xcode랑 친해지는 것은 조금 힘들었다… (아직 안 친한 듯)
작업에 들인 공은.. 세팅 > 리팩토링 > 코드 디테일 순서다. 작은 규모의 프로젝트다 보니까 디테일은 적었고, 처음 짜보는 스타일의 코드이다보니까 세팅 및 리팩토링 시간이 길었다.
event driven 방식으로 디자인하는 것이 정석이나, State 및 주기적 업데이트 처리로 구현했다. 좋지 않은 디자인일 수도 있으나, 움직임 및 애니메이션 처리에 적당한 선택이었다고 본다. Update 안에 프로그램 흐름이 보이는 편이 익숙하다. 그런 이유로, 이 프로젝트가 좋은 스위프트 예시라고는 생각하지 않는다. 조금 더 전형적인 방식으로 구현할 필요를 느낀다. 하지만 짧고 빠르게 만드는 것도 이 프로젝트에서 바라던 것이니 이 정도로 타협…! 하기로…!
앞으로도 짬짬히 다양한 기능을 추가할 예정이다.