Skip to content

Commit 78366c9

Browse files
2 parents 7d864fc + 246b89e commit 78366c9

1 file changed

Lines changed: 194 additions & 0 deletions

File tree

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// Some tips before starts
2+
Prefer Value Types (Structs) When Possible
3+
Value types are copied when passed around, reducing the risk of shared mutable state.
4+
5+
// MARK: Example 1
6+
class HomeViewControllerLegacy {
7+
8+
private var loadingView = UIView() // Error here `Main actor-isolated default value in a nonisolated context`
9+
var hasLaunched = false
10+
11+
@MainActor
12+
func home() {
13+
self.hasLaunched = true
14+
}
15+
}
16+
17+
// To solve it - initializing the view at declaration time, so you can safely create it in viewDidLoad() which runs on the main thread.
18+
class HomeViewControllerNew: UIViewController {
19+
private var loadingView: UIView!
20+
21+
override func viewDidLoad() {
22+
super.viewDidLoad()
23+
loadingView = UIView()
24+
loadingView.backgroundColor = .gray
25+
view.addSubview(loadingView)
26+
}
27+
}
28+
29+
// MARK: Example 2 Passing closure as a 'sending' parameter risks causing data races
30+
class LoggerLegacy {
31+
var logs: [String] = []
32+
33+
func log(_ message: String) {
34+
logs.append(message)
35+
}
36+
}
37+
struct WorkerLegacy {
38+
let logger: Logger
39+
40+
func doWork() {
41+
Task {//<<-- Error Passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure
42+
logger.log("Work started")
43+
}
44+
}
45+
}
46+
47+
//-> If you know there is no data race in your codebase & want to skip data race use @preconcurrency
48+
@preconcurrency
49+
class LoggerNew {
50+
var logs: [String] = []
51+
52+
func log(_ message: String) {
53+
logs.append(message)
54+
}
55+
}
56+
57+
extension LoggerNew: @unchecked Sendable {}
58+
59+
struct WorkerNew {
60+
let logger: LoggerNew
61+
62+
func doWork() {
63+
Task {
64+
logger.log("Work started") // ✅ No error now
65+
}
66+
}
67+
}
68+
69+
// MARK: Example # Trying to access heightConstraint from background thread.
70+
71+
class ProfileViewControllerLegacy: UIViewController {
72+
var heightConstraint: NSLayoutConstraint!
73+
74+
override func viewDidLoad() {
75+
super.viewDidLoad()
76+
77+
DispatchQueue.global().async {
78+
// ❌ This is a background thread!
79+
// ❌ Call to main actor-isolated instance method 'constraint(equalToConstant:)' in a synchronous nonisolated context
80+
self.heightConstraint = self.view.heightAnchor.constraint(equalToConstant: 0)
81+
self.heightConstraint.isActive = true
82+
}
83+
}
84+
}
85+
86+
// Solution ->
87+
class ProfileViewControllerNew: UIViewController {
88+
var heightConstraint: NSLayoutConstraint!
89+
90+
override func viewDidLoad() {
91+
super.viewDidLoad()
92+
93+
// Simulate background work
94+
DispatchQueue.global().async {
95+
// ✅ Launch a Swift concurrency task from background by using MainActor
96+
Task { @MainActor in
97+
self.heightConstraint = self.view.heightAnchor.constraint(equalToConstant: 0)
98+
self.heightConstraint.isActive = true
99+
}
100+
}
101+
}
102+
}
103+
104+
// MARK: Example #4 UI Updates from Background Threads
105+
class CartView {
106+
107+
@IBOutlet var label: UILabel!
108+
109+
// Error version
110+
func addItemLegacy() {
111+
DispatchQueue.global().async {
112+
// Error here # Main actor-isolated property 'text' can not be mutated from a Sendable closure
113+
self.label.text = "Updated"
114+
}
115+
}
116+
117+
// Fix version
118+
func addItemFixed() {
119+
Task { @MainActor in
120+
self.label.text = "Updated"
121+
}
122+
}
123+
124+
}
125+
126+
// MARK: Example #5 Accessing Non-Sendable Types Across Threads
127+
class SongClassLegacy {
128+
var data: String = ""
129+
130+
func update() {
131+
DispatchQueue.global().async {
132+
// Warning here Capture of 'self' with non-sendable type 'SongClass' in a '@Sendable' closure
133+
// Warning Class 'SongClass' does not conform to the 'Sendable' protocol
134+
self.data.append("New")
135+
}
136+
}
137+
}
138+
139+
actor SongClassNew {
140+
private var data = ""
141+
142+
func append(_ value: String) {
143+
data.append(value)
144+
}
145+
146+
func getData() -> String {
147+
return data
148+
}
149+
}
150+
151+
// MARK: Example #6 sendable examples
152+
final class SteelFactoryLegacy {
153+
// Closure to be called after background work
154+
var onUpdate: (() -> Void)?
155+
156+
func performWork() {
157+
DispatchQueue.global().async { [weak self] in
158+
// Simulate background work
159+
sleep(1)
160+
// Warning Capture of 'self' with non-sendable type 'SteelFactoryLegacy?' in a '@Sendable' closure
161+
self?.onUpdate?()
162+
163+
}
164+
}
165+
}
166+
167+
168+
final class SteelFactoryNew {
169+
// Closure that can be safely called from concurrent contexts
170+
var onUpdate: (@Sendable () -> Void)?
171+
172+
func performWork() {
173+
DispatchQueue.global().async { [onUpdate] in
174+
// Simulate background work
175+
sleep(1)
176+
177+
// Safely call the closure
178+
onUpdate?()
179+
}
180+
}
181+
}
182+
// Usage -
183+
class CompanyOffice {
184+
185+
func getInfo() {
186+
let manager = SteelFactoryNew()
187+
manager.onUpdate = { [message = "Update received!"] in
188+
print(message)
189+
}
190+
manager.performWork()
191+
192+
}
193+
}
194+

0 commit comments

Comments
 (0)