์๋ ํ์ธ์ Foma ๐ ์ ๋๋ค!
์ ๋ฒ ๊ธ์์ RIBs ์ ๋ํ ์ด๋ก ์ ๋ค๋ค๋๋ฐ์.
(ํน์ ์๋ณด์ ๋ถ๋ค์ ์ฌ๊ธฐ ์์ ๋ณด๊ณ ์์ฃผ์ธ์~)
์ค๋์ Uber์์ ์ง์ ์ ๊ณตํ๋ ํํ ๋ฆฌ์ผ์ ํ๋ฒ ๋ฐ๋ผํด๋ณด๋ฉด์ ๊ตฌํํด๋ณด๋ ค๊ณ ํฉ๋๋ค!
๋ฐ๋ก ์์ํ ๊ฒ์~
ํ๋ก์ ํธ ์์ฑ
๊ฐ์ฅ ๋จผ์ RIBs ํํ ๋ฆฌ์ผ์ ๋ฐ๋ผํ ํ๋ก์ ํธ๋ฅผ ์์ฑํด์ค์ผ๊ฒ ์ฃ ?
์ ๋ RIBs Example๋ก ์ด๋ฆ ์ง๊ฒ ์ต๋๋ค.
Pod init & install
์ ๋ CocoaPod์ ์ด์ฉํด์ RIBs๋ฅผ ์ค์นํ๋๋ก ํ๊ฒ ์ต๋๋ค.
ํฐ๋ฏธ๋์์ ํ๋ก์ ํธ๊ฐ ์๋ ๊ฒฝ๋ก๋ก ์ด๋ํด pod init์ ํด์ฃผ์๋ฉด pod file์ด ์๊ธธ๊ฑฐ์์.
ํํ์ผ์ ์๋ ์ฝ๋๋ฅผ ์ถ๊ฐํฉ๋๋ค. (๋ฒ์ ์ ๋ฐ๋ ์ ์์ผ๋ ๊ณต์ ๊นํ์์ ํ์ธํด์ฃผ์ธ์!)
pod 'RIBs', '~> 0.9'
์ถ๊ฐํ ๋ค์ pod install์ ํด์ค RIBs๋ฅผ ์ค์นํด์ค๋๋ค.
ํ๋ ์์ํฌ ์ค์น
RIBs๋ ์์ฒด ํ๋ ์์ํฌ๋ฅผ Xcode์์์ ์ฌ์ฉํ ์ ์๋๋ก ๋ง๋ค์ด์ก์ต๋๋ค.
RIBs ๊ณต์ ๊นํ์ผ๋ก ์ด๋ํด์ฃผ์ธ์.
Download ZIP์ ๋๋ฌ์ ๋ค์ด๋ฐ์ ์ฃผ์ธ์.
ํฐ๋ฏธ๋๋ก ๋ค์ ์ด๋ํด์ ๋ค์ด๋ฐ์ผ์ ํ์ผ ์ค RIBs-master/ios/tooling/install-xcode-template.sh๋ฅผ ๋๋๊ทธ ํด์ ๊ฐ์ ธ๊ฐ์ค๋๋ค.
์ด๋ ๊ฒ ์ฑ๊ณต์ ์ผ๋ก ์ค์น๋์๋ค๋ฉด success!๊ฐ ๋ฐ๊ฑฐ์์.
Xcode
์์ค์ฝ๋ ํ๋ก์ ํธ๋ฅผ ์คํํด์ฃผ์๋๋ฐ ์ฃผ์ํ ์ ์ ํ๋ก์ ํธ๋ช .xworkspace๋ก ์คํ์์ผ์ค์ผ ํฉ๋๋ค.
LoggedOut RIB
์ด์ RIB ํ๋ ์์ํฌ๋ฅผ ์์ฑํด๋ณผ๊น์?
์๋ก์ด ํ์ผ ์์ฑ์ ๋๋ฅด๊ณ ์๋๋ก ์ญ ๋ด๋ฆฌ๋ฉด ์๋์ ๊ฐ์ด RIB ํ์ผ๋ค์ด ์์๊ฑฐ์์.
Storyboard๋ XIB๋ฅผ ๋ง๋์ค๊ฑฐ๋ฉด ์๋์์ Adds XIB file or Adds Storyboard file์ ๋๋ฌ์ฃผ์ธ์.
(์ ๋ ์คํ ๋ฆฌ๋ณด๋๋ฅผ ๋ฃ์ด์คฌ์ต๋๋ค.)
์ด๋ฆ์ LoggedOut์ผ๋ก ์ง์ด์ฃผ์ธ์ (ํํ ๋ฆฌ์ผ์ ๊ทธ๋ ๊ฒ ๋์ด์์)
์ด๋ ๊ฒํ๋ฉด Router,ViewController,Builder,Interactor,Storyboard 5๊ฐ์ ํ์ผ์ด ์๋์ผ๋ก ๋ง๋ค์ด์ง๋๋ค.
๊ฐ์ฅ ๋จผ์ ์คํ ๋ฆฌ๋ณด๋๋ก ์ด๋ํด์ LoggedOut ๋ ์ด์์์ ํด์ฃผ๊ฒ ์ต๋๋ค.
ํ๋ ์ด์ด1,ํ๋ ์ด์ด2์ ์ด๋ฆ์ ์ ์ ํ ์คํธํ๋ 2๊ฐ์ ๋ก๊ทธ์ธ ๋ฒํผ ํ๋๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
๋ทฐ์ปจํธ๋กค๋ฌ์ ์ฐ๊ฒฐํด์ฃผ๊ณ Login ๋ฒํผ์ ํญํ์ ๋ ์ก์ ๋ ์ฐ๊ฒฐํด์ค๋๋ค.
import RIBs
import RxSwift
import UIKit
protocol LoggedOutPresentableListener: AnyObject {
}
final class LoggedOutViewController: UIViewController, LoggedOutPresentable, LoggedOutViewControllable {
@IBOutlet weak var player1NameTF: UITextField!
@IBOutlet weak var player2NameTF: UITextField!
@IBOutlet weak var loginButton: UIButton!
weak var listener: LoggedOutPresentableListener?
@IBAction func tapLoginButton(_ sender: Any) {
}
}
์์ชฝ์ LoggedOutPresentableListener ํ๋กํ ์ฝ์ด ์์๊ฑฐ์์.
์ด ๋ฆฌ์ค๋๋ ๋ทฐ์ปจํธ๋กค๋ฌ์์ ์ผ์ด๋ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ ๋ก์ง ํจ์๋ฅผ ์ ์ํฉ๋๋ค.
๋ก๊ทธ์ธ๋ฒํผ์ ๋๋ ์ ๋ ๋น์ง๋์ค ๋ก์ง์ ์ฒ๋ฆฌํ ํจ์๋ฅผ ์ ์ํ๊ฒ ์ต๋๋ค.
protocol LoggedOutPresentableListener: AnyObject {
func handleLogin(player1Name:String,player2Name:String)
}
๋ก๊ทธ์ธ ๋ฒํผ์ ๋๋ ์ ๋ ๋ฆฌ์ค๋์ ํธ๋ค๋ก๊ทธ์ธ ํจ์๋ฅผ ์คํํด์ค๋๋ค.
ํ๋ผ๋ฏธํฐ๋ก๋ ํ๋ ์ด์ด1์ ์ด๋ฆ, ํ๋ ์ด์ด2์ ์ด๋ฆ์ ๋๊ฒจ์ฃผ๋๋ก ํฉ๋๋ค.
@IBAction func tapLoginButton(_ sender: Any) {
listener?.handleLogin(player1Name: player1NameTF.text ?? "", player2Name: player2NameTF.text ?? "")
}
์ ๋ฒ ๊ธ์ ๋น์ง๋์ค ๋ก์ง์ ์ฒ๋ฆฌํ๋ ๋ถ๋ถ์ด ์ด๋๋ผ๊ณ ํ์ฃ ?
๋ฐ๋ก ์ธํฐ๋ํฐ ์ ๋๋ค.
LoggedOutInteractor๋ก ์ด๋ํด์ค๋๋ค.
์ธํฐ๋ํฐ ๋ ๋ถ๋ถ์ LoggedOutPresentableListener์์ ์ ์ํ ํจ์๋ฅผ ๊ตฌํํด์ค๋๋ค.
์ด๋ ๊ฒ ํจ์ ์ด๋ฆ์ ์กฐ๊ธ๋ง ์ณ๋ ์๋ ์์ฑ์ด ๋ ๊ฑฐ์์!
๊ฐ๋จํ๊ฒ ํ๋ ์ด์ด1,ํ๋ ์ด์ด2์ ์ด๋ฆ์ ์ถ๋ ฅ๋ง ํ๋ ๋ก์ง์ ์์ฑํ๊ฒ ์ต๋๋ค.
final class LoggedOutInteractor: PresentableInteractor<LoggedOutPresentable>, LoggedOutInteractable, LoggedOutPresentableListener {
weak var router: LoggedOutRouting?
weak var listener: LoggedOutListener?
...
func handleLogin(player1Name: String, player2Name: String) {
print(player1Name,player2Name)
}
}
์ด๋ ๊ฒ ํ๊ณ ์คํ์ ๋๋ฅด๋ฉด ๊น๋ง ํ๋ฉด๋ง ๋์ฌ๊ฑฐ์์.
์๋ํ๋ฉด ์ด๊ธฐ ํ๋ฉด์ ์ ํด์ฃผ์ง ์์๊ธฐ ๋๋ฌธ์ด์ฃ .
RootRIB
์ด๊ธฐRIB์ ๋ง๋ค์ด์ค๋๋ค.
์ด๋ฆ์ Root๋ก ํ ๊ฒ์.
RootBuilder
Builder๋ RIB์ ์์ฑํด์ฃผ๋ ์ญํ ์ ํฉ๋๋ค.
๊ณ ๋ก RootBuilder์์ ์์์ธ LoggedOutRIB์ ์์ฑํด์ค์ผ ๋๊ฒ ์ฃ ?
build ๋ฉ์๋์ LoggedOut๋น๋๋ฅผ ์์ฑํด์ค๋๋ค.
์ฌ๊ธฐ์ 2๊ฐ์ง ์ค๋ฅ๊ฐ ๋ ๊ฑฐ์์.
ํ๋๋ component์ ๋ํ ๊ฒ์ธ๋ฐ ์์ง RootComponent์ LoggedOutDependency๋ฅผ ์ฃผ์ ํด์ฃผ์ง ์์๊ธฐ ๋๋ฌธ์ด๊ณ ,
ํ๋๋ RootRouter์ ์ด๊ธฐํ์์ ์์ง LoggedOutBuilder๋ฅผ ๊ตฌํํ์ง ์์๊ธฐ ๋๋ฌธ์ด์์.
func build() -> LaunchRouting {
let viewController = RootViewController()
let component = RootComponent(dependency: dependency,
rootViewController: viewController)
let interactor = RootInteractor(presenter: viewController)
let loggedOutBuilder = LoggedOutBuilder(dependency: component)
return RootRouter(interactor: interactor,
viewController: viewController,
loggedOutBuilder: loggedOutBuilder)
}
RootComponent + LoggedOut
Component๋ ํด๋น RIB์ Dependency ๊ด๋ฆฌํ๋ ์ญํ ์ ํฉ๋๋ค.
์๋์ ๊ฐ์ด RootComponent์ LoggedOutDependency๋ฅผ ์ฑํํด์ค๋๋ค.
import RIBs
protocol RootDependencyLoggedOut: Dependency {
}
extension RootComponent: LoggedOutDependency {
}
RootRouter
RootRouter์์ loggedOutBuilder๋ฅผ ์ด๊ธฐํํด์ค๋๋ค.
final class RootRouter: LaunchRouter<RootInteractable, RootViewControllable>, RootRouting {
private let loggedOutBuilder: LoggedOutBuildable
init(interactor: RootInteractable,
viewController: RootViewControllable,
loggedOutBuilder: LoggedOutBuildable) {
self.loggedOutBuilder = loggedOutBuilder
super.init(interactor: interactor, viewController: viewController)
interactor.router = self
}
์ด ๋ LoggedOutRIB์์ ์ผ์ด๋๋ ์ด๋ฒคํธ๋ฅผ ๊ฐ์งํ๊ธฐ ์ํด์ RootInteractable ํ๋กํ ์ฝ์ LoggedOutListener๋ฅผ ์ฑํํด์ค๋๋ค.
protocol RootInteractable: Interactable, LoggedOutListener,LoggedInListener {
var router: RootRouting? { get set }
var listener: RootListener? { get set }
}
RootViewController
๋ค๋ง RootRIB์ด๋ผ๋ ๊ฒ์ ์์์ฑ ์ ์๊ฒ ๋ ์ด๋ธ์ ์ถ๊ฐํด์ค๋๋ค.
let rootLabel:UILabel = UILabel()
๋ ์ด์์์ ํด์ฃผ์๊ณ
func layout() {
self.view.backgroundColor = .white
rootLabel.text = "์ฌ๊ธฐ๋ ๋ฃจํธ๋ฆฝ์
๋๋ค."
view.addSubview(rootLabel)
rootLabel.translatesAutoresizingMaskIntoConstraints = false
rootLabel.centerXAnchor.constraint(equalTo:view.centerXAnchor)
.isActive = true
rootLabel.centerYAnchor.constraint(equalTo:view.centerYAnchor)
.isActive = true
}
๋ทฐ๋๋๋ก๋์ ์คํ์์ผ์ค๋๋ค.
override func viewDidLoad() {
super.viewDidLoad()
layout()
}
SceneDelegate
์ฌ๋๋ฆฌ๊ฒ์ดํธ๋ก ์ด๋ํด์ RIBs๋ฅผ import ํด์ฃผ๊ณ window์ launchRouter๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
๊ทธ ๋ค์ scene ๋ฉ์๋์ ์๋์ ๊ฐ์ด ๋ถ์ฌ๋ฃ์ด์ฃผ์ธ์.
(๋์ถฉ RootBuilder๋ก ์ด๊ธฐ๋ฅผ ์ค์ ํ๋ค๋ ๋ด์ฉ๊ฐ์์)
import UIKit
import RIBs
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private var launchRouter: LaunchRouting?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
self.window = window
let launchRouter = RootBuilder(dependency: AppComponent()).build()
self.launchRouter = launchRouter
launchRouter.launchFromWindow(window)
}
}
AppComponent
์ ์ฌ๋๋ฆฌ๊ฒ์ดํธ์์ AppComponent ๋ถ๋ถ์ ์๋ฌ๊ฐ ๋ ๊ฑฐ์์.
์ฑ์ปดํฌ๋ํธ ํ์ผ์ ๋ง๋ค์ด์ค๋๋ค.
import RIBs
class AppComponent: Component<EmptyDependency>, RootDependency {
init() {
super.init(dependency: EmptyComponent())
}
}
์ด์ ์ฑ์ ์คํ์์ผ๋ณด๋ฉด?
์์ง๋ ๊ฒ์ ํ๋ฉด ์ผ๊ฑฐ์์.. (ํ..ํํ ๋ฆฌ์ผ ํ๊ธฐ ํ๋๋ค..)
๊ธฐ์กด์ ์๋ ViewController์ Main.storyboard ํ์ผ์ ์ง์๋ฒ๋ฆฌ๊ณ
Target - info๋ก ์ด๋ํด์ค๋๋ค.
Application Scene Manifest - Scene Configuration - Application Session Role - Item 0 - Storyboard Name
์ ์ง์์ฃผ์ธ์!
๊ทธ๋ฆฌ๊ณ Main storyboard file base name์ด ์์๊ฑฐ์์.
์ด๊ฒ๋ ์ง์์ค๋๋ค
๊ทธ๋ฆฌ๊ณ ์คํํด์ฃผ์๋ฉด ์๋์ ๊ฐ์ด LoggedOutViewController๋ฅผ ๋ณผ ์ ์๊ฒ ๋ฉ๋๋ค... ๋๋์ด...!
์ด์ ํ๋ ์ด์ด1 ์ด๋ฆ๊ณผ ํ๋ ์ด์ด2 ์ด๋ฆ์ ์ ๊ณ Login์ ํด์ฃผ์๋ฉด
์๋์ ๊ฐ์ด ํ๋ ์ด์ด1,ํ๋ ์ด์ด2 ์ด๋ฆ์ด ์ถ๋ ฅ๋๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค!!
์ด๋ฒ ํํ ๋ฆฌ์ผ์ ํตํด์ ViewController์์ ์ผ์ด๋ ์ด๋ฒคํธ๋ค์ Listener๋ก ๊ฐ์งํ๊ณ Interactor์ ์ ๋ฌํ์ฌ
Interactor์์ ๋น์ง๋์ค ๋ก์ง์ ์ฒ๋ฆฌํ๋๋ก ํ์ต๋๋ค.
์ฒ์ ์ธํ ํ๊ณ ํ๋ ๊ฑฐ๋ผ ์ด๋ ค์์ด ๋ง์์ง๋ง ํํ ๋ฆฌ์ผ ํ๋๋ฅผ ๋๋ด๋ ๋ฟ๋ฏํ๋ค์..
ํน์๋ผ๋ ๊ถ๊ธํ์ ์ ์ด๋ ํ๋ฆฐ ๋ถ๋ถ์ด ์๋ค๋ฉด ๋๊ธ๋ก ์๋ ค์ฃผ์ธ์!
'๐ iOS > Architecture' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[RIBs] Memo ์ญ์ ,์์ ๊ตฌํํด๋ณด๊ธฐ (0) | 2021.08.26 |
---|---|
[RIBs] Router๋ฅผ ์ด์ฉํด ํ๋ฉด ์ ํํด๋ณด๊ธฐ (feat. Memo) (0) | 2021.08.24 |
[Design Pattern] RIBs๋?(feat. Uber) (0) | 2021.08.18 |
[Design Pattern] MVP ํจํด์ด๋? (0) | 2021.08.11 |
[Design Pattern] ReactorKit์ด๋? (0) | 2021.06.17 |
๋๊ธ