Skip to content

Commit 1f2b740

Browse files
swift 6 migration step 2
1 parent ec9be7a commit 1f2b740

7 files changed

Lines changed: 288 additions & 270 deletions

File tree

SwiftUIExamples/SwiftUI-CommandLineTool/main.swift

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,101 @@ await asyncExample()
185185
// }
186186
// }
187187
//}
188+
/**
189+
In a multithreaded program, shared mutable state can lead to data races.
190+
Swift `actor`s solve this by **serializing access** to their internal state — ensuring that only one task at a time can access or mutate it.
191+
192+
### Actor Features
193+
194+
| Feature | Description |
195+
|--------------------------|-----------------------------------------------------------------------------|
196+
| Reference Type | ✅ Yes |
197+
| Thread-Safe by Default | ✅ Yes – internal state is isolated and synchronized automatically |
198+
| Mutable State Isolation | ✅ Yes – protects mutable properties from concurrent access |
199+
| External Access | ✅ Requires `await` for methods/properties that can suspend (`async`) calls |
200+
201+
Use `actor`s when you need to protect shared, mutable state in concurrent Swift code.
202+
*/
203+
204+
205+
actor Counter {
206+
private var value = 0
207+
208+
func increment() {
209+
value += 1
210+
}
211+
212+
func get() -> Int {
213+
return value
214+
}
215+
216+
// Sometimes you want an actor method that doesn’t need to access isolated state. Use nonisolated:
217+
// No await needed to call it.
218+
219+
nonisolated func logInfo(_ message: String) {
220+
print("Info: \(message)")
221+
222+
}
223+
}
224+
225+
func counterExample() {
226+
let counter = Counter()
227+
Task {
228+
await counter.increment()
229+
print("Counter Value:", await counter.get()) // Prints 1
230+
}
231+
counter.logInfo("This is nonisolated")// No await needed to call it.
232+
}
233+
counterExample()
234+
/******************************/
235+
236+
// Example of sendable
237+
struct UserSettings {
238+
var theme: String = "Light"
239+
}
240+
241+
func useInDetachedTask(settings: UserSettings) {
242+
Task.detached {
243+
print("Theme: \(settings.theme)")
244+
}
245+
}
246+
// Note # actors & struct are already sendable by default. but classes are not Why? Because class instances can be shared mutable state, which isn't safe across threads.
247+
// Task.detached creates a new, independent concurrent task that runs outside the current actor context.
248+
249+
250+
// To silance warning use @unchecked Sendable {Only do this if you are 100% sure: All properties are immutable OR You've synchronized access (e.g., with actors or locks)
251+
final class CarSettings: @unchecked Sendable {
252+
let temprature: Int
253+
init(temprature: Int) {
254+
self.temprature = temprature
255+
}
256+
}
257+
258+
func useSafely(settings: CarSettings) {
259+
Task.detached {
260+
print("temprature: \(settings.temprature)")
261+
}
262+
}
263+
264+
// Making class as sendable
265+
final class MyLogger: Sendable {
266+
let message: String
267+
268+
init(message: String) {
269+
self.message = message
270+
}
271+
}
272+
273+
func detachTaskExample(logger: MyLogger) {
274+
Task.detached {
275+
print("message: \(logger.message)")
276+
}
277+
}
278+
279+
func exampleOfClassWithSendableProperties() {
280+
let logger = MyLogger(message: "Hello World! from class Sendable detched task")
281+
detachTaskExample(logger: logger)
282+
}
283+
exampleOfClassWithSendableProperties()
284+
// Use @concurrent to run on background thread. example fetchImage and decode image. for display Image use @mainactor
285+
// preconcurrency to silence the warning

SwiftUIExamples/SwiftUIExamples.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
10AB45412DD24B7D00967614 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 10AB45402DD24B7D00967614 /* Assets.xcassets */; };
2929
10AB45442DD24B7D00967614 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 10AB45432DD24B7D00967614 /* Preview Assets.xcassets */; };
3030
10AB454B2DD257A100967614 /* BindingExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10AB454A2DD257A100967614 /* BindingExample.swift */; };
31+
4FB02AD62E3B3B320010B8D8 /* DependencyInjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB02AD52E3B3B320010B8D8 /* DependencyInjectionTests.swift */; };
3132
/* End PBXBuildFile section */
3233

3334
/* Begin PBXContainerItemProxy section */
@@ -77,6 +78,7 @@
7778
10AB45402DD24B7D00967614 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
7879
10AB45432DD24B7D00967614 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
7980
10AB454A2DD257A100967614 /* BindingExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindingExample.swift; sourceTree = "<group>"; };
81+
4FB02AD52E3B3B320010B8D8 /* DependencyInjectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyInjectionTests.swift; sourceTree = "<group>"; };
8082
/* End PBXFileReference section */
8183

