- ํฌํ ๋ถ์ค, ๋ฐฐ๊ฒฝ ๋ฐ ํํฐ, ํฌ์ฆ ๊ณต์
- ๋ค์ปท ์ฌ์ง ํฌ์คํธ
- ๊ทผ์ฒ์ ์๋ ํฌํ ๋ถ์ค ์์น ํ์
- ๋ค์ปท์ฌ์ง๊ณผ ๊ด๋ จ๋ ์ํ ๊ฒฐ์
- UIKit, RxSwift, RxCocoa
- MVVM - InOut, Router ํจํด
- CoreLocation
- NaverMapSDK
- Alamofire
- iamport_ios
- SnapKit
- Kingfisher
- Toast
ํ ํฐ์ ์ฌ์ฉํ๋ API์์ ํ ํฐ ๋ง๋ฃ ์ ๋ฆฌํ๋ ์ ํ ํฐ์ผ๋ก ํ ํฐ ๊ฐฑ์ , ๋ฆฌํ๋ ์ ํ ํฐ ๋ง๋ฃ ์ ๋์
๐ก ๋ฌธ์ ์ํฉ: ํ ํฐ ๋ง๋ฃ ์ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ ์, ์ค๊ฐ์ ๊ฐ๋ก์ฑ ์์ ํ ๋ค ๋ค์ ์๋ฒ๋ก ๋ณด๋ด๋ Interceptor๋ฅผ ํ์ฉํ์ฌ retry ๋ฉ์๋๋ก ๊ฐฑ์ ํ๋ ค ํ์ผ๋, retry ๋ฉ์๋๊ฐ ๋ฌดํ ์คํ๋๋ ๋ฌธ์ ๋ฐ์ โ ์ด๋ป๊ฒ ๋ฌดํ ์คํ์ ๋ฉ์ถ๊ณ ํ ํฐ ๊ฐฑ์ ์ ํ ์ ์์๊น?
- Router ํจํด & TargetType ์ ์ฉ์ผ๋ก Router๋ฅผ ํตํด ๋ง๋ฃ๋ ํ ํฐ์ด ํฌํจ๋ Request๋ฅผ ์ ์ฉํ๊ณ ์์
- retry ๋ฉ์๋์ request๋ ํ ํฐ ๊ฐฑ์ ์ ์ request๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์
- adapt ๋ฉ์๋์ request Header์ ๊ฐฑ์ ๋ ํ ํฐ์ ์ ๋ณด๋ฅผ ๋ฃ์ด์ฃผ๊ณ retry ๋ฉ์๋๋ฅผ ์คํ์์ผ์ผ ์ ์์ ์ผ๋ก ์๋ํจ
- ๋ฆฌํ๋ ์ ํ ํฐ๊น์ง ๋ง๋ฃ๋๋ฉด ์ ์ฅ๋์ด ์๋ ๋ชจ๋ ์ ๋ณด๋ฅผ ์ญ์ ํ๊ณ , ๋ก๊ทธ์ธ ํ๋ฉด์ ๋์
- ์ ์์ ์ธ ๋ก๊ทธ์ธ์ด ๊ฐ๋ฅํ๋ค๋ฉด ํ ํฐ๊ณผ, ๋ฆฌํ๋ ์ ํ ํฐ์ ๋ฐ๊ธ๋ฐ์ ์ ์๋๋ก ๋์
PG ๊ฒฐ์ ํ๋ก์ฐ์ ์์์ฆ ๊ฒ์ฆ
๐ก ๋ฌธ์ ์ํฉ: ๋ค์ปท ์ฌ์ง๊ณผ ๊ด๋ จ๋ ์ํ์ ํ๋งคํ๊ณ ์ถ์๋ฐ ์ด๋ป๊ฒ ๊ฒฐ์ ์์คํ ์ ์ ์ฉ์ํค๊ณ ์ค์ ๊ฒฐ์ ๊ฐ ์ผ์ด๋ฌ๋์ง ํ์ธํ ์ ์์๊น?
-
ํตํฉ ๊ฒฐ์ API์ธ ํฌํธ์์ ํ์ฉํ์ฌ ๊ฒฐ์ ๋ํ์ฌ(PG) ์ฐ๊ฒฐ
- ๊ฒฐ์ ๋ฒํผ์ ํญํ์ ๋ ๊ฒฐ์ ์ ๋ณด ์ ๋ ฅ
input.payTap .subscribe(with: self) { owner, value in let payment = IamportPayment( pg: PG.html5_inicis.makePgRawName(pgId: "INIpayTest"), merchant_uid: "ios_\(APIKey.sesacKey)_\(Int(Date().timeIntervalSince1970))", amount: "\(value.1)").then { $0.pay_method = PayMethod.card.rawValue $0.name = "4cut.zip" $0.buyer_name = "์๋ณด๋ผ" $0.app_scheme = "sesac" } outputPayment.onNext((payment, value.0)) } .disposed(by: disposeBag)
-
์ค์ ๊ฒฐ์ ๊ฐ ๋์๋์ง ํ์ธํ๊ธฐ ์ํด
- ๊ฒฐ์ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์์์ฆ ๊ฒ์ฆ์ ํตํด ์ค์ ๊ฒฐ์ ๋ด์ญ์ด ์๋์ง ํ์ธ ํ
- ์ ์ ์๊ฒ ํ ์คํธ ๋ฉ์์ง๋ฅผ ๋์ ์ฃผ๊ณ ๋ก์ง์ ์ฒ๋ฆฌ
output.payment
.subscribe(with: self) { owner, payment in
Iamport.shared.payment(viewController: owner,
userCode: "imp57573124",
payment: payment.0) { paymentResult in
NetworkManager.shared.validPayment(impUid: paymentResult.imp_uid, postId: payment.1) { value in
switch value {
case .success(let success):
owner.makeToast(title: "๊ฒฐ์ ", message: "๊ฒฐ์ ๊ฐ ์๋ฃ๋์์ด์!")
case .failure(let failure):
self.view.makeToast("๊ฒฐ์ ๊ฐ ์คํจํ์ต๋๋ค")
}
}
}
}
.disposed(by: disposeBag)์ฌ์ฉ์์ ์์ดํฐ ์์ฒด์ ์์น์๋น์ค๊ฐ ๊บผ์ ธ์๊ฑฐ๋, ์ฑ์ ์์น์๋น์ค๋ฅผ ํ์ฉํ์ง ์์ ์ํ์ ๋์
๐ก ๋ฌธ์ ์ํฉ: CoreLocation์ ํ์ฉํ์ฌ ์ฌ์ฉ์์ ์์น๋ฅผ ๋ฐ์์ ์ฃผ๋ณ์ ํฌํ ๋ถ์ค๋ค์ ํ์ํด ์ฃผ๊ณ ์ถ์๋ฐ ์์น ์๋น์ค ์ํ์ ๋ฐ๋ผ ์ฑ์ด ๊ทธ๋ฅ ๊บผ์ง๊ธฐ๋ ํ์ฌ ์์ธํ๊ณ ์ ์ ํ ๋์์ด ํ์ํ๊ณ , ์ฑ์ ์ฌ์ฉํ๋ค๊ฐ ์ค์ ์ ๋ณ๊ฒฝ๊น์ง ํ์ธํ์ฌ ๋์ํด์ผ ํ์ง ์์๊น?
- ์ด๋ ํ ์ด์ ๋ก ํ์ฌ ์์น๊ฐ ๋จ์ง ์๋์ง์ ๋ํ ์์ธํ ์ด์ ๋ฅผ ํ ์คํธ ๋ฉ์ธ์ง๋ก ๋์์ฃผ๊ณ
- ๋ํดํธ ์ฃผ์๋ฅผ ์ค์ ํ์ฌ ์ง๋๋ฅผ ๋์์ค
Create์ Update๋ฅผ ํ ๋ฉ์๋์์ ์ฒ๋ฆฌํ๊ธฐ
๐ก ๋ฌธ์ ์ํฉ: ๊ธ์ ํฌ์คํธ ํ๊ฑฐ๋, ์์ ํ๋ ๋ก์ง์ ํ ํ๋ฉด์์ ๊ตฌํํ๊ธฐ ๋๋ฌธ์ ์ ๋ก๋ ๋ฒํผ์ด tap ๋์์ ๋ multipartFormData ํ์ ์ผ๋ก ์ด๋ฏธ์ง๋ฅผ ์ฌ๋ฆฌ๋ ๊ฒ๊น์ง๋ ๋์ผํ๋ ๋คํธ์ํน ํ๋ ๋ถ๋ถ๋ถํฐ ๋ถ๊ธฐ์ฒ๋ฆฌ๊ฐ ํ์ํ๋ฐ ์ด๋ป๊ฒ ์ต์ํ์ ์ฝ๋๋ก ๋ ๊ฐ์ง ๋ก์ง์ ๋์ํ ์ ์์๊น?
- ์ด๋ฏธ์ง๋ฅผ ์
๋ก๋ํ๋ ๋คํธ์ํน์ ์คํํ๊ณ ์คํจํ์ ๋๋ ์ด๋ฏธ์ง ์
๋ก๋ ์คํจ์ ๋ํ ํ ์คํธ ๋ฉ์์ง๋ฅผ ํ์ํ๊ณ ์ฑ๊ณตํ์ ๋๋
- Update์ ๊ฒฝ์ฐ์๋ ์ด๋ฏธ PostId๋ฅผ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์
- PostId ์ ๋ฌด๋ก ๋ถ๊ธฐ์ฒ๋ฆฌํ์ฌ ๊ฐ๊ฐ์ ๋ผ์ฐํฐ์ request๋ก ๋คํธ์ํน ์ฒ๋ฆฌ๋ก
- ํ ๋ฉ์๋๋ก ๋ ๊ฐ์ง ๋ก์ง ๋์
input.uploadButtonTap
.subscribe(with: self) { owner, contentValue in
let imageContents = contentValue.0
let stringContent = contentValue.1
let postId = contentValue.2
do {
AF.upload(multipartFormData: { multipartFormData in
imageContents.forEach { image in
if let convertedImage = image.jpegData(compressionQuality: 0.1) {
multipartFormData.append(convertedImage, withName: "files", fileName: "\(stringContent).png", mimeType: "image/png")
}
}
}, with: try Router.uploadPhoto.asURLRequest())
.responseDecodable(of: PhotoListModel.self) { [weak self] response in
guard let self else { return }
switch response.result {
case .success(let value):
Observable.just(value)
.flatMap { photo -> Single<Result<PostContent, NetworkError>> in
if let id = postId {
NetworkManager.shared.callRequestWithToken(router: .editPost(id: id, content: Content(content: stringContent, product_id: "4cut_photo", files: photo.files)))
} else {
NetworkManager.shared.callRequestWithToken(router: .postContent(content: Content(content: stringContent, product_id: "4cut_photo", files: photo.files)))
}
}
.subscribe(with: self, onNext: { owner, value in
switch value {
case .success:
owner.popNavi.onNext(true)
case .failure:
failUploadString.onNext(true)
}
})
.disposed(by: self.disposeBag)
case .failure:
failUploadImage.onNext(true)
}
}
} catch {
failNetworking.onNext(true)
}
}
.disposed(by: disposeBag)