Skip to content

Commit 382f86e

Browse files
authored
Merge pull request #38 from inDriver/feature_docs
doc: Book of UDF
2 parents 3ce4193 + 3ca390a commit 382f86e

8 files changed

Lines changed: 1521 additions & 1 deletion

File tree

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:5.2
1+
// swift-tools-version:5.5
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription

UDF/Documentation.docc/Action.md

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
# Action
2+
3+
Events that describe what happened in your application and trigger state updates.
4+
5+
## Overview
6+
7+
`Action` is a marker protocol that represents events or intentions in your application. Actions are the only way to update the state in a UDF application. They describe what happened (not how to update the state), making your application's behavior predictable and testable.
8+
9+
### Key Principles
10+
11+
- **Descriptive**: Actions describe what happened, not how to handle it
12+
- **Immutable**: Actions should be immutable data structures
13+
- **Serializable**: Actions should be easily serializable for debugging
14+
- **Type-Safe**: Use specific action types for different events
15+
16+
## Example
17+
18+
```swift
19+
// Simple actions
20+
struct IncrementCounterAction: Action {}
21+
struct DecrementCounterAction: Action {}
22+
23+
// Actions with data
24+
struct LoadUserAction: Action {
25+
let userId: String
26+
}
27+
28+
struct UserLoadedAction: Action {
29+
let user: User
30+
}
31+
32+
struct UserLoadErrorAction: Action {
33+
let message: String
34+
}
35+
36+
// Complex actions
37+
struct UpdateUserProfileAction: Action {
38+
let userId: String
39+
let name: String
40+
let email: String
41+
let avatar: URL?
42+
}
43+
```
44+
45+
## Action Categories
46+
47+
### User Actions
48+
Actions triggered by user interactions:
49+
50+
```swift
51+
struct ButtonTappedAction: Action {
52+
let buttonId: String
53+
}
54+
55+
struct TextFieldChangedAction: Action {
56+
let fieldId: String
57+
let text: String
58+
}
59+
60+
struct SwipeGestureAction: Action {
61+
let direction: SwipeDirection
62+
}
63+
```
64+
65+
### System Actions
66+
Actions triggered by system events:
67+
68+
```swift
69+
struct AppDidBecomeActiveAction: Action {}
70+
struct AppWillResignActiveAction: Action {}
71+
struct NetworkStatusChangedAction: Action {
72+
let isConnected: Bool
73+
}
74+
```
75+
76+
### Async Actions
77+
Actions that represent asynchronous operations:
78+
79+
```swift
80+
struct APIRequestStartedAction: Action {
81+
let requestId: String
82+
let endpoint: String
83+
}
84+
85+
struct APIRequestCompletedAction: Action {
86+
let requestId: String
87+
let data: Data
88+
}
89+
90+
struct APIRequestFailedAction: Action {
91+
let requestId: String
92+
let error: Error
93+
}
94+
```
95+
96+
## Dispatching Actions
97+
98+
Actions are dispatched through the store:
99+
100+
```swift
101+
// Dispatch simple actions
102+
store.dispatch(IncrementCounterAction())
103+
store.dispatch(DecrementCounterAction())
104+
105+
// Dispatch actions with data
106+
store.dispatch(LoadUserAction(userId: "123"))
107+
108+
// Dispatch from components
109+
class UserViewController: UIViewController, ViewComponent {
110+
let disposer = Disposer()
111+
var props: UserProps = UserProps()
112+
113+
@IBAction func loadUserButtonTapped() {
114+
// Dispatch action through the store
115+
store.dispatch(LoadUserAction(userId: "123"))
116+
}
117+
}
118+
```
119+
120+
## Action Handling in Reducers
121+
122+
Actions are handled in reducers to update state:
123+
124+
```swift
125+
let userReducer: Reducer<AppState> = { state, action in
126+
switch action {
127+
case let action as LoadUserAction:
128+
state.isLoading = true
129+
state.error = nil
130+
// Trigger async operation here
131+
132+
case let action as UserLoadedAction:
133+
state.user = action.user
134+
state.isLoading = false
135+
136+
case let action as UserLoadErrorAction:
137+
state.error = action.message
138+
state.isLoading = false
139+
140+
default:
141+
break
142+
}
143+
}
144+
```
145+
146+
## Action Observation
147+
148+
You can observe actions for logging, analytics, or side effects:
149+
150+
```swift
151+
store.onAction { state, action in
152+
// Log all actions
153+
print("Action: \(type(of: action))")
154+
155+
// Track analytics
156+
Analytics.track(action: action)
157+
158+
// Handle side effects
159+
switch action {
160+
case is AppDidBecomeActiveAction:
161+
// Refresh data when app becomes active
162+
store.dispatch(RefreshDataAction())
163+
default:
164+
break
165+
}
166+
}.dispose(on: disposer)
167+
```
168+
169+
## Best Practices
170+
171+
### Action Naming
172+
- Use descriptive names that explain what happened
173+
- Use past tense for completed actions
174+
- Use present tense for ongoing actions
175+
176+
```swift
177+
// Good
178+
struct UserProfileUpdatedAction: Action {}
179+
struct DataLoadingStartedAction: Action {}
180+
181+
// Avoid
182+
struct UpdateUserAction: Action {}
183+
struct LoadDataAction: Action {}
184+
```
185+
186+
### Action Structure
187+
- Keep actions simple and focused
188+
- Include only necessary data
189+
- Make actions immutable
190+
191+
```swift
192+
// Good - focused and immutable
193+
struct UserNameChangedAction: Action {
194+
let newName: String
195+
}
196+
197+
// Avoid - too complex
198+
struct UserAction: Action {
199+
var name: String
200+
var email: String
201+
var avatar: URL?
202+
var isEditing: Bool
203+
}
204+
```
205+
206+
### Action Organization
207+
- Group related actions together
208+
- Use namespaces for large applications
209+
- Consider using enums for related actions
210+
211+
```swift
212+
// Using enums for related actions
213+
enum UserAction {
214+
case load(userId: String)
215+
case loaded(user: User)
216+
case loadFailed(error: String)
217+
case update(profile: UserProfile)
218+
case updateCompleted(user: User)
219+
case updateFailed(error: String)
220+
}
221+
```
222+
223+
## Summary
224+
225+
Actions are the foundation of the unidirectional data flow pattern:
226+
227+
- **Describe events**: Actions describe what happened in your app
228+
- **Trigger updates**: Actions are the only way to update state
229+
- **Enable testing**: Actions make behavior predictable and testable
230+
- **Support debugging**: Actions provide a clear audit trail
231+
232+
Design your actions to be descriptive, immutable, and focused on specific events in your application.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Book of UDF
2+
3+
@Metadata {
4+
@CallToAction (
5+
purpose: link,
6+
url: https://github.com/inDriver/UDF
7+
)
8+
@PageImage(
9+
purpose: icon,
10+
source: "https://github.com/inDriver/UDF/blob/master/Docs/img/logo.svg",
11+
alt: "A technology icon representing UDF framework"
12+
)
13+
}
14+
15+
16+
17+
18+
## Overview
19+
20+
Unidirectional Data Flow (UDF) is an architectural approach that focuses on how data is stored and transferred within an application. The main principle of Unidirectional Data Flow is to pass immutable data in only one direction. This allows for efficient state management in an application and simplifies the testing of its components.
21+
There are many ways to implement an application based on unidirectional data flow, but this approach reveals its full potential when combined with functional programming. However, mastering and using Unidirectional Data Flow does not require deep academic knowledge of mathematics. To ease the learning process, we will start with an application that does not follow the UDF principle and refactor it step by step. At each stage, we will analyze the current state of the project and gradually move towards the most optimal solution. So, let’s get started!
22+
23+
24+
### Topics
25+
26+
- <doc:Getting-started>
27+
- <doc:Store>
28+
- <doc:Action>
29+
- <doc:Reducer>
30+
- <doc:Props>
31+
- <doc:Service-Component>
32+
33+
34+
## See Also
35+
36+
- ``Taxi App tutorial``
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Getting started
2+
3+
Learn how to set up and use the UDF framework in your iOS application.
4+
5+
## Overview
6+
7+
UDF (Unidirectional Data Flow) is a Swift framework that implements the unidirectional data flow architecture pattern. This pattern ensures that data flows in only one direction through your application, making state management predictable and testable.
8+
9+
The framework consists of several core components:
10+
- **Store**: The central state container
11+
- **Action**: Events that describe what happened
12+
- **Reducer**: Pure functions that update state based on actions
13+
- **Component**: UI or service components that connect to the store
14+
- **Connector**: Objects that transform state into props for components
15+
16+
### Key Benefits
17+
18+
- **Predictable State Management**: All state changes flow through a single path
19+
- **Testability**: Pure functions and isolated components make testing easier
20+
- **Debugging**: Clear data flow makes it easier to track down issues
21+
- **Scalability**: Modular architecture supports large applications
22+
23+
## Example
24+
25+
Here's a simple example of how to set up a UDF application:
26+
27+
```swift
28+
// 1. Define your state
29+
struct AppState {
30+
var counter: Int = 0
31+
}
32+
33+
// 2. Define actions
34+
struct IncrementAction: Action {}
35+
struct DecrementAction: Action {}
36+
37+
// 3. Create a reducer
38+
let appReducer: Reducer<AppState> = { state, action in
39+
switch action {
40+
case is IncrementAction:
41+
state.counter += 1
42+
case is DecrementAction:
43+
state.counter -= 1
44+
default:
45+
break
46+
}
47+
}
48+
49+
// 4. Create the store
50+
let store = Store(
51+
state: AppState(),
52+
reducer: appReducer
53+
)
54+
55+
// 5. Create a component
56+
class CounterViewController: UIViewController, ViewComponent {
57+
let disposer = Disposer()
58+
var props: CounterProps = CounterProps(count: 0)
59+
60+
override func viewDidLoad() {
61+
super.viewDidLoad()
62+
63+
// Connect to store
64+
connect(to: store) { state in
65+
CounterProps(count: state.counter)
66+
}
67+
}
68+
69+
func updateProps() {
70+
// Update UI with props.count
71+
}
72+
}
73+
74+
struct CounterProps: Equatable {
75+
let count: Int
76+
}
77+
```
78+
79+
## Summary
80+
81+
The UDF framework provides a robust foundation for building iOS applications with predictable state management. By following the unidirectional data flow pattern, you can create maintainable, testable, and scalable applications.
82+
83+
The main concepts to understand are:
84+
- **Store**: Holds your application state and dispatches actions
85+
- **Action**: Describes what happened in your app
86+
- **Reducer**: Pure functions that update state based on actions
87+
- **Component**: UI or service components that react to state changes
88+
- **Connector**: Transforms state into props for components
89+
90+
Start with a simple example and gradually add complexity as you become more familiar with the patterns.

0 commit comments

Comments
 (0)