Open Taehyeon-Kim opened 2 years ago
์ฐ๋ฆฌ์ ๋ง์ ์์ฉ ํ๋ก๊ทธ๋จ์ ์ผ๋ฐ์ ์ผ๋ก iPhone๊ณผ ๊ฐ์ ๋จ์ผ ๊ธฐ๊ธฐ์ ์ด์ ์ ๋ง์ถฅ๋๋ค. ์ฌ์ฉ๋์ด ๋์ด๋จ์ ๋ฐ๋ผ Mac, Watch, ๊ธฐํ Apple ํ๋ซํผ ๋ฐ ์ฅ์น์ ๊ฐ์ ์ถ๊ฐ ์ฅ์น๋ก ๊ฐ์ ธ์ค๊ณ ์ถ์ดํฉ๋๋ค. Xcode๋ ์ด๋ฌํ ํ๋ซํผ์ ์ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ฑํ๊ณ ๊ตฌ์ถํ๋๋ฐ ๋์์ด ๋ฉ๋๋ค. ํ๋ซํผ๋ณ ์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋์์ ๊ฐ ์ฅ์น์ ๊ณ ์ ํ ์ธก๋ฉด์ ์์ฉํ๋ฉด์ ํจํค์ง๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๋๋ฅผ ๊ณต์ ํ ์ ์์ต๋๋ค. ์์คํ ์ด ์ฑ์ฅํ๊ณ ๋ฐ์ ํจ์ ๋ฐ๋ผ์ ์์ฉ ํ๋ก๊ทธ๋จ์ ์ข ์ข ์๋ฒ ๊ตฌ์ฑ ์์๋ก ํด๋ผ์ด์ธํธ ์์ฉ ํ๋ก๊ทธ๋จ์ ๋ณด์ํด์ผ ํฉ๋๋ค.
์๋ฅผ ๋ค์ด, ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ํํ ์ ์๋ ์์ ์ ์คํ๋ก๋ํ๊ฑฐ๋ ๊ณ์ฐ๋์ด ๋ง์ ์์ ์ ์คํ๋ก๋ํ๊ฑฐ๋ ์ฅ์น์์ ์ฌ์ฉํ ์ ์๋ ๋ฐ์ดํฐ์ ์ก์ธ์คํด์ผ ํ๋ ์์ ์ด ์์ต๋๋ค. ์๋ฒ ๊ตฌ์ฑ ์์๋ ํด๋ผ์ด์ธํธ ์๋์ ๋ค๋ฅธ ๋๊ตฌ์ ๋ฐฉ๋ฒ๋ก ์ ์ฌ์ฉํ์ฌ ๊ตฌ์ถํด์ผ ํ๋ฏ๋ก ๋น์ฉ๊ณผ ๋ ธ๋ ฅ์ด ๋ญ๋๋ค. ์๋ฒ๋ฅผ ๊ตฌ์ถํ๊ธฐ ์ํด Swift๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ฌํ ๊ธฐ์ ๊ฒฉ์ฐจ๋ฅผ ํด์ํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
Swift๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฒ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋น๋ํ๋ ๋ฐฉ๋ฒ์ ์ดํด๋ด ์๋ค.
์๋ฒ ์ ํ๋ฆฌ์ผ์ด์ ์ Swift ํจํค์ง๋ก ๋ชจ๋ธ๋ง๋ฉ๋๋ค. ํจํค์ง๋ ์ ํ๋ฆฌ์ผ์ด์ ์ง์ ์ ์ ๋งคํํ๋ ์คํ๊ฐ๋ฅํ ๋์์ ์ ์ํฉ๋๋ค. ์ธ์ ์ ์์์์๋ Vapor Web Framework๋ฅผ ์ฌ์ฉํฉ๋๋ค.
// Package.swift
import PackageDescription
let package = Package(
name: "MyServer",
platforms: [.macOS("12.0")],
products: [
.executable(
name: "MyServer",
targets: ["MyServer"]),
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "4.0.0")),
],
targets: [
.executableTarget(
name: "MyServer",
dependencies: [
.product(name: "Vapor", package: "vapor")
]),
.testTarget(
name: "MyServerTests",
dependencies: ["MyServer"]),
]
)
// Main : ์ง์
์
import Vapor
@main
public struct MyServer {
public static func main() async throws {
let webapp = Application()
webapp.get("greet", use: Self.greet) // HTTP Endpoint
webapp.post("echo", use: Self.echo)
try webapp.run()
}
static func greet(request: Request) async throws -> String {
return "Hello from Swift Server"
}
static func echo(request: Request) async throws -> String {
if let body = request.body.string {
return body
}
return ""
}
}
// ์คํ ํ์ ํ์ธํด๋ด
์๋ค.
curl http://127.0.0.1:8080/greet; echo // Get ์์ฒญ
curl http://127.0.0.1:8080/echo --data "Hello, world"; echo // Post ์์ฒญ
SwiftUI ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณด๊ณ ๊ตฌ์ถํด๋์ ์๋ฒ์ ํต์ ํด๋ณด๊ฒ ์ต๋๋ค.
// MyServerClient.swift
import Foundation
struct MyServerClient {
let baseURL = URL(string: "http://127.0.0.1:8080")!
func greet() async throws -> String {
let url = baseURL.appendingPathComponent("greet")
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: url))
guard let responseBody = String(data: data, encoding: .utf8) else {
throw Errors.invaildResponseEncoding
}
return responseBody
}
enum Errors: Error {
case invaildResponseEncoding
}
}
// ContentView.swift
import SwiftUI
struct ContentView: View {
@State var serverGreeting = ""
var body: some View {
Text(serverGreeting)
.padding()
.task {
do {
let myServerClient = MyServerClient()
self.serverGreeting = try await myServerClient.greet()
} catch {
self.serverGreeting = String(describing: error)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
AWS, Google Cloud, Azure, Heroku ๋ฐ ๊ธฐํ ์ฌ๋ฌ ํด๋ผ์ฐ๋ ์ ๊ณต์ ์ฒด๊ฐ ์์ง๋ง ์์์์๋ Heroku๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์๊ท๋ชจ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ๋ฐฐํฌ ์์คํ ์ ํธ๋ฆฌํฉ๋๋ค. git push๋ฅผ ์ด์ฉํด ์์ฝ๊ฒ ๋ฐฐํฌํ ์ ์์ต๋๋ค.
Heroku ๋ฅผ ์ด์ฉํด์ ์๋ฒ๋ฅผ ๋ฐฐํฌํด๋ณด์ธ์
Xcode๋ฒ์ ๊ณผ iOS๋ฒ์ ์ด ๋ง์ง ์์ ์คํ์ด ์ ๋๋ ๊ฒ ๊ฐ์ต๋๋ค.
iOS ๋๋ macOS ์ ํ๋ฆฌ์ผ์ด์ ์ ๋น๋ํ๊ธฐ ์ํด ์ด๋ฏธ Swift๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด ์์คํ ์ ์๋ฒ ์ธก ๊ฐ๋ฐ์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. Xcode๋ ํ๋์ ์์ ๊ณต๊ฐ์์ ํด๋ผ์ด์ธํธ์ ์๋ฒ ๋ชจ๋ ์์คํ ์ ๋ค์ํ ๊ตฌ์ฑ ์์๋ฅผ ๊ฐ๋ฐํ๊ณ ๋๋ฒ๊ทธํ๋๋ฐ ๋์์ด ๋ฉ๋๋ค. Swift ๊ธฐ๋ฐ ์๋ฒ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐฐํฌํ๊ธฐ ์ํ ํด๋ผ์ฐ๋ ๊ณต๊ธ์๋ฅผ ์ ํํ ์๋ ์์ต๋๋ค.
์ฐ๋ฆฌ๊ฐ ํธ๋ ํธ๋ญ์ ์ด์ฉํ๊ธฐ ์ํด ์ฑ์ ์ฌ์ฉํ๋ค๊ณ ๊ฐ์ ํด๋ณธ์ฑ๋ก ์ดํดํด๋ณด๋๋ก ํ ๊ฒ์.
์์ ์ฑ์ ๋ค์ฌ๋ค๋ณด๋ฉด ๋ฐ์ดํฐ๊ฐ ํ๋์ฝ๋ฉ๋ ์ฑ๋ก ๊ด๋ฆฌ๋๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ๊ทธ๋ ๋ค๋ฉด ์ ์ ๋ ์ค์ ๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ๋ฅผ ๋ณผ ์๋ ์๊ฒ ๋ค์. ์๊ท๋ชจ ํธ๋ ํธ๋ญ ์ด์ฉ์๋ ๊ด์ฐฎ์ ์ ์๊ฒ ์ง๋ง ์ฌ๋ฌ ๊ฐ์ ํธ๋ ํธ๋ญ์์ ํ๋์ ์๋น์ค๋ฅผ ์ด์ฉํด์ ์ด์ฉ์ ํ๋ค๊ณ ์๊ฐ์ ํด๋ณด๋ฉด ๋นํจ์จ์ ์ธ ๋ฐฉ์์ด ๋ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค.
์ฐ๋ฆฌ๋ ์ง๊ธ ๋ฉ๋ด๊ฐ ์ค์ ์ง์คํ๋๊ณ ๋ชจ๋ ๊ณ ๊ฐ ์๋น์ค๋ฅผ ๊ด๋ฆฌํ๋ ๋๋ ์ ๊ตญ์ ๋ง๋ค๊ณ ์ถ์ ๊ฒ์ ๋๋ค. ์ค์ ์ง์ค ์์คํ ์ ์ด๋ป๊ฒ ๋ง๋ค ์ ์์์ง ์๊ฐํด๋ณด์ฃ .
๋ฉ๋ชจ๋ฆฌ ๋ด ์ ์ฅ์๊ฐ ์๋ ์ฑ์์ ์์ํด๋ณผ๊น์?
๋ฉ๋ด๋ฅผ ์ค์ ์ง์คํํ๊ธฐ ์ํด์ iOS ์ฑ์์ ์คํ ๋ฆฌ์ง๋ฅผ ์ถ์ถํ์ฌ ์๋ฒ๋ก ์ด๋ํ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ฑ์ ๋ชจ๋ ์ฌ์ฉ์๊ฐ ๋์ผํ ์ ์ฅ์๋ฅผ ๊ณต์ ํ ์ ์๊ธฐ ๋๋ฌธ์ ๋์ผํ ๋๋ ๋ฉ๋ด๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ฒซ๋ฒ์งธ ์์ ์ ๋ง์ฐฌ๊ฐ์ง๋ก HTTP ๊ธฐ๋ฐ API์ ๋ ธ์ถ๋์ด ์์ต๋๋ค. iOS ์ฑ์ ์ด๋ฌํ API ์์ ์ ์ํด ์ถ์ํ๋ฅผ ์ฌ์ฉํ ๋ค์ Presentation Layer(์ด ์์์๋ SwiftUI)์ ์ฐ๊ฒฐํฉ๋๋ค.
@main
struct FoodTruckServerBootstrap {
public static func main() async throws {
let foodTruckServer = FoodTruckServer()
let webapp = Application()
webapp.get("donuts", use: foodTruckServer.listDonuts) // Set Endpoints
try webapp.run()
}
}
struct FoodTruckServer {
func listDonuts(request: Request) -> Response.Donuts{
return Reponse.Donuts(donuts: [])
}
enum Response {
struct Donuts: Content {
var donuts: [Model.Donut]
}
}
}
enum Model {
struct Donut: Encodable {
public var id: UUID
public var name: String
public var date: String
public var dough: Dough
public var glaze: Glaze?
public var topping: Topping?
}
struct Dough: Encodable {
}
struct Glaze: Encodable {
}
struct Topping: Encodable {
}
}
struct FoodTruckServer {
private let storage = Storage()
func listDonuts(request: Request) -> Response.Donuts {
let donuts = self.storage.listDonuts()
return Response.Donuts(donuts: [])
}
enum Response {
struct Donuts: Content {
var donuts: [Model.Donut]
}
}
}
struct Storage {
let donuts = [Model.Donut]()
func listDonuts() -> [Model.Donut] {
return self.donuts
}
}
์๋ฒ ์ธก ์ ํ๋ฆฌ์ผ์ด์ ์ ์ค๊ณํ ๋ ํญ์ ํฅ๋ฏธ๋ก์ด ์ฃผ์ ์ ๋๋ค. ์ฌ์ฉ ์ฌ๋ก(Use case)์ ๋ฐ๋ผ ์ ํํ ์ ์๋ ๋ช ๊ฐ์ง ์ ๋ต์ด ์์ต๋๋ค.
๋ฐ๋ชจ์์๋ ์ ์ ํ์ผ ์ ์ฅ ์ ๋ต(JSON ์ด์ฉ)๋ง ๋ค๋ฃน๋๋ค.
Storage์ ๋ณ๊ฒฝ ๊ฐ๋ฅํ donuts ๋ณ์๊ฐ ์๊ณ load ๋ฐ listDonuts ๋ฉ์๋๊ฐ ๋์์ ์ก์ธ์คํ ์ ์๊ธฐ ๋๋ฌธ์ Actor๋ฅผ ์ฌ์ฉํฉ๋๋ค. Swift 5.5์ ์ฒ์ ๋์ ๋ Actor๋ ๋ฐ์ดํฐ ๊ฒฝ์์ ํผํ๊ณ ๊ณต์ ๋ ์์์ ์์ ํ์ง๋ง ์ฌ์ด ๋ฐฉ๋ฒ์ผ๋ก ์ฒ๋ฆฌํ๋๋ก ๋์์ค๋๋ค. Actor๊ฐ ๋์ ๋๊ธฐ ์ ์๋ Lock ๋๋ ํ(๋๊ธฐ์ด)์ ์ด์ฉํด์ ์ฒ๋ฆฌ๋ฅผ ํ์ต๋๋ค.
์๋ฒ ์ถ์ํ(abstraction)์ bootstrap๋ฉ์๋๋ฅผ ์ถ๊ฐํ๊ณ ๊ฑฐ๊ธฐ์์ ์คํ ๋ฆฌ์ง๋ฅผ ๋ก๋ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ bootstrap์ ์คํ ๊ฐ๋ฅํ ์ง์ ์ ์ ์ฐ๊ฒฐํฉ๋๋ค. ์คํ ๋ฆฌ์ง๋ ์ด์ ์กํฐ์ด๋ฏ๋ก ๋น๋๊ธฐ ์ปจํ ์คํธ์์ ์ ๊ทผํ๋๋ก ํฉ๋๋ค. ์๋ฒ API๋ฅผ ์บก์ํํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
๊ธฐ์กด ๋ฐฉ์๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก URLSession์ ์ด์ฉํด์ HTTP ์์ฒญ์ ๋ง๋ค๊ณ JSONDecoder๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฒ ์๋ต์ ๋์ฝ๋ฉํ๊ณ JSON์์ iOS ์ ํ๋ฆฌ์ผ์ด์ ๋ชจ๋ธ๋ก ๋ณํํฉ๋๋ค.
iOS์ ์๋ฒ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ชจ๋ธ์ ๊ณต์ ํจ์ผ๋ก์จ ๋ฐ๋ณต์ ํผํ๊ณ ์ง๋ ฌํ๋ฅผ ๋ ์์ ํ๊ฒ ๋ง๋ค ์ ์์ต๋๋ค. ๋ค์์ ๋ฐ์ดํฐ ๋ชจ๋ธ ์ฝ๋๋ฅผ Shared ํ๋ ์์ํฌ๋ก ์ฎ๊ฒจ์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.