Skip to content

Commit 98923d2

Browse files
committed
first update for concurrency
1 parent 4b4eb76 commit 98923d2

10 files changed

Lines changed: 96 additions & 153 deletions

.DS_Store

0 Bytes
Binary file not shown.

ICTMDBHomeModule/HomeInteractor.swift

Lines changed: 14 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import Foundation
99
import ICTMDBNetworkManagerKit
1010

1111

12-
final class HomeInteractor: PresenterToInteractorHomeProtocol {
13-
12+
final class HomeInteractor: PresenterToInteractorHomeProtocol,@unchecked Sendable {
13+
1414
// MARK: - Properties
1515

1616
/// Reference to the Presenter to send data or errors back.
17-
var presenter: (any InteractorToPresenterHomeProtocol)?
17+
weak var presenter: (any InteractorToPresenterHomeProtocol)?
1818

1919
/// Network manager responsible for API requests.
2020
private let network: NetworkManagerProtocol
@@ -26,41 +26,20 @@ final class HomeInteractor: PresenterToInteractorHomeProtocol {
2626
self.network = network
2727
}
2828

29-
// MARK: - Popular TV Shows
30-
31-
/// Requests popular TV shows data
32-
func loadPopularMovies() async {
33-
let request = PopularMoviesRequest(
34-
language: deviceLanguageCode == .turkish ? .tr : .en,
35-
page: 1
36-
)
37-
38-
do {
39-
let list = try await network.execute(request)
40-
presenter?.sendPopularTvShows(list.results)
41-
} catch {
42-
presenter?.sendError(.popular)
43-
}
44-
}
45-
46-
47-
48-
// MARK: - Airing Today TV Shows
49-
50-
/// Requests airing today TV shows data
51-
52-
func loadAiringMovies() async {
53-
let request = AiringTodayRequest(
54-
language: deviceLanguageCode == .turkish ? .tr : .en,
55-
page: 1
56-
)
29+
func loadData() async {
30+
let popularMoviesRequest = PopularMoviesRequest(language: deviceLanguageCode == .turkish ? .tr : .en, page: 1)
31+
let airingTodayRequest = AiringTodayRequest(language: deviceLanguageCode == .turkish ? .tr : .en, page: 1)
32+
async let popularMovies = network.execute(popularMoviesRequest)
5733

34+
async let airingMovies = network.execute(airingTodayRequest)
35+
5836
do {
59-
let list = try await network.execute(request)
60-
presenter?.sendAiringTvShows(list.results)
37+
let (popularResult, airingResult) = try await (popularMovies, airingMovies)
38+
39+
await presenter?.sendPopularTvShows(popularResult.results)
40+
await presenter?.sendAiringTvShows(airingResult.results)
6141
} catch {
62-
presenter?.sendError(.airingToday)
63-
42+
await presenter?.sendError()
6443
}
6544
}
6645
}

ICTMDBHomeModule/HomePresenter.swift

Lines changed: 28 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ final class HomePresenter {
5353
/// Handles actions triggered by the View layer.
5454

5555
extension HomePresenter: ViewToPresenterHomeProtocol {
56+
57+
5658

5759
func viewDidLoad() {
5860
view?.setBackColorAble(color: "backColor")
@@ -62,27 +64,14 @@ extension HomePresenter: ViewToPresenterHomeProtocol {
6264
Task {@MainActor in
6365
await loadData()
6466
}
65-
66-
6767
}
6868

69-
7069

7170
@MainActor
7271
func loadData() async {
73-
// Sending 'self' risks causing data races
74-
// Sending main actor-isolated 'self' into async let risks causing data races between nonisolated and main actor-isolated uses
75-
76-
/*
77-
async let popular = interactor.loadPopularMovies()
78-
async let movies = interactor.loadAiringMovies()
79-
await popular
80-
await movies
81-
*/
82-
83-
84-
await interactor.loadPopularMovies()
85-
await interactor.loadAiringMovies()
72+
view?.startLoading()
73+
await interactor.loadData()
74+
view?.finishLoading()
8675
}
8776

8877
}
@@ -142,33 +131,26 @@ extension HomePresenter {
142131
}
143132
}
144133

145-
146-
func titleForSection(at section: Int) -> (
147-
title: String,
148-
sizeType: SectionSizeType,
149-
buttonType: [TitleForSectionButtonType]?
150-
) {
134+
func titleForSection(at section: Int) -> GenericCollectionViewKit.HeaderViewItem {
135+
var headerViewItem:HeaderViewItem
151136
guard let sectionType = SectionType(rawValue: section) else {
152-
return (title: "", sizeType: .small, buttonType: [])
137+
headerViewItem = .init(title: "", sizeType: .empty)
138+
return headerViewItem
153139
}
154140

155-
var item: (title: String, sizeType: SectionSizeType, buttonType: [TitleForSectionButtonType]?)
156-
157141
switch sectionType {
158142
case .popular:
159-
item = (
143+
headerViewItem = .init(
160144
title: LocalizableUI.popular.localized,
161145
sizeType: .large,
162-
buttonType: [.allList]
163-
)
146+
buttonTypes: [.allList])
164147
case .airingToday:
165-
item = (
148+
headerViewItem = .init(
166149
title: LocalizableUI.airingToday.localized,
167-
sizeType: .small,
168-
buttonType: [.allList]
169-
)
150+
sizeType: .large,
151+
buttonTypes: [.allList])
170152
}
171-
return item
153+
return headerViewItem
172154
}
173155

174156

@@ -210,32 +192,32 @@ extension HomePresenter {
210192

211193
// MARK: - InteractorToPresenterHomeProtocol
212194
/// Receives data from the Interactor and updates the view.
213-
extension HomePresenter: InteractorToPresenterHomeProtocol {
195+
extension HomePresenter: InteractorToPresenterHomeProtocol {
196+
func sendError() {
197+
198+
view?.sendError(errorState: (isHidden: true,
199+
message: LocalizableUI.somethingWentWrong.localized))
200+
view?.relaodCollectionView()
201+
202+
}
203+
214204

215205
func sendAiringTvShows(_ data: [AiringToday]) {
216-
view?.startLoading()
206+
217207
airingTodayShows = data.map { AiringTodayPresentation(tvShow: $0) }
218208
view?.sendError(errorState: (isHidden: false, message: ""))
219209
view?.relaodCollectionView()
220-
view?.finishLoading()
210+
221211
}
222212

223213
func sendPopularTvShows(_ data: [PopularTvShows]) {
224-
view?.startLoading()
214+
225215
popularTvShows = data
226216
.map { PopularTVShowPresentation(tvShow: $0) }
227217
.sorted { $0.rating > $1.rating }
228218
view?.sendError(errorState: (isHidden: false, message: ""))
229219
view?.relaodCollectionView()
230-
view?.finishLoading()
231-
}
232-
233-
func sendError(_ type: HomePageErrorType) {
234-
view?.startLoading()
235-
view?.sendError(errorState: (isHidden: true,
236-
message: LocalizableUI.somethingWentWrong.localized))
237-
view?.relaodCollectionView()
238-
view?.finishLoading()
220+
239221
}
240222
}
241223

ICTMDBHomeModule/HomeProtocols.swift

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,22 +50,20 @@ protocol PresenterToViewHomeProtocol: AnyObject, Ables {
5050

5151
// MARK: - Presenter → Interactor
5252
/// Protocol defining communication from Presenter to Interactor.
53-
@MainActor
54-
protocol PresenterToInteractorHomeProtocol:AnyObject {
53+
54+
protocol PresenterToInteractorHomeProtocol:Sendable,AnyObject {
5555

5656
/// Reference to the Presenter layer
5757
var presenter: InteractorToPresenterHomeProtocol? { get set }
58-
59-
/// Requests popular TV shows data
60-
func loadPopularMovies() async
61-
62-
/// Requests airing today TV shows data
63-
func loadAiringMovies() async
58+
59+
// Requests data
60+
func loadData() async
6461
}
6562

6663

6764
// MARK: - Interactor → Presenter
6865
/// Protocol for sending data or errors from Interactor to Presenter.
66+
@MainActor
6967
protocol InteractorToPresenterHomeProtocol : AnyObject{
7068

7169
/// Sends fetched popular TV shows
@@ -75,7 +73,8 @@ protocol InteractorToPresenterHomeProtocol : AnyObject{
7573
func sendAiringTvShows(_ data: [AiringToday])
7674

7775
/// Sends an error state
78-
func sendError(_ type: HomePageErrorType)
76+
func sendError()
77+
7978
}
8079

8180
// MARK: - Presenter → Router

ICTMDBHomeModule/HomeViewController.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,5 +136,6 @@ extension HomeViewController: @MainActor PresenterToViewHomeProtocol {
136136
// MARK: - Preview
137137
#Preview {
138138
let module = ICTMDBHomeModule()
139-
UINavigationController(rootViewController: module.createHomeModule())
139+
140+
return UINavigationController(rootViewController: module.createHomeModule())
140141
}

ICTMDBHomeModuleTests/ICTMDBHomeModuleTests.swift

Lines changed: 23 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import GenericCollectionViewKit
1010
import ICTMDBViewKit
1111
@testable import ICTMDBHomeModule
1212

13+
@MainActor
1314
struct ICTMDBHomeModuleTests {
1415

1516
private var view: MockHomeViewController!
@@ -37,6 +38,8 @@ struct ICTMDBHomeModuleTests {
3738

3839
presenter.viewDidLoad()
3940

41+
42+
4043
#expect(view.invokedSetBackColorAble == true)
4144
#expect(view.invokedSetBackColorAbleCount == 1)
4245
#expect(view.invokedSetBackColorAbleData == ["backColor"])
@@ -50,39 +53,45 @@ struct ICTMDBHomeModuleTests {
5053

5154
presenter.viewDidLoad()
5255

56+
57+
5358
#expect(view.invokedSetNavigationTitle == true)
5459
#expect(view.invokedSetNavigationTitleCount == 1)
5560
#expect(view.invokedSetNavigationTitleData.map(\.title) == ["Home Page"])
5661

5762
}
5863

5964
@Test("Check number of sections")
60-
func testNumberOfSections() {
65+
func testNumberOfSections() async {
6166
presenter.viewDidLoad()
6267

68+
await Task.yield()
69+
6370
let sectionCount = presenter.numberOfSections()
6471

6572
#expect(sectionCount == 2)
6673
}
6774

68-
@Test("Popular section layout")
75+
@Test("Popular section layout")
6976
func testPopularLayout() {
7077
presenter.viewDidLoad()
7178

7279
let layout = presenter.layout(for: 0)
7380
let expected = LayoutSourceTeamplate.horizontalSingleRow.template
7481

75-
// #expect(layout == expected)
82+
#expect(layout == expected)
7683
}
7784

78-
@Test("Airing today section layout")
85+
@Test("Airing today section layout")
7986
func testAiringTodayLayout() {
8087
presenter.viewDidLoad()
8188

89+
90+
8291
let layout = presenter.layout(for: 1)
8392
let expected = LayoutSourceTeamplate.verticalTwoPerRow.template
8493

85-
// #expect(layout == expected)
94+
#expect(layout == expected)
8695
}
8796

8897
@Test("Check section type")
@@ -95,30 +104,7 @@ struct ICTMDBHomeModuleTests {
95104
let airingSection = presenter.sectionType(at: 1)
96105
#expect(airingSection == .airingToday)
97106
}
98-
99-
@Test("Title configuration for popular section")
100-
func testTitleForPopularSection() async throws {
101-
presenter.viewDidLoad()
102-
103-
let item = presenter.titleForSection(at: 0)
104-
105-
#expect(item.title == LocalizableUI.popular.localized)
106-
#expect(item.sizeType == .large)
107-
#expect(item.buttonType?.contains { if case .allList = $0 { return true } else { return false } } == true)
108-
}
109-
110-
@Test("Title configuration for airing today section")
111-
func testTitleForAiringTodaySection() async throws {
112-
presenter.viewDidLoad()
113-
114-
let item = presenter.titleForSection(at: 1)
115107

116-
#expect(item.title == LocalizableUI.airingToday.localized)
117-
#expect(item.sizeType == .small)
118-
#expect(item.buttonType?.contains { if case .allList = $0 { return true } else { return false } } == true)
119-
}
120-
121-
122108
@Test("Handle tapping title button in popular section")
123109
func testOnTappedTitleButtonForPopular() async throws {
124110
presenter.viewDidLoad()
@@ -145,7 +131,7 @@ struct ICTMDBHomeModuleTests {
145131
#expect(view.invokedRelaodCollectionViewCount == 0)
146132

147133
presenter.viewDidLoad()
148-
134+
await Task.yield()
149135
#expect(view.invokedRelaodCollectionView == true)
150136
#expect(view.invokedRelaodCollectionViewCount == 2)
151137
}
@@ -156,9 +142,9 @@ struct ICTMDBHomeModuleTests {
156142
#expect(view.invokedStartLoadingCount == 0)
157143

158144
presenter.viewDidLoad()
159-
145+
await Task.yield()
160146
#expect(view.invokedStartLoading == true)
161-
#expect(view.invokedStartLoadingCount == 2)
147+
#expect(view.invokedStartLoadingCount == 1)
162148
}
163149

164150
@Test("Stop loading animation on view load")
@@ -167,22 +153,22 @@ struct ICTMDBHomeModuleTests {
167153
#expect(view.invokedFinishLoadingCount == 0)
168154

169155
presenter.viewDidLoad()
170-
156+
await Task.yield()
171157
#expect(view.invokedFinishLoading == true)
172-
#expect(view.invokedFinishLoadingCount == 2)
158+
#expect(view.invokedFinishLoadingCount == 1)
173159
}
174160

175-
@Test("Display error message when interactor fails")
161+
@Test("Display error message when interactor fails")
176162
func testErrorHandling() async throws {
177163
#expect(view.invokedSendError == false)
178164
#expect(view.invokedSendErrorCount == 0)
179165

180166
interactor.loadError = true
181167
presenter.viewDidLoad()
182-
168+
await Task.yield()
183169
#expect(view.invokedSendError == true)
184-
#expect(view.invokedSendErrorCount == 2)
185-
#expect(view.invokedSendErrorData.map(\.isHidden) == [true, true])
170+
#expect(view.invokedSendErrorCount == 1)
171+
#expect(view.invokedSendErrorData.map(\.isHidden) == [true])
186172
#expect(view.invokedSendErrorData.map(\.message) == [
187173
LocalizableUI.somethingWentWrong.localized,
188174
LocalizableUI.somethingWentWrong.localized

0 commit comments

Comments
 (0)