- ์ฐ์ธ ๊ฐ ๋ฏธ์ ์น๋ถ๋ฅผ ํตํ ์์๊ถ ๋ด๊ธฐ ์๋น์ค ์ฑ
- ์ปคํ์ด ํจ๊ป ์ฌ์ฉํ๋ ์ฑ์ด๊ธฐ ๋๋ฌธ์ ๋ ๋ช ์ด ์๋ก ์ฐ๊ฒฐํด์ ์ฌ์ฉํ๋ ์ฑ์ ๋๋ค.
- ํด๋น ์ฑ์ ๋์์ธ ์์คํ ์ ์ํด SPM์ ํ์ฉํ์ต๋๋ค. ๋์์ธ ์์คํ ๋ฆฌ๋๋ฏธ ๋งํฌ
- ์งํ ๊ธฐ๊ฐ
- ๊ฐ๋ฐ : 2023.07 ~ 2023.12(์งํ์ค)
- ์ถ์
- 1.0.0 : 2023.12.11
- 1.0.1 : 2023.12.22(ํ์ด๋จธ, ๋ก๊ทธ์ธ ์์ )
- ๊ธฐ์ ์คํ
- iOS : UIKit, SwiftUI, Combine, SwiftLint
- Deployment Target : iOS 15.0
- ์ฆ์ ๊ธฐํ ๋ณ๊ฒฝ ๋ฐ ์ ์ง๋ณด์ ๋ฑ ์
๋ฌด ํจ์จ์ ๋์ด๊ธฐ ์ํด
ํด๋ฆฐ ์ํคํ ์ฒ
๋ฅผ ๋์ ์ค์ ๋๋ค.
Entity
- ์๋น์ค์ ์ฐ์ด๋ ๋ฐ์ดํฐ๋ฅผ ์ ์ํฉ๋๋ค. ์๋ฒ์์ ์ํต ์ค๋ฅ๋ก ๋ฐํ์์๋ฌ๋ฅผ ๋ง๊ธฐ ์ํด
์ต์ ๋
๋ก ์ฒ๋ฆฌํ์ต๋๋ค.
struct BattleHistoryResultDTO: Codable {
let roundGameId: Int?
let date: String?
let result: String?
let title: String?
let image: String?
let winner: String?
let myMission: MyMission
let partnerMission: PartnerMission
}
UseCase
- Entity๊ฐ ์ฌ์ฉ๋๋ ์๋๋ฆฌ์ค๋ฅผ ์ ์ํฉ๋๋ค.
protocol BattleHistoryResultUseCaseProtocol {
func execute() -> AnyPublisher<[BattleHistoryResultDTO], ErrorType>
}
final class BattleHistoryResultUseCase: BattleHistoryResultUseCaseProtocol {
private let battleHistoryResultRepository: BattleHistoryResultRepositoryInterface
init(battleHistoryResultRepository: BattleHistoryResultRepositoryInterface) {
self.battleHistoryResultRepository = battleHistoryResultRepository
}
func execute() -> AnyPublisher<[BattleHistoryResultDTO], ErrorType> {
return self.battleHistoryResultRepository.data()
}
}
Repository Interface
- ํด๋ฆฐ ์ํคํ
์ฒ ๋ค์ด์ด๊ทธ๋จ์ ๋ ์์ชฝ์ ์์นํ Domain ์์ญ์ด Data ์์ญ์ Repository์ ์ ๊ทผํ๊ธฐ ์ํด
์์กด์ฑ ์ญ์
๋ฅผ ํตํด ๋ ํผ์งํ ๋ฆฌ์ ์ ๊ทผํ๊ธฐ ์ํ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค.
protocol RoundBattleMissionRepositoryInterface {
func data(roundId: Int) -> AnyPublisher<RoundBattleMissionDTO, ErrorType>
}
Repository
- ๋ฐ์ดํฐ๋ฅผ ์ง์ ์ ์ผ๋ก ํ๋ํฉ๋๋ค. ๋ฐ์ดํฐ๋ฅผ ํ๋ํ๋ ๋ฐฉ๋ฒ์ ํ์ฅ์ฑ์๋๋ก ํ๊ธฐ ์ํด ์ฌ์ฉ๋ฉ๋๋ค.
final class RoundBattleMissionRepository: RoundBattleMissionRepositoryInterface {
private let service: GetServiceCombine
init(service: GetServiceCombine) {
self.service = service
}
func data(roundId: Int) -> AnyPublisher<RoundBattleMissionDTO, ErrorType> {
self.service.getService(from: Config.baseURL + "api/game/short/\(roundId)", isUseHeader: true)
}
}
View
- ๊ธฐ์กด MVC ํจํด ์ UIViewController์
loadView
์๋ช ์ฃผ๊ธฐ์ UIView๋ฅผ ๋ฐ๊ฟ์ฃผ์ด ViewController์ ๋ ์ด์์ ์ฝ๋๋ฅผ ์ต์ํํ๊ณ ์ ํ์ต๋๋ค.
// MARK: - Life Cycle
override func loadView() {
super.loadView()
historyView = HistoryView(frame: self.view.frame)
self.view = historyView
}
- ํ์ฌ๋ UIHostingController๋ฅผ ์ฌ์ฉํด SwiftUI๋ก UI๋ฅผ ๊ตฌ์ฑํ๊ณ ์์ต๋๋ค.
// MARK: - Setting
override func setConfig() {
battleHistoryHostingController = UIHostingController(rootView: BattleProgressView(data: battleHistoryViewData))
self.addChild(battleHistoryHostingController)
view.addSubview(battleHistoryHostingController.view)
battleHistoryHostingController.didMove(toParent: self)
}
override func setLayout() {
battleHistoryHostingController.view.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
ViewModel
- SwiftUI์ UIKit์ ๋ชจ๋ ๋น๋๊ธฐํ๋ก๊ทธ๋๋ฐ์ ์ ์ฉํ๊ณ ์
Combine
์ ํ์ต ์ค์ ๋๋ค. - UIKit์ ViewModel์
Input/Output Modeling
์ ํตํด View๋ก๋ถํฐ ์ ๋ฌ๋ ์ด๋ฒคํธ๋ Input, View๋ก ์ ๋ฌํ ๋ฐ์ดํฐ๋ Output์ ํตํด Bindingํฉ๋๋ค.
struct BattleHistoryDetailViewModel {
struct Input {
let viewLoad: AnyPublisher<BattleHistoryResultDTO, Never>
}
struct Output {
let historyDetailData: AnyPublisher<BattleHistoryDetailItemViewData, Never>
}
func transform(input: Input) -> Output {
let historyData = input.viewLoad.map {
return BattleHistoryDetailItemViewData(battleHistoryItem: $0)
}.eraseToAnyPublisher()
return Output(historyDetailData: historyData)
}
}
- SwiftUI์ ViewModel์
ObservableObject
์@Published
ํ๋กํผํฐ ํจํด๋ฅผ ํ์ฉํด Bindingํฉ๋๋ค.
final class MyPageViewModel: ObservableObject {
@Published var myPageName: String = ""
@Published var myPageDate: String = ""
@Published var startDate: String = ""
private var cancellables: Set<AnyCancellable> = []
private let myPageGetUseCase: MyPageGetUseCaseProtocol
private let navigationController: UINavigationController
private let viewController: UIViewController
init(myPageGetUseCase: MyPageGetUseCaseProtocol, navigationController: UINavigationController, viewController: UIViewController) {
self.myPageGetUseCase = myPageGetUseCase
self.navigationController = navigationController
self.viewController = viewController
}
// MARK: - Custom Method
func getMyPageData() {
myPageGetUseCase.excute().sink { [weak self] completion in
guard let self = self else { return }
switch completion {
case .failure(let errorType):
errorResponse(status: errorType)
case .finished:
break
}
} receiveValue: { [weak self] data in
guard let self = self else { return }
self.myPageDate = MyPageGetItemViewData(myPageGetItem: data).date
self.myPageName = MyPageGetItemViewData(myPageGetItem: data).name
self.startDate = data.startDate ?? ""
}
.store(in: &cancellables)
}
}
ItemViewData
- Entity๋ฅผ View์์ ์ฝ๊ฒ ์ฌ์ฉํ๊ธฐ ์ํ ๋ฐ์ดํฐ๋ก ๋ณํํ๋ ์์ ์ ์ํํฉ๋๋ค.
struct BattleHistoryItemViewData {
let date: String
let gameTitle: String
let imagePath: URL?
let result: String
let battleHistoryItem: BattleHistoryResultDTO
init(battleHistoryItem: BattleHistoryResultDTO) {
self.battleHistoryItem = battleHistoryItem
self.date = battleHistoryItem.date ?? ""
self.gameTitle = battleHistoryItem.title ?? ""
self.imagePath = URL(string: battleHistoryItem.image ?? "")
switch battleHistoryItem.result {
case "DRAW":
self.result = "๋ฌด์น๋ถ"
case "WIN":
self.result = "์น๋ฆฌ"
case "LOSE":
self.result = "ํจ๋ฐฐ"
default:
self.result = battleHistoryItem.result ?? ""
}
}
}
SwiftUI๋ฅผ ์ฌ์ฉํ๋ฉด์ ํ๋ฉด์ด ๋งค๋๋ฝ์ง ์๊ฒ ๋๊ปด์ง๋ ๊ฒฝ์ฐ๋ฅผ ์ข ์ข ๋ฐ๊ฒฌํจ. ์ด์ ๋ฐ๋ผ SwiftUI์ ์ฑ๋ฅ ํฅ์์ ์ํ ๊ณ ๋ฏผ์ ํ์์ฑ์ ๋๋
๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก ํด๊ฒฐํ๊ณ ์ ํ์ต๋๋ค.
- ๋ทฐ๋ฅผ ํ์ ๋ทฐ๋ก ๋๋๊ธฐ
- ๋ทฐ๋ฅผ ํ๋์ ๋ทฐ๋ก ์์ฑํ๋ค๋ฉด ๊ทธ์ ํ์ํ ๋ฐ์ดํฐ ๊ฐ์ด ๋ณ๊ฒฝ๋จ์ ๋ฐ๋ผ ๋ทฐ๊ฐ ๋ค์ ๊ทธ๋ ค์ง๊ฒ ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด 10๊ฐ์ ๋ฒํผ์ด ์์ผ๋ฉด ๊ฐ ๋ฒํผ์ด ๋๋ฆด ๋๋ง๋ค ๊ฐ๊ฐ์ ๋ฒํผ์ ํด๋นํ๋ ๋ทฐ๋ง ๋ค์ ๊ทธ๋ ค์ง๊ฒ ํ ์ ์์ง๋ง ํ๋์ ์ ์ฒด ๋ทฐ๊ฐ ๋ค์ ๊ทธ๋ ค์ง๊ฒ ๋๋ฉด ์ฑ๋ฅ์ ์ฐจ์ด๊ฐ ๋๊ฒ ๋ ๊ฒ์ ๋๋ค.
- ๋ค์๊ณผ ๊ฐ์ด ๋ฐ์ดํฐ๊ฐ ๋ฐ๋ ๋๋ง๋ค ์๊ด์๋ ๋ทฐ๊ฐ ๋ค์ ๊ทธ๋ ค์ง๋ ๊ฒ์ ๋ง๊ณ ์ ํ์ต๋๋ค.
- ์๋์ ์ฝ๋์ ๊ฐ์ด ํ์ ์ปจํ ์ด๋ ๋ทฐ๋ก ๋๋๊ฒ ๋๋ฉด ์์ ๋ทฐ์ ๋ฐ์ดํฐ๊ฐ ๋ค์ ๊ทธ๋ ค์ง๋ ๊ฒ๊ณผ ๋ณ๊ฐ๋ก ๋ค์ ๋ทฐ๊ฐ ๊ทธ๋ ค์ง์ง ์๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ ํฅ์์ ์ป์ ์ ์์ต๋๋ค.
ํ์ ๋ทฐ ๋ถ๋ฆฌ๋ก ๊ฐ์ ํ ์ฝ๋
struct TimerTimeTextView: View {
// MARK: - Property
let text: String
// MARK: - UI Property
var body: some View {
Text(text)
.font(Font(SDSFont.body1.font))
.foregroundColor(Color(.gray600))
.background(.random)
}
}
- ๋ทฐ๊ฐ ๊ผญ ํ์ํ ์ข ์์ฑ๋ง์ ๊ฐ๊ฒ ํ๊ธฐ
- ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ฅผ ํตํด ์ข ์์ฑ์ ์ฒดํฌํ๊ณ ์์ต๋๋ค.
let _ = Self._printChanges()
- ObservableObject์ ์ข ์์ฑ์ ๊ฐ์ง๊ณ ์๋ ๊ฒฝ์ฐ ํ์ด๋จธ๊ฐ ํ๋ฆ์ ๋ฐ๋ผ ๋ฐ์ดํฐ๊ฐ ๋ณํ๋๋ ๊ฒ์ ์ ์ ์์์ต๋๋ค.
์ข
์์ฑ์ ์ต์ํ ํ๋ฉด์ ๊ฐ์ ํ ์ฝ๋
TimerProgressView(remainingTime: timerData.remainingTime, totalTime: timerData.totalTime, isTimerRunning: timerData.isTimerRunning)
.padding(.horizontal, 24)
.padding(.top, 32)
- ๋ค์๊ณผ ๊ฐ์ด ๋ทฐ๊ฐ ObservableObject์ ์ข ์์ฑ์ ๊ฐ์ง๋ ๊ฒ์ด ์๋๋ผ ํ์ํ ๋ฐ์ดํฐ๋ง์ ๊ฐ์ง๊ฒ ๋จ์ผ๋ก์จ ์ ๋ฐ์ดํธ ํ์๋ฅผ ์ค์ผ ์ ์์์ต๋๋ค.
- ๊พธ์คํ MVCํจํด์์ MVVM + Clean Architecture ํจํด์ผ๋ก ์ ์ง๋ณด์๋ฅผ ํ ์์ ์ ๋๋ค.
- ๊ฐ๊ฐ ๋ถ๋ฆฌ๋ ๋ ์ด์ด๋ฅผ ํ ์คํธํ ์ ์๋ ๋ฐฉ๋ฒ์ ๊ณ ๋ฏผํด๋ณด๊ณ ์ถ์ต๋๋ค.
๐ ๋ก๊ทธ์ธ ํ๋ก์ฐ - ์นด์นด์ค, ์ ํ๋ก ๋ก๊ทธ์ธ์ ํ ์ ์์ด์. ์ปคํ์ด ์ฐ๊ฒฐ๋์ด ์๋ค๋ฉด ํ ํ๋ฉด, ์๋ค๋ฉด ์ปคํ์ ์ฐ๊ฒฐํ๋ ํ๋ก์ฐ๋ก ๋์ด๊ฐ์.
๐๏ธ ์น๋ถ ์์ฑ - ์ปคํ์ด ํจ๊ป ํ ์ ์๋ ๊ฒ์์ ์์ฑํ ์ ์์ด์. ์ด๋ฏธ ์๋๊ฐ ๊ฒ์์ ์์ฑํ๋ค๋ฉด ์์ฑํ ์ ์๋ค๋ ์๋์ด ๋ํ๋์.
๐๏ธ ์น๋ถ ๊ฒฐ๊ณผ ํ์ธ - ์๋๋ฐฉ์ ๊ฒฐ๊ณผ์ ๋น๊ตํ์ฌ ์นํจ๋ฅผ ์ ํ๊ณ ์์ง ์น๋ถ๊ฐ ๋์ง ์์๋ค๋ฉด ์๋์ด ๋ํ๋์.
๐ซ ์์๊ถ ์ฌ์ฉ - ์น๋ถ์์ ์ด๊ฒผ๋ค๋ฉด ์์๊ถ์ ์ฌ์ฉํ ์ ์์ด์.
๐ ์น๋ถ ํ์คํ ๋ฆฌ ํ๋ก์ฐ - ์ปคํ๊ณผ ํจ๊ป ํ ์น๋ถ์ ๊ธฐ๋ก๋ค์ ํ์ธํ ์ ์์ด์
โ๏ธ ๋ง์ดํ์ด์ง ํ๋ก์ฐ - ๋ด ์ ๋ณด๋ฅผ ์์ ํ ์ ์๊ณ ๋ก๊ทธ์์, ์ปคํ ์ฐ๊ฒฐ ๋๊ธฐ, ๊ณ์ ํํด๋ฅผ ํ ์ ์์ด์.
๐จ[FIX] : ๋ฒ๊ทธ, ์ค๋ฅ ํด๊ฒฐ
โ[ADD] : Feat ์ด์ธ์ ๋ถ์์ ์ธ ์ฝ๋ ์ถ๊ฐ, ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐ, ์๋ก์ด ํ์ผ ์์ฑ ์
โจ[FEAT] : ์๋ก์ด ๊ธฐ๋ฅ ๊ตฌํ
โ
[CHORE] : ์ฝ๋ ์์ , ๋ด๋ถ ํ์ผ ์์
โฐ๏ธ[DEL] : ์ธ๋ชจ์๋ ํ์ผ,์ฝ๋ ์ญ์
โป๏ธ[REFACTOR] : ์ ๋ฉด ์์ ์ด ์์ ๋ ์ฌ์ฉํฉ๋๋ค
๐[MERGE]: ๋ค๋ฅธ๋ธ๋ ์น๋ฅผ merge ํ ๋ ์ฌ์ฉํฉ๋๋ค.
๐ ๋ค์ด๋ฐ
- UpperCamelCase ์ฌ์ฉ
// - example
struct MyTicketResponseDTO {
}
class UserInfo {
}
- lowerCamelCase ์ฌ์ฉํ๊ณ ๋์ฌ๋ก ์์
// - example
private func setDataBind() {
}
- pop, push, present, dismiss
- ๋์ฌ + To + ๋ชฉ์ ์ง ๋ทฐ (๋ค์์ ๋ณด์ผ ๋ทฐ)
- dismiss๋ dismiss + ํ์ฌ ๋ทฐ
// - example pop, push, present
popToFirstViewController()
pushToFirstViewController()
presentToFirstViewController()
dismissFirstViewController()
- register + ๋ชฉ์ ์ด
// - example
registerXib()
registerCell()
- ์๋น์คํจ์๋ช + WithAPI
// - example
fetchListWithAPI()
requestListWithAPI()
fetch๋ ๋ฌด์กฐ๊ฑด ์ฑ๊ณต
request๋ ์คํจํ ์๋ ์๋ ์์ฒญ
- ๋์ฌ์ํ + ๋ชฉ์ ์ด + WithAnimation
showButtonsWithAnimation()
delegate ๋ฉ์๋๋ ํ๋กํ ์ฝ๋ช ์ผ๋ก ๋ค์์คํ์ด์ค๋ฅผ ๊ตฌ๋ถ
์ข์ ์:
protocol UserCellDelegate {
func userCellDidSetProfileImage(_ cell: UserCell)
func userCell(_ cell: UserCell, didTapFollowButtonWith user: User)
}
protocol UITableViewDelegate {
func tableview( ....)
func tableview...
}
protocol JunhoViewDelegate {
func junhoViewTouched()
func junhoViewScrolled()
}
Delegate ์์ชฝ์ ์๋ ๋จ์ด๋ฅผ ์ค์ฌ์ผ๋ก ๋ฉ์๋ ๋ค์ด๋ฐํ๊ธฐ
๋์ ์:
protocol UserCellDelegate {
// userCellDidSetProfileImage() ๊ฐ ์ณ์
func didSetProfileImage()
func followPressed(user: User)
// `UserCell`์ด๋ผ๋ ํด๋์ค๊ฐ ์กด์ฌํ ๊ฒฝ์ฐ ์ปดํ์ผ ์๋ฌ ๋ฐ์ (userCell ๋ก ํด์ฃผ์)
func UserCell(_ cell: UserCell, didTapFollowButtonWith user: User)
}
ํจ์ ์ด๋ฆ ์์๋ ๋๋๋ก์ด๋ฉด get
์ ๋ถ์ด์ง ์์ต๋๋ค.
- lowerCamelCase ์ฌ์ฉ
let userName: String
- ๊ฐ case ์๋ lowerCamelCase ์ฌ์ฉ
enum UserType {
case viewDeveloper
case serverDeveloper
}
์ฝ์ด๋ก ์์ํ๋ ๊ฒฝ์ฐ ์๋ฌธ์๋ก ํ๊ธฐ, ๊ทธ ์ธ์๋ ํญ์ ๋๋ฌธ์
// ์ข์ ์:
let userID: Int?
let html: String?
let websiteURL: URL?
let urlString: String?
// ๋์ ์:
let userId: Int?
let HTML: String?
let websiteUrl: NSURL?
let URLString: String?
setUI() : @IBOutlet ์์ฑ ์ค์
setLayout() : ๋ ์ด์์ ๊ด๋ จ ์ฝ๋
setDataBind() : ๋ฐฐ์ด ํญ๋ชฉ ์ธํ
. ์ปฌ๋ ์
๋ทฐ ์์ ๋ฆฌ์คํธ ์ด๊ธฐ ์ธํ
ํ ๋
setAddTarget() : addtarget ๋ชจ์
setDelegate() : delegate, datasource ๋ชจ์
setCollectionView() : ์ปฌ๋ ์
๋ทฐ ๊ด๋ จ ์ธํ
setTableView() : ํ
์ด๋ธ๋ทฐ ๊ด๋ จ ์ธํ
initCell() : ์
๋ฐ์ดํฐ ์ด๊ธฐํ
registerXib() : ์
xib ๋ฑ๋ก.
setNotification() : NotificationCenter addObserver ๋ชจ์
ํท๊ฐ๋ฆฐ๋ค? set์ ์ฐ์ธ์ ^^
๐ ์ฝ๋ ๋ ์ด์์
-
๋ค์ฌ์ฐ๊ธฐ์๋ ํญ(tab) ๋์ 4๊ฐ์ space๋ฅผ ์ฌ์ฉํฉ๋๋ค.
-
์ฝ๋ก (
:
)์ ์ธ ๋์๋ ์ฝ๋ก ์ ์ค๋ฅธ์ชฝ์๋ง ๊ณต๋ฐฑ์ ๋ก๋๋ค.let names: [String: String]?
let name: String
-
์ฐ์ฐ์ ์ค๋ฒ๋ก๋ฉ ํจ์ ์ ์์์๋ ์ฐ์ฐ์์ ๊ดํธ ์ฌ์ด์ ํ ์นธ ๋์ด์๋๋ค.
func ** (lhs: Int, rhs: Int)
-
ํจ์๋ฅผ ํธ์ถํ๋ ์ฝ๋๊ฐ ์ต๋ ๊ธธ์ด๋ฅผ ์ด๊ณผํ๋ ๊ฒฝ์ฐ์๋ ํ๋ผ๋ฏธํฐ ์ด๋ฆ์ ๊ธฐ์ค์ผ๋ก ์ค๋ฐ๊ฟํฉ๋๋ค. ํ๋ผ๋ฏธํฐ๊ฐ 3๊ฐ ์ด์์ด๋ฉด ์ค๋ฐ๊ฟํ๋๋ก!!
๋จ, ํ๋ผ๋ฏธํฐ์ ํด๋ก์ ๊ฐ 2๊ฐ ์ด์ ์กด์ฌํ๋ ๊ฒฝ์ฐ์๋ ๋ฌด์กฐ๊ฑด ๋ด๋ ค์ฐ๊ธฐํฉ๋๋ค.
UIView.animate( withDuration: 0.25, animations: { // doSomething() }, completion: { finished in // doSomething() } )
-
if let
๊ตฌ๋ฌธ์ด ๊ธธ ๊ฒฝ์ฐ์๋ ์ค๋ฐ๊ฟํ๊ณ ํ ์นธ ๋ค์ฌ์๋๋ค.if let user = self.veryLongFunctionNameWhichReturnsOptionalUser(), let name = user.veryLongFunctionNameWhichReturnsOptionalName(), user.gender == .female { // ... }
-
guard let
๊ตฌ๋ฌธ์ด ๊ธธ ๊ฒฝ์ฐ์๋ ์ค๋ฐ๊ฟํ๊ณ ํ ์นธ ๋ค์ฌ์๋๋ค.else
๋ ๋ง์ง๋ง ์ค์ ๋ถ์ฌ์ฐ๊ธฐguard let user = self.veryLongFunctionNameWhichReturnsOptionalUser(), let name = user.veryLongFunctionNameWhichReturnsOptionalName(), user.gender == .female else { return } guard let self = self else { return } (X) guard let self = self else { return } (O)
-
else ๊ตฌ๋ฌธ์ด ๊ธธ ์ ์ค๋ฐ๊ฟ
-
ํด๋์ค ์ ์ธ ๋ค์์ , extension ๋ค์์ ํ ์ค ๋์ด์ฃผ๊ธฐ
-
๋น ์ค์๋ ๊ณต๋ฐฑ์ด ํฌํจ๋์ง ์๋๋ก ํฉ๋๋ค. ( ๋์ด์ฐ๊ธฐ ์ธ๋ฐ์์ด ๋ฃ์ง ๋ง๊ธฐ )
-
๋ชจ๋ ํ์ผ์ ๋น ์ค๋ก ๋๋๋๋ก ํฉ๋๋ค. ( ๋์ ์ํฐ ํ๋ ๋ฃ๊ธฐ)
-
MARK ๊ตฌ๋ฌธ ์์ ์๋์๋ ๊ณต๋ฐฑ์ด ํ์ํฉ๋๋ค.
// MARK: Layout override func layoutSubviews() { // doSomething() } // MARK: Actions override func menuButtonDidTap() { // doSomething() }
๋ชจ๋ ์ํฌํธ๋ ์ํ๋ฒณ ์์ผ๋ก ์ ๋ ฌํฉ๋๋ค. ๋ด์ฅ ํ๋ ์์ํฌ๋ฅผ ๋จผ์ ์ํฌํธํ๊ณ , ๋น ์ค๋ก ๊ตฌ๋ถํ์ฌ ์๋ํํฐ ํ๋ ์์ํฌ๋ฅผ ์ํฌํธํฉ๋๋ค.
import UIKit
import Moya
import SnapKit
import SwiftyColor
import Then
import UIKit
import SwiftyColor
import SwiftyImage
import JunhoKit
import Then
import URLNavigator
๐ ํด๋ก์
-
ํ๋ผ๋ฏธํฐ์ ๋ฆฌํด ํ์ ์ด ์๋ Closure ์ ์์์๋
() -> Void
๋ฅผ ์ฌ์ฉํฉ๋๋ค.์ข์ ์:
let completionBlock: (() -> Void)?
๋์ ์:
let completionBlock: (() -> ())? let completionBlock: ((Void) -> (Void))?
-
Closure ์ ์์ ํ๋ผ๋ฏธํฐ์๋ ๊ดํธ๋ฅผ ์ฌ์ฉํ์ง ์์ต๋๋ค.
์ข์ ์:
{ operation, responseObject in // doSomething() }
๋์ ์:
{ (operation, responseObject) in // doSomething() }
-
Closure ์ ์์ ๊ฐ๋ฅํ ๊ฒฝ์ฐ ํ์ ์ ์๋ฅผ ์๋ตํฉ๋๋ค.
์ข์ ์:
..., completion: { finished in // doSomething() }
๋์ ์:
..., completion: { (finished: Bool) -> Void in // doSomething() } completion: { data -> Void in // doSomething() } (X)
-
Closure ํธ์ถ์ ๋๋ค๋ฅธ ์ ์ผํ Closure๋ฅผ ๋ง์ง๋ง ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๋ ๊ฒฝ์ฐ, ํ๋ผ๋ฏธํฐ ์ด๋ฆ์ ์๋ตํฉ๋๋ค.
์ข์ ์:
UIView.animate(withDuration: 0.5) { // doSomething() }
๋์ ์:
UIView.animate(withDuration: 0.5, animations: { () -> Void in // doSomething() })
๐ ์ฃผ์
์ฝ๋๋ ๊ฐ๋ฅํ๋ฉด ์์ฒด์ ์ผ๋ก ๋ฌธ์๊ฐ ๋์ด์ผ ํ๋ฏ๋ก, ์ฝ๋์ ํจ๊ป ์๋ ์ธ๋ผ์ธ(inline) ์ฃผ์์ ํผํ๋ค.
class ViewController: UIViewController {
// MARK: - Property
// MARK: - UI Property
// MARK: - Life Cycle
// MARK: - Setting
// MARK: - Action Helper
// MARK: - @objc Methods
// MARK: - Custom Method
}
// MARK: - Extensions
์ปค์คํ ๋ฉ์๋, ํ๋กํ ์ฝ, ํด๋์ค์ ๊ฒฝ์ฐ์ ํตํฌํ ์ฃผ์ ๋ฌ๊ธฐ
/// (์๋จธ๋ฆฌ ๋ถ๋ถ)
/// (๋์คํฌ๋ฆฝ์
๋ถ๋ถ)
class MyClass {
let myProperty: Int
init(myProperty: Int) {
self.myProperty = myProperty
}
}
/**summary
(์๋จธ๋ฆฌ ๋ถ๋ถ)
> (๋์คํฌ๋ฆฝ์
๋ถ๋ถ)
- parameters:
- property: ํ๋กํผํฐ
- throws: ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด customError์ ํ ์ผ์ด์ค๋ฅผ throw
- returns: "\\(name)๋ ~" String
*/
func printProperty(property: Int) {
print(property)
}
- ์ฐธ๊ณ :
๐ ํ๋ก๊ทธ๋๋ฐ ๊ถ์ฅ์ฌํญ
์ข์ ์:
let name: String = "์ฒ ์"
let height: Float = "10.0"
๋์ ์:
let name = "์ฒ ์"
let height = "10.0"
ํ๋กํ ์ฝ์ ์ ์ฉํ ๋์๋ extension์ ๋ง๋ค์ด์ ๊ด๋ จ๋ ๋ฉ์๋๋ฅผ ๋ชจ์๋ก๋๋ค.
์ข์ ์:
final class MyViewController: UIViewController {
// ...
}
// MARK: - UITableViewDataSource
extension MyViewController: UITableViewDataSource {
// ...
}
// MARK: - UITableViewDelegate
extension MyViewController: UITableViewDelegate {
// ...
}
๋์ ์:
final class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// ...
}
// ํ๋กํ ์ฝ ์ฌ๋ฌ๊ฐ๋ฅผ ํ๊ณณ์ ๋ชฐ์์ ๋๋ ค๋ฃ์ง ๋ง์!
๐ ๊ธฐํ๊ท์น
-
self
๋ ์ต๋ํ ์ฌ์ฉ์ ์ง์ โ**์์๋ฑ๊น์ผ selfโฆ**
-
viewDidLoad()
์์๋ ํจ์ํธ์ถ๋ง -
delegate ์ง์ , UI๊ด๋ จ ์ค์ ๋ฑ๋ฑ ๋ชจ๋ ํจ์์ ์ญํ ์ ๋ฐ๋ผ์ extension ์ผ๋ก ๋นผ๊ธฐ
-
ํ์์๋ ์ฃผ์ ๋ฐ Mark ๊ตฌ๋ฌธ๋ค ์ ๊ฑฐ
-
deinit{}
๋ชจ๋ ๋ทฐ์ปจ์์ ํ์ฑํ -
guard let
์ผ๋ก unwrapping ํ ์, nil ๊ฐ๋ฅ์ฑ์ด ๋์ ๊ฒฝ์ฐ์๋else{}
์์print()
ํด์ ๋๋ฒ๊น ํ๊ธฐ ์ฝ๊ฒ ๋ง๋ค๊ธฐ -
return
์ฌ์ฉ์ ๋ ์ค ์ด์ ์ฝ๋๊ฐ ์์ ์, ํ ์ค ๋๊ณreturn
์ฌ์ฉfunc fetchFalse() -> Bool { return false } (O) func isDataValid(data: Data?) -> Bool { guard let data else { return false } return true } (O) func isDataValid(data: Data?) -> Bool { guard let data else { return false } return true } (X)
์ฝ์ด ์ง์
โ TVC๋ณด๋ค๋ TableViewCell
- set_ ํํ๋ก ์์ฑ
โ setUI, setData
setLayout()
,setStyle()
,setDelegate()
class ViewController: UIViewController { // MARK: - Property // MARK: - UI Property // MARK: - Life Cycle // MARK: - Setting // MARK: - Action Helper // MARK: - Custom Method } // MARK: - UITableView Delegate
- ๋งํฌ ์ฃผ์ ๋ฏธ์ฌ์ฉ์ ์ญ์
ํ๋กํผํฐ ์์ฑ์ ๋ ์ด์์ ์์๋๋ก์ง๋ง, collectionView, tableView๋ ์ต์๋จ์ ์ ์์๋ค
private let tableView: UITableView = { let view = UITableView() // ... return view }() private let view = UIView() private let view2 = UIView()
๋ทฐ์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๋ด๋นํ๋ ํจ์ ์์๋, ์ง์ ์ ์ธ ๊ตฌํ ๋ณด๋ค๋ ํจ์ ํธ์ถ๋ง ์งํ
~~override viewDidLoad() { super.viewDidLoad() self.view.addsubView(uniView) }~~
override viewDidLoad() { super.viewDidLoad() self.addUniView() } private func addUniView() { super.viewDidLoad() self.view.addsubView(uniView) }
SPM์ ์ด์ฉ
Kingfisher //์ด๋ฏธ์ง์ฒ๋ฆฌ
Alamofire // ๋คํธ์ํฌ
Snapkit //๋ ์ด์์
Then //์ฝ๋ ๊ฐ๊ฒฐํ
Sentry //error tracking
kakao-ios-sdk //socialLogin
firebase-auth //socialLogin