8284
/* Begin PBXFrameworksBuildPhase section */
@@ -135,6 +137,7 @@
135137
isa = PBXGroup;
136138
children = (
137139
10525C742E2D2D1A00D7C510 /* git_hub_repos_async_tests.swift */,
140+
4FB02AD52E3B3B320010B8D8 /* DependencyInjectionTests.swift */,
138141
10525C7E2E2D2D9800D7C510 /* SwiftUIExamplesTests.swift */,
139142
);
140143
path = SwiftUIExamplesTests;
@@ -321,6 +324,7 @@
321324
files = (
322325
10525C7F2E2D2D9800D7C510 /* SwiftUIExamplesTests.swift in Sources */,
323326
10525C852E2D2D9F00D7C510 /* git_hub_repos_async_tests.swift in Sources */,
327+
4FB02AD62E3B3B320010B8D8 /* DependencyInjectionTests.swift in Sources */,
324328
);
325329
runOnlyForDeploymentPostprocessing = 0;
326330
};
@@ -560,6 +564,7 @@
560564
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
561565
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
562566
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
567+
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
563568
LD_RUNPATH_SEARCH_PATHS = (
564569
"$(inherited)",
565570
"@executable_path/Frameworks",
@@ -589,6 +594,7 @@
589594
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
590595
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
591596
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
597+
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
592598
LD_RUNPATH_SEARCH_PATHS = (
593599
"$(inherited)",
594600
"@executable_path/Frameworks",
Lines changed: 28 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,64 @@
11
import SwiftUI
22

3-
// MARK: - Protocol
3+
// MARK: - Repo Service Protocol
44

55
protocol RepoServiceProtocol: Sendable {
6-
// func fetchRepos(completion: @escaping ([String]) -> Void)
7-
// clouser replaced with async
86
func fetchRepos() async -> [String]
97
}
108

11-
// MARK: - Real Service
12-
13-
final class RepoService: RepoServiceProtocol {
14-
func fetchRepos() async -> [String] {
15-
try? await Task.sleep(nanoseconds: 1_000_000_000) // Simulate 1 second delay
16-
return ["Swift", "Combine", "UIKit", "CoreData"]
17-
}
18-
}
19-
20-
// MARK: - Mock Service (for previews or unit tests)
21-
22-
final class MockRepoService: RepoServiceProtocol {
9+
struct MockRepoService: RepoServiceProtocol {
2310
func fetchRepos() async -> [String] {
11+
try? await Task.sleep(nanoseconds: 500_000_000) // 0.5 sec
2412
return ["MockRepo1", "MockRepo2"]
2513
}
2614
}
2715

28-
// MARK: - ViewModel
16+
// MARK: - Actor
2917

30-
@MainActor
31-
final class RepoViewModel: ObservableObject {
32-
@Published var repos: [String] = []
33-
private let service: any RepoServiceProtocol
34-
35-
init(service: any RepoServiceProtocol) {
18+
actor RepoActor {
19+
private let service: RepoServiceProtocol
20+
21+
init(service: RepoServiceProtocol) {
3622
self.service = service
3723
}
38-
39-
// fetchRepos() is nonisolated — it won’t run on MainActor.
40-
nonisolated func loadRepos() async {
41-
// This runs on background thread
42-
let repos = await service.fetchRepos()
43-
44-
// Switch to main actor for UI update
45-
await setRepos(repos)
46-
}
47-
48-
private func setRepos(_ repos: [String]) {
49-
self.repos = repos
24+
25+
func loadRepos() async -> [String] {
26+
await service.fetchRepos()
5027
}
5128
}
5229

5330
// MARK: - View
5431

5532
struct ProjectsListView: View {
56-
@StateObject var viewModel: RepoViewModel
57-
33+
@State private var repos: [String] = []
34+
private let repoActor: RepoActor
35+
36+
init(service: RepoServiceProtocol = MockRepoService()) {
37+
self.repoActor = RepoActor(service: service)
38+
}
39+
5840
var body: some View {
5941
NavigationView {
60-
List(viewModel.repos, id: \.self) { repo in
42+
List(repos, id: \.self) { repo in
6143
Text(repo)
6244
}
6345
.navigationTitle("Projects")
6446
.task {
65-
await viewModel.loadRepos()
47+
await fetchAndUpdate()
6648
}
6749
}
6850
}
51+
52+
private func fetchAndUpdate() async {
53+
let result = await repoActor.loadRepos()
54+
repos = result
55+
}
6956
}
7057

58+
// MARK: - Preview
59+
7160
struct ProjectsListView_Previews: PreviewProvider {
7261
static var previews: some View {
73-
ProjectsListView(viewModel: .init(service: MockRepoService()))
62+
ProjectsListView()
7463
}
7564
}

0 commit comments

Comments
 (0)