[Swift - UIKit] BaseView, APIService 싱글턴 패턴, DispatchGroup 등을 활용한 간단한 영화 평점 매기기 서비스를 구현해보았다.
한결

class BaseView: UIView {
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
configureSubView()
configureLayout()
configureUI()
}
func configureSubView() {}
func configureLayout() {}
func configureUI() {}
}// BaseLabel
class BaseLabel: UILabel {
enum LabelType {
case normal, title, subTitle, date, error
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
convenience init(_ t: String, size: CGFloat, type: LabelType) {
self.init(frame: .zero)
text = t
configureFontByType(size: size, type: type)
}
private func configureFontByType(size: CGFloat, type: LabelType) {
switch type {
case .normal, .date:
font = .systemFont(ofSize: size)
textColor = .systemGray
case .title, .subTitle:
font = .boldSystemFont(ofSize: size)
textColor = .black
case .error:
font = .boldSystemFont(ofSize: size)
textColor = ._grayDark
textAlignment = .center
}
}
func changeText(_ t: String) {
text = t
}
func changeColor(_ c: UIColor) {
textColor = c
}
}class BaseBoxItem: BaseView {
let back = UIView()
let sectionTitle = BaseLabel("", size: 14, type: .title)
override func configureSubView() {
self.addSubview(back)
back.addSubview(sectionTitle)
}
override func configureLayout() {
back.snp.makeConstraints {
$0.top.equalTo(self.safeAreaLayoutGuide).inset(12)
$0.bottom.equalTo(self.safeAreaLayoutGuide)
$0.horizontalEdges.equalTo(self.safeAreaLayoutGuide).inset(16)
}
sectionTitle.snp.makeConstraints {
$0.top.equalTo(back.safeAreaLayoutGuide).inset(8)
$0.horizontalEdges.equalTo(back.safeAreaLayoutGuide).inset(20)
$0.height.equalTo(22)
}
}
override func configureUI() {
back.backgroundColor = .systemGray6
back.layer.cornerRadius = 8
}
}class SomeViewController: BaseViewController {
private let mainView = SomeMainView()
override func loadView() {
self.view = mainView
}
override func viewDidLoad() {
super.viewDidLoad()
mainView.label.xxx // 변경된 뷰 객체의 속성들에 접근하는 형식으로 코드를 작성한다.
}
}class APIService {
private init() {}
static let manager = APIService()
private let base = BASE
private let headers = HEADERS
enum ServiceType {
case current
case trend
case search(query: String)
case detail(id: Int)
}
private func serviceEndPoint(_ type: ServiceType) -> String {
switch type {
case .current:
return "movie/now_playing"
case .trend:
return "trending/movie/day"
case .search(let query):
return "search/movie?query=\(query)"
case .detail(let id):
return "movie/\(String(id))"
}
}
private func serviceMethod(_ type: ServiceType) -> HTTPMethod {
switch type {
case .current, .trend, .search, .detail:
return .get
}
}
private func genRequest(_ type: ServiceType) -> DataRequest {
return AF.request(
self.base + self.serviceEndPoint(type),
method: self.serviceMethod(type),
parameters: ["language": "ko-KR"],
encoding: URLEncoding(destination: .queryString),
headers: self.headers
)
}
typealias SuccessHandler<T: Decodable> = (T) -> ()
typealias ErrorHandler = (any Error) -> ()
func fetch<T: Decodable>(
_ serviceType: ServiceType,
successHandler: @escaping SuccessHandler<T>,
errorHandler: @escaping ErrorHandler) {
self.genRequest(serviceType).responseDecodable(of: T.self) { response in
do {
let data = try response.result.get()
successHandler(data)
} catch {
errorHandler(error)
}
}
}
}override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
func fetchData() {
let group = DispatchGroup()
[.current, .trend, .topRated].enumerated().forEach { (index, type) in
group.enter()
DispatchQueue.global().async(group: group) {
APIService.manager.fetch(type) { (data: Movie) in
self.listDatas[index] = data.results
group.leave()
} errorHandler: { error in
group.leave()
}
}
}
group.notify(queue: .main) {
self.mainView.table.reloadSections(IndexSet(integer: 0), with: .none)
}
}