Skip to content

Commit b39e259

Browse files
committed
[Dependencies] Update Swift Flow to 0.2.2
1 parent 7d09b3d commit b39e259

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+3284
-3578
lines changed

Cartfile.resolved

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
github "Quick/Nimble" "v3.0.0"
22
github "Quick/Quick" "v0.8.0"
3-
github "Swift-Flow/Swift-Flow" "v0.2"
3+
github "Swift-Flow/Swift-Flow" "0.2.2"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
swift:
2+
enabled: true
3+
config_file: .swiftlint.yml

Carthage/Checkouts/Swift-Flow/.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ before_install:
1212

1313
install:
1414
- gem install xcpretty
15-
- carthage bootstrap
15+
- carthage bootstrap --no-use-binaries --platform Mac,iOS
1616

1717
env:
1818
global:
@@ -26,7 +26,7 @@ env:
2626
matrix:
2727
- DESTINATION="arch=x86_64" SCHEME="OSX" SDK="$OSX_SDK" ACTION="test"
2828
- DESTINATION="OS=9.2,name=iPhone 6S Plus" SCHEME="iOS" SDK="$IOS_SDK" ACTION="test"
29-
- DESTINATION="OS=9.1,name=Apple TV 1080p" SCHEME="tvOS" SDK="$TVOS_SDK" ACTION="test"
29+
- DESTINATION="OS=9.1,name=Apple TV 1080p" SCHEME="tvOS" SDK="$TVOS_SDK" ACTION="build"
3030
- DESTINATION="OS=2.1,name=Apple Watch - 38mm" SCHEME="watchOS" SDK="$WATCHOS_SDK" ACTION="build"
3131
- DESTINATION="OS=9.1,name=iPhone 6S" SCHEME="iOS" SDK="$IOS_SDK" ACTION="test"
3232
- DESTINATION="OS=9.0,name=iPhone 6 Plus" SCHEME="iOS" SDK="$IOS_SDK" ACTION="test"

Carthage/Checkouts/Swift-Flow/README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,36 @@ It also leads to code that is difficult to understand at a glance, since depende
120120

121121
Swift Flow attempts to solve these problem by placing strong constraints on the way applications can be written. This reduces the room for programmer error and leads to applications that can be easily understood - by inspecting the application state data structure, the actions and the reducers.
122122

123+
This architecture provides further benefits beyond improving your code base:
124+
125+
- Stores, Reducers, Actions and extensions such as Swift Flow Router are entirely platform independent - you can easily use the same business logic and share it between apps for multiple platforms (iOS, tvOS, etc.)
126+
- Want to collaborate with a co-worker on fixing an app crash? Use [Swift Flow Recorder](https://github.com/Swift-Flow/Swift-Flow-Recorder) to record the actions that lead up to the crash and send them the JSON file so that they can replay the actions and reproduce the issue right away.
127+
- Maybe recorded actions can be used to build UI and integration tests?
128+
129+
The Swift Flow tooling is still in a very early stage, but aforementioned prospects excite me and hopefully others in the community as well!
130+
123131
#Getting Started Guide
124132

125133
[A Getting Started Guide that describes the core components of apps built with Swift Flow lives here](Readme/GettingStarted.md). It will be expanded in the next few weeks. To get an understanding of the core principles I recommend reading the brilliant [redux documentation](http://rackt.org/redux/).
126134

127135
#Installation
128136

129-
You can install SwiftFlow via [Carthage]() by adding the following line to your Cartfile:
137+
##Cocoapods
138+
139+
You can install Swift Flow via CocoaPods by adding it to your `Podfile`:
140+
141+
use_frameworks!
142+
143+
source 'https://github.com/CocoaPods/Specs.git'
144+
platform :ios, '8.0'
145+
146+
pod 'SwiftFlow'
147+
148+
And run `pod install`.
149+
150+
##Carthage
151+
152+
You can install Swift Flow via [Carthage]() by adding the following line to your Cartfile:
130153

131154
github "Swift-Flow/Swift-Flow"
132155

@@ -156,6 +179,12 @@ This repository contains the core component for Swift Flow, the following extens
156179
- [CounterExample](https://github.com/Swift-Flow/CounterExample): A very simple counter app implemented with Swift Flow. This app also demonstrates the basics of routing with SwiftFlowRouter.
157180
- [Meet](https://github.com/Ben-G/Meet): A real world application being built with Swift Flow - currently still very early on.
158181

182+
#Contributing
183+
184+
There's still a lot of work to do here! I would love to see you involved! Some design decisions for the core of Swift Flow are still up in the air (see issues), there's lots of useful documentation that can be written and a ton of extensions and tools are waiting to be built on top of Swift Flow.
185+
186+
I personally think the best way to get started contributing to this library is by using it in one of your projects!
187+
159188
#Credits
160189

161190
- Thanks a lot to [Dan Abramov](https://github.com/gaearon) for building [Redux](https://github.com/rackt/redux) - all ideas in here and many implementation details were provided by his library.

Carthage/Checkouts/Swift-Flow/Readme/GettingStarted.md

Lines changed: 50 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ There are multiple things to note:
2828

2929
##Viewing the State Through a Protocol
3030

31-
Protocols are extremely useful for working with this framework. As you can see in the example above, `SwiftRouter` can work with any app state that you define, as long as it conforms to the `HasNavigationState` protocol. The router will only ever access your app state through this protocol - this also means it won't ever see and depend upon any other state that you store within your state struct.
31+
Protocols are extremely useful for working with this library. As you can see in the example above, `SwiftRouter` can work with any app state that you define, as long as it conforms to the `HasNavigationState` protocol. The router will only ever access your app state through this protocol - this also means it won't ever see and depend upon any other state that you store within your state struct.
3232

3333
**You should use this approach as much as possible in your app.** Swift Flow provides some features that make this approach even easier to use. Let's say you want to expand the state above by a simple authentication state. You would do this by first defining a new struct for this sub-state:
3434

@@ -75,115 +75,45 @@ Note that you don't need to store derived state inside of your app state. E.g. i
7575

7676
#Actions
7777

78-
Swift Flow provides the `Action` type to create actions that describe state changes. It has the following structure:
78+
Actions are used to express intented state changes. Actions don't contain functions, instead they provide information about the intended state change, e.g. which user should be deleted.
7979

80-
```swift
81-
struct Action : ActionType {
82-
let type: String
83-
let payload: [String : AnyObject]?
84-
}
85-
```
86-
This `Action` type is serializable, which is imported for storing past actions to disk and enabling time traveling and hot reloading.
80+
In your Swift Flow app you will define actions for every possible state change that can happen.
8781

88-
For simple actions you can use this type directly; for actions with a complex payload you should make use of Swift's rich type system and create separate type. This type needs to be convertible into plain `Action`s.
82+
Reducers handle these actions and implement state changes based on the information they provide.
8983

90-
##Using Plain Actions
84+
All actions in Swift Flow conform to the `Action` protocol, which currently is just a marker procotol.
9185

92-
The [Counter Example App](https://github.com/Swift-Flow/CounterExample) makes use of plain actions that don't carry any payload. They are simply initialized with a string constant:
86+
You can either provide custom types as actions, or you can use the built in `StandardAction`.
9387

94-
```swift
95-
@IBAction func increaseButtonTapped(sender: UIButton) {
96-
mainStore.dispatch(
97-
Action(CounterActionIncrease)
98-
)
99-
}
100-
```
101-
102-
Within your reducer you switch over the `type` string of this plain action to identify the type and perform a state change:
88+
The `StandardAction` has the following structure:
10389

10490
```swift
105-
struct CounterReducer: Reducer {
106-
107-
func handleAction(var state: AppState, action: Action) -> AppState {
108-
switch action.type {
109-
case CounterActionIncrease:
110-
state.counter += 1
111-
case CounterActionDecrease:
112-
state.counter -= 1
113-
default:
114-
break
115-
}
116-
117-
return state
118-
}
119-
120-
}
121-
```
122-
This approach works best with simple actions, with no or very simple payloads. For more complex actions you should use a typed action.
123-
124-
##Using Typed Actions
125-
126-
Here's an example from the Meet App that shows you how to define a custom, typed action:
127-
128-
```swift
129-
struct CreateContactFromEmail {
130-
static let type = "CreateContactFromEmail"
131-
let email: String
132-
133-
init(_ email: String) {
134-
self.email = email
135-
}
136-
}
137-
138-
extension CreateContactFromEmail: ActionConvertible, ActionType {
139-
140-
init(_ action: Action) {
141-
if (action.type != CreateContactFromEmail.type) {
142-
fatalError("Typed Action cannot be initialized with plain Action of wrong type!")
143-
}
144-
self.email = action.payload!["email"] as! String
145-
}
146-
147-
func toAction() -> Action {
148-
return Action(type: CreateContactFromEmail.type, payload: ["email": email])
149-
}
91+
struct StandardAction: Action {
92+
// identifies the action
93+
let type: String
94+
// provides information that is relevant to processing this action
95+
// e.g. details about which post should be favorited
96+
let payload: [String : AnyObject]?
97+
// this flag is used for serialization when working with Swift Flow Recorder
98+
let isTypedAction: Bool
15099
}
151100
```
152-
The `CreateContactFromEmail` struct contains a type string that identifies this action (this preserves the type upon serialization and deserialization) and a payload, in this case an email address.
153-
154-
In the extension you can see boilerplate code that allows this typed action to be initialized with a plain action and that allows us to convert it into a plain action. [This code will mostly be auto-generated in future, since it is one of the painpoints of working with this framework right now.](https://github.com/Swift-Flow/Swift-Flow/issues/2)
101+
**For most applications it is recommended to create your own types for actions instead of using `StandardAction`, as this allows you to take advantage of Swift's type system**.
155102

156-
###Dispatching a Typed Action
157-
158-
The `Store` will accept your typed action directly, as soon as it conforms to the `ActionType` protocol. You can dispatch a typed action the same way as dispatching a plain action:
103+
To provide your own action, simply create a type that conforms to the `Action` protocol:
159104

160105
```swift
161-
store.dispatch(CreateContactFromEmail("[email protected]"))
106+
struct LikePostAction: Action {
107+
let post: Post
108+
let userLikingPost: User
109+
}
162110
```
163111

164-
###Using Typed Actions in a Reducer
165-
166-
In the `handleAction` method of your reducers you will always receive plain `Action`s. This ensure that your reducer works with serialized actions and therefore supports time-traveling and hot-reloading. If your typed action conforms to `ActionConvertible` then it provides a convenience initializer that allows you to easily create a typed action from an untyped one. You should do this as part of your type checking code in the reducers, e.g.:
167-
168-
```swift
169-
func handleAction(state: HasDataState, action: Action) -> HasDataState {
170-
switch action.type {
171-
case CreateContactFromEmail.type:
172-
return createContact(state, email: CreateContactFromEmail(action).email)
173-
...
174-
}
175-
}
176-
177-
func createContact(var state: HasDataState, email: String) -> HasDataState {
178-
let newContactID = state.dataState.contacts.count + 1
179-
let newContact = Contact(identifier: newContactID, emailAddress: email)
180-
state.dataState.contacts.append(newContact)
112+
The advantage of using a `StandardAction` is that it can be serialized; this is required for using the features provided by [Swift Flow Recorder](https://github.com/Swift-Flow/Swift-Flow-Recorder); such as persisting the application state between app launches.
181113

182-
return state
183-
}
184-
```
114+
If you want to use custom types for actions, but still want to be able to make use of the features provided by Swift Flow Recorder, you can implement the `StandardActionConvertible` protocol. This will allow Swift Flow to convert your typed actions to standard actions that can then be serialized.
185115

186-
We create a typed action from the plain action and pass it on to a method of the reducer. This way the method has access to the types defined as part of our `CreateContactFromEmail` method.
116+
Once Swift Flow Recorder's implementation is further along, you will find detailed information on all of this in its documentation.
187117

188118
#Reducers
189119

@@ -193,15 +123,15 @@ This is the only place where you should modify application state! Reducers, just
193123
struct DataMutationReducer: Reducer {
194124

195125
func handleAction(state: HasDataState, action: Action) -> HasDataState {
196-
switch action.type {
197-
case CreateContactFromEmail.type:
198-
return createContact(state, email: CreateContactFromEmail(action).email)
199-
case CreateContactWithTwitterUser.type:
200-
return createContact(state, twitterUser: CreateContactWithTwitterUser(action).twitterUser)
201-
case DeleteContact.type:
202-
return deleteContact(state, identifier: DeleteContact(action).contactID)
203-
case SetContacts.type:
204-
return setContacts(state, contacts: SetContacts(action).contacts)
126+
switch action {
127+
case let action as IncrementAction:
128+
return createContact(state, email: action.email)
129+
case let action as CreateContactWithTwitterUser:
130+
return createContact(state, twitterUser: action.twitterUser)
131+
case let action as DeleteContact:
132+
return deleteContact(state, identifier: action.contactID)
133+
case let action as SetContacts:
134+
return setContacts(state, contacts: action.contacts)
205135
default:
206136
return state
207137
}
@@ -224,17 +154,29 @@ struct DataMutationReducer: Reducer {
224154
}
225155
```
226156

227-
You typically switch over the types in `handleAction`, then instantiate typed actions from plain actions and finally call a method that implements the actual state mutation.
157+
You typically switch over the types in `handleAction`, then call a method that implements the actual state mutation.
158+
159+
#Store Subscribers
160+
161+
Store subscribers are types that are interested in receiving state updates from a store. Whenever the store updates its state it will notify all subscribers by calling the `newState` method on them. Subscribers need to conform to the `StoreSubscriber` protocol:
162+
163+
```swift
164+
protocol StoreSubscriber {
165+
func newState(state: StoreSubscriberStateType)
166+
}
167+
```
168+
169+
Most of your `StoreSubscriber`s will be in the view layer and update their representation whenever they receive a new state.
228170

229171
#Middleware
230172

231-
Swift Flow supports middleware in the same way as Redux does, [you can read this great documentation on Redux middleware to get started](http://rackt.org/redux/docs/advanced/Middleware.html).
173+
Swift Flow supports middleware in the same way as Redux does, [you can read this great documentation on Redux middleware to get started](http://rackt.org/redux/docs/advanced/Middleware.html). Middleware allows developers to provide extensions that wrap the `dispatch` function.
232174

233175
Let's take a look at a quick example that shows how Swift Flow supports Redux style middleware.
234176

235177
The simplest example of a middleware, is one that prints all actions to the console. Here's how you can implement it:
236178

237-
```
179+
```swift
238180
let loggingMiddleware: Middleware = { dispatch, getState in
239181
return { next in
240182
return { action in
@@ -249,7 +191,8 @@ let loggingMiddleware: Middleware = { dispatch, getState in
249191
```
250192
You can define which middleware you would like to use when creating your store:
251193

252-
```
194+
```swift
253195
MainStore(reducer: reducer, appState: TestStringAppState(),
254196
middleware: [loggingMiddleware, secondMiddleware])
255197
```
198+
The actions will pass through the middleware in the order in which they are arranged in the array passed to the store initializer, however ideally middleware should not make any assumptions about when exactly it is called.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Pod::Spec.new do |s|
2+
s.name = "SwiftFlow"
3+
s.version = "0.2.2"
4+
s.summary = "Unidirectional Data Flow in Swift"
5+
s.description = <<-DESC
6+
Swift Flow is a Redux-like implementation of the unidirectional data flow architecture in Swift.
7+
It embraces a unidirectional data flow that only allows state mutations through declarative actions.
8+
DESC
9+
s.homepage = "https://github.com/Swift-Flow/Swift-Flow"
10+
s.license = { :type => "MIT", :file => "LICENSE.md" }
11+
s.author = { "Benjamin Encz" => "[email protected]" }
12+
s.social_media_url = "http://twitter.com/benjaminencz"
13+
s.source = { :git => "https://github.com/Swift-Flow/Swift-Flow.git", :tag => s.version.to_s }
14+
s.ios.deployment_target = '8.0'
15+
s.osx.deployment_target = '10.10'
16+
s.tvos.deployment_target = '9.0'
17+
s.watchos.deployment_target = '2.0'
18+
s.requires_arc = true
19+
s.source_files = 'SwiftFlow/**/*.swift'
20+
end

0 commit comments

Comments
 (0)