from

problem
to
solution
soroush khanlou
dotSwift 2019
@khanlou
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
let urlRequest = URLRequestBuilder(request: request).urlRequest
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
let urlRequest = URLRequestBuilder(request: request).urlRequest
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
let urlRequest = URLRequestBuilder(request: request).urlRequest
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
let urlRequest = URLRequestBuilder(request: request).urlRequest
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
let urlRequest = URLRequestBuilder(request: request).urlRequest
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
let urlRequest = URLRequestBuilder(request: request).urlRequest
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
}
}
this is testable
how do we know it’s testable?
only one singleton
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
let urlRequest = URLRequestBuilder(request: request).urlRequest
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
let urlRequest = URLRequestBuilder(request: request).urlRequest
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
}
}
final class NetworkClient {
let session: URLSessionProtocol = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
let urlRequest = URLRequestBuilder(request: request).urlRequest
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
}
}
final class NetworkClient {
let session: URLSessionProtocol
init(session: URLSessionProtocol = URLSession.shared) {
self.session = session
}
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
let urlRequest = URLRequestBuilder(request: request).urlRequest
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
}
}
good code never
gets to stay good
what happens when we
try to add a feature?
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
let urlRequest = URLRequestBuilder(request: request).urlRequest
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
class RequestCounter {
static let shared = RequestCounter()
var counter = 0 {
didSet {
UIApplication.shared.isNetworkActivityIndicatorVisible = counter == 0
}
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
class RequestCounter {
static let shared = RequestCounter()
var counter = 0 {
didSet {
UIApplication.shared.isNetworkActivityIndicatorVisible = counter !== 0
}
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) !-> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200!!..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) !-> Promise<Output> {
let urlRequest = URLRequestBuilder(request: request).urlRequest
return session.data(with: urlRequest)
.then({ data, response in
guard (200!!..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
}
}
what happened??
single responsibility principle
class RequestCounter {
static let shared = RequestCounter()
var counter = 0 {
didSet {
UIApplication.shared.isNetworkActivityIndicatorVisible = counter !== 0
}
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) !-> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200!!..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
class RequestCounter {
static let shared = RequestCounter()
var counter = 0 {
didSet {
UIApplication.shared.isNetworkActivityIndicatorVisible = counter !== 0
}
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) !-> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200!!..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
class RequestCounter {
static let shared = RequestCounter()
var counter = 0 {
didSet {
UIApplication.shared.isNetworkActivityIndicatorVisible = counter !== 0
}
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) !-> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200!!..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
class RequestCounter {
static let shared = RequestCounter()
var counter = 0 {
didSet {
UIApplication.shared.isNetworkActivityIndicatorVisible = counter !== 0
}
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) !-> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200!!..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
singletons
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
final class NetworkClient {
let session = URLSession.shared
let application = UIApplication.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = application.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
application.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
application.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
final class NetworkClient {
let session = URLSession.shared
let application = UIApplication.shared
let defaults = UserDefaults.standard
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = defaults.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = application.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
application.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
application.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
final class NetworkClient {
let session = URLSession.shared
let application = UIApplication.shared
let defaults = UserDefaults.standard
let counter = RequestCounter.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = defaults.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = application.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
application.endBackgroundTask(identifier)
}
})
counter.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
application.endBackgroundTask(identifier)
}
counter.counter -= 1
})
}
}
final class NetworkClient {
let session = URLSession.shared
let application = UIApplication.shared
let defaults = UserDefaults.standard
let counter = RequestCounter.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = defaults.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = application.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
application.endBackgroundTask(identifier)
}
})
counter.counter += 1
final class NetworkClient {
let session: URLSessionProtocol = URLSession.shared
let application = UIApplication.shared
let defaults = UserDefaults.standard
let counter = RequestCounter.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = defaults.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = application.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
application.endBackgroundTask(identifier)
}
})
counter.counter += 1
final class NetworkClient {
let session: URLSessionProtocol = URLSession.shared
let application: UIApplicationProtocol = UIApplication.shared
let defaults = UserDefaults.standard
let counter = RequestCounter.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = defaults.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = application.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
application.endBackgroundTask(identifier)
}
})
counter.counter += 1
final class NetworkClient {
let session: URLSessionProtocol = URLSession.shared
let application: UIApplicationProtocol = UIApplication.shared
let defaults: UserDefaultsProtocol = UserDefaults.standard
let counter = RequestCounter.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = defaults.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = application.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
application.endBackgroundTask(identifier)
}
})
counter.counter += 1
final class NetworkClient {
let session: URLSessionProtocol = URLSession.shared
let application: UIApplicationProtocol = UIApplication.shared
let defaults: UserDefaultsProtocol = UserDefaults.standard
let counter: RequestCounterProtocol = RequestCounter.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = defaults.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = application.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
application.endBackgroundTask(identifier)
}
})
counter.counter += 1
final class NetworkClient {
let session: URLSessionProtocol
let application: UIApplicationProtocol
let defaults: UserDefaultsProtocol
let counter: RequestCounterProtocol
init(session: URLSessionProtocol = URLSession.shared, application:
UIApplicationProtocol = UIApplication.shared, defaults: UserDefaultsProtocol =
UserDefaults.standard, counter: RequestCounterProtocol = RequestCounter.shared) {
self.session = session
self.application = application
self.defaults = defaults
self.counter = counter
}
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = defaults.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
you don’t have to live like this!
we need to learn
how to abstract
why now?
why now?
step 1 - find the similarities in the problem
step 2 - develop the abstraction
step 3 - build commonalities and tools
step 4 - extract
step 5 - reap the benefits
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by time
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by time
headers - before - after
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by time
headers - before - after
final class NetworkClient {
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by time
headers - before - after
final class NetworkClient {
let session = URLSession.shared
let headers = Headers()
let before = Before()
let after = After()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by time
headers - before - after
final class NetworkClient {
let session = URLSession.shared
let headers = Headers()
let before = Before()
let after = After()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
headers.add(urlRequest)
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by time
headers - before - after
final class NetworkClient {
let session = URLSession.shared
let headers = Headers()
let before = Before()
let after = After()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
headers.add(urlRequest)
before.execute()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by time
headers - before - after
final class NetworkClient {
let session = URLSession.shared
let headers = Headers()
let before = Before()
let after = After()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
headers.add(urlRequest)
before.execute()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
after.execute()
})
}
}
by time
headers - before - after
we have a problem here,
which is that before and after
rely on a piece of state, identifier
final class NetworkClient {
let session = URLSession.shared
let headers = Headers()
let before = Before()
let after = After()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by time
what other ways can we slice up our code?
final class NetworkClient { // by time
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by time
headers - before - after
final class NetworkClient { // by time
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by time
headers - before - after
final class NetworkClient { // by time
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
final class NetworkClient { // by responsibility
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by time by responsibility
headers - before - after
final class NetworkClient { // by responsibility
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by responsibility
final class NetworkClient { // by responsibility
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by responsibility
auth token - backgrounding - counter
final class NetworkClient { // by responsibility
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by responsibility
auth token - backgrounding - counter
final class NetworkClient { // by responsibility
let session = URLSession.shared
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by responsibility
auth token - backgrounding - counter
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
if let authToken = UserDefaults.standard.string(forKey: "authToken") {
urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token")
}
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by responsibility
auth token - backgrounding - counter
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.add(to: urlRequest)
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
})
RequestCounter.shared.counter += 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by responsibility
auth token - backgrounding - counter
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.add(to: urlRequest)
backgrounding.prepare()


RequestCounter.shared.counter -= 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
if let identifier = identifier {
UIApplication.shared.endBackgroundTask(identifier)
}
RequestCounter.shared.counter -= 1
})
}
}
by responsibility
auth token - backgrounding - counter
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.add(to: urlRequest)
backgrounding.prepare()


RequestCounter.shared.counter -= 1
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.release()
RequestCounter.shared.counter -= 1
})
}
}
by responsibility
auth token - backgrounding - counter
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.add(to: urlRequest)
backgrounding.prepare()


counter.increment()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.release()
RequestCounter.shared.counter -= 1
})
}
}
by responsibility
auth token - backgrounding - counter
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.add(to: urlRequest)
backgrounding.prepare()


counter.increment()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.release()
counter.decrement()
})
}
}
by responsibility
auth token - backgrounding - counter
this can work for us
step 1 - find the similarities in the problem
step 2 - develop the abstraction
step 3 - build commonalities and tools
step 4 - extract
step 5 - reap the benefits
step 1 - find the similarities in the problem
step 2 - develop the abstraction
step 3 - build commonalities and tools
step 4 - extract
step 5 - reap the benefits
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.add(to: urlRequest)
backgrounding.prepare()
counter.increment()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.release()
counter.decrement()
})
}
}
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.add(to: urlRequest)
backgrounding.prepare()
counter.increment()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.release()
counter.decrement()
})
}
}
headers
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.add(to: urlRequest)
backgrounding.prepare()
counter.increment()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.release()
counter.decrement()
})
}
}
headers
before
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.add(to: urlRequest)
backgrounding.prepare()
counter.increment()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.release()
counter.decrement()
})
}
}
headers
before
after
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.addHeaders(to: urlRequest)
backgrounding.prepare()
counter.increment()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.release()
counter.decrement()
})
}
}
headers
before
after
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.addHeaders(to: urlRequest)
backgrounding.before()
counter.increment()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.release()
counter.decrement()
})
}
}
headers
before
after
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.addHeaders(to: urlRequest)
backgrounding.before()
counter.before()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.release()
counter.decrement()
})
}
}
headers
before
after
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.addHeaders(to: urlRequest)
backgrounding.before()
counter.before()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.after()
counter.decrement()
})
}
}
headers
before
after
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.addHeaders(to: urlRequest)
backgrounding.before()
counter.before()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.after()
counter.after()
})
}
}
headers
before
after
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.addHeaders(to: urlRequest)
backgrounding.before()
counter.before()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.after()
counter.after()
})
}
}
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.addHeaders(to: urlRequest)
backgrounding.before()
counter.before()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.after()
counter.after()
})
}
}
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.addHeaders(to: urlRequest)
backgrounding.before()
counter.before()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.after()
counter.after()
})
}
}
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.addHeaders(to: urlRequest)
backgrounding.before()
counter.before()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.after()
counter.after()
})
}
}
protocol RequestSideEffect {
func addHeaders(to request: URLRequest)
func before()
func after()
}
protocol RequestBehavior {
func addHeaders(to request: URLRequest)
func before()
func after()
}
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.addHeaders(to: urlRequest)
backgrounding.before()
counter.before()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.after()
counter.after()
})
}
}
final class NetworkClient {
let session = URLSession.shared
let authToken = AuthToken()
let counter = Counter()
let backgrounding = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.addHeaders(to: urlRequest)
backgrounding.before()
counter.before()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.after()
counter.after()
})
}
}
final class NetworkClient {
let session = URLSession.shared
let authToken: RequestBehavior = AuthToken()
let counter: RequestBehavior = Counter()
let backgrounding: RequestBehavior = Backgrounding()
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.addHeaders(to: urlRequest)
backgrounding.before()
counter.before()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.after()
counter.after()
})
}
}
final class NetworkClient {
let session = URLSession.shared
let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()]
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.addHeaders(to: urlRequest)
backgrounding.before()
counter.before()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.after()
counter.after()
})
}
}
final class NetworkClient {
let session = URLSession.shared
let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()]
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
authToken.addHeaders(to: urlRequest)
backgrounding.before()
counter.before()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.after()
counter.after()
})
}
}
final class NetworkClient {
let session = URLSession.shared
let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()]
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
behaviors.forEach({ $0.addHeaders(to: urlRequest) })
backgrounding.before()
counter.before()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.after()
counter.after()
})
}
}
final class NetworkClient {
let session = URLSession.shared
let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()]
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
behaviors.forEach({ $0.addHeaders(to: urlRequest) })
behaviors.forEach({ $0.before() })
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
backgrounding.after()
counter.after()
})
}
}
final class NetworkClient {
let session = URLSession.shared
let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()]
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
behaviors.forEach({ $0.addHeaders(to: urlRequest) })
behaviors.forEach({ $0.before() })
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
behaviors.forEach({ $0.after() })
})
}
}
final class NetworkClient {
let session = URLSession.shared
let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()]
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
behaviors.forEach({ $0.addHeaders(to: urlRequest) })
behaviors.forEach({ $0.before() })
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
behaviors.forEach({ $0.after() })
})
}
}
final class NetworkClient {
let session = URLSession.shared
let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()]
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
behaviors.forEach({ $0.addHeaders(to: urlRequest) })
behaviors.forEach({ $0.before() })
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
return try JSONDecoder().decode(Output.self, from: data)
})
.always({
behaviors.forEach({ $0.after() })
})
}
}
protocol RequestBehavior {
func addHeaders(to request: URLRequest)
func before()
func after()
}
protocol RequestBehavior {
var additionalHeaders: [String: String] { get }
func before()
func after()
}
protocol RequestBehavior {
var additionalHeaders: [String: String] { get }
func beforeSend()
func after()
}
protocol RequestBehavior {
var additionalHeaders: [String: String] { get }
func beforeSend()
func afterSuccess(result: AnyResponse)
func afterFailure(error: Error)
} // comment
protocol RequestBehavior {
var additionalHeaders: [String: String] { get }
func beforeSend()
func afterSuccess(result: AnyResponse)
func afterFailure(error: Error)
} // comment
extension RequestBehavior {
}
protocol RequestBehavior {
var additionalHeaders: [String: String] { get }
func beforeSend()
func afterSuccess(result: AnyResponse)
func afterFailure(error: Error)
} // comment
extension RequestBehavior {
var additionalHeaders: [String: String] { return [:] }
}
protocol RequestBehavior {
var additionalHeaders: [String: String] { get }
func beforeSend()
func afterSuccess(result: AnyResponse)
func afterFailure(error: Error)
} // comment
extension RequestBehavior {
var additionalHeaders: [String: String] { return [:] }
func beforeSend() { }
}
protocol RequestBehavior {
var additionalHeaders: [String: String] { get }
func beforeSend()
func afterSuccess(result: AnyResponse)
func afterFailure(error: Error)
} // comment
extension RequestBehavior {
var additionalHeaders: [String: String] { return [:] }
func beforeSend() { }
func afterSuccess(result: AnyResponse) { }
}
protocol RequestBehavior {
var additionalHeaders: [String: String] { get }
func beforeSend()
func afterSuccess(result: AnyResponse)
func afterFailure(error: Error)
} // comment
extension RequestBehavior {
var additionalHeaders: [String: String] { return [:] }
func beforeSend() { }
func afterSuccess(result: AnyResponse) { }
func afterFailure(error: Error) { }
}
protocol RequestBehavior {
var additionalHeaders: [String: String] { get }
func beforeSend()
func afterSuccess(result: AnyResponse)
func afterFailure(error: Error)
} // comment
step 1 - find the similarities in the problem
step 2 - develop the abstraction
step 3 - build commonalities and tools
step 4 - extract
step 5 - reap the benefits
a word on “don’t repeat yourself”
similar code dissimilar code
similar underlying
concepts
dissimilar underlying
concepts
similar code dissimilar code
similar underlying
concepts
dissimilar underlying
concepts
most code
similar code dissimilar code
similar underlying
concepts
probably already
“abstracted”
dissimilar underlying
concepts
most code
similar code dissimilar code
similar underlying
concepts
probably already
“abstracted”
dissimilar underlying
concepts
a world of pain
(false positive)
most code
similar code dissimilar code
similar underlying
concepts
probably already
“abstracted”
ripe for abstraction

(false negative)
dissimilar underlying
concepts
a world of pain
(false positive)
most code
– sandi metz
“duplication is far cheaper than
the wrong abstraction”
step 1 - find the similarities in the problem
step 2 - develop the abstraction
step 3 - build commonalities and tools
step 4 - extract
step 5 - reap the benefits
step 1 - find the similarities in the problem
step 2 - develop the abstraction
step 3 - build commonalities and tools
step 4 - extract
step 5 - reap the benefits
struct CombinedRequestBehavior: RequestBehavior {
let behaviors: [RequestBehavior]
var additionalHeaders: [String : String] {
return behaviors.reduce([String: String](), { sum, behavior in
return sum.merging(behavior.additionalHeaders, uniquingKeysWith: { $0 })
})
}
func beforeSend() {
behaviors.forEach({ $0.beforeSend() })
}
func afterSuccess(result: AnyResponse) {
behaviors.forEach({ $0.afterSuccess(result: result) })
}
func afterFailure(error: Error) {
behaviors.forEach({ $0.afterFailure(error: error) })
}
}
struct CombinedRequestBehavior: RequestBehavior {
let behaviors: [RequestBehavior]
var additionalHeaders: [String : String] {
return behaviors.reduce([String: String](), { sum, behavior in
return sum.merging(behavior.additionalHeaders, uniquingKeysWith: { $0 })
})
}
func beforeSend() {
behaviors.forEach({ $0.beforeSend() })
}
func afterSuccess(result: AnyResponse) {
behaviors.forEach({ $0.afterSuccess(result: result) })
}
func afterFailure(error: Error) {
behaviors.forEach({ $0.afterFailure(error: error) })
}
}
with CombinedRequestBehavior,
you can treat arrays like regular objects
final class NetworkClient {
let session = URLSession.shared
let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()]
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
behaviors.forEach({ $0.addHeaders(to: urlRequest) })
behaviors.forEach({ $0.beforeSend() })
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
let result = try JSONDecoder().decode(Output.self, from: data)
behaviors.forEach({ $0.afterSuccess(result) })
return result
})
.catch({ error in
behaviors.forEach({ $0.afterFailure(error) })
})
}
}
final class NetworkClient {
let session = URLSession.shared
let behavior = CombinedRequestBehavior([AuthToken(), Counter(), Backgrounding()])
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
behaviors.forEach({ $0.addHeaders(to: urlRequest) })
behaviors.forEach({ $0.beforeSend() })
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
let result = try JSONDecoder().decode(Output.self, from: data)
behaviors.forEach({ $0.afterSuccess(result) })
return result
})
.catch({ error in
behaviors.forEach({ $0.afterFailure(error) })
})
}
}
final class NetworkClient {
let session = URLSession.shared
let behavior = CombinedRequestBehavior([AuthToken(), Counter(), Backgrounding()])
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
behavior.addHeaders(to: urlRequest)
behaviors.forEach({ $0.beforeSend() })
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
let result = try JSONDecoder().decode(Output.self, from: data)
behaviors.forEach({ $0.afterSuccess(result) })
return result
})
.catch({ error in
behaviors.forEach({ $0.afterFailure(error) })
})
}
}
final class NetworkClient {
let session = URLSession.shared
let behavior = CombinedRequestBehavior([AuthToken(), Counter(), Backgrounding()])
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
behavior.addHeaders(to: urlRequest)
behavior.beforeSend()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
let result = try JSONDecoder().decode(Output.self, from: data)
behaviors.forEach({ $0.afterSuccess(result) })
return result
})
.catch({ error in
behaviors.forEach({ $0.afterFailure(error) })
})
}
}
final class NetworkClient {
let session = URLSession.shared
let behavior = CombinedRequestBehavior([AuthToken(), Counter(), Backgrounding()])
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
behavior.addHeaders(to: urlRequest)
behavior.beforeSend()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
let result = try JSONDecoder().decode(Output.self, from: data)
behavior.afterSuccess(result)
return result
})
.catch({ error in
behaviors.forEach({ $0.afterFailure(error) })
})
}
}
final class NetworkClient {
let session = URLSession.shared
let behavior = CombinedRequestBehavior([AuthToken(), Counter(), Backgrounding()])
func send<Output: Codable>(request: Request<Output>) -> Promise<Output> {
var urlRequest = URLRequestBuilder(request: request).urlRequest
behavior.addHeaders(to: urlRequest)
behavior.beforeSend()
return session.data(with: urlRequest)
.then({ data, response in
guard (200..<300).contains(response.statusCode) else {
throw StatusCodeError(statusCode: response.statusCode)
}
let result = try JSONDecoder().decode(Output.self, from: data)
behavior.afterSuccess(result)
return result
})
.catch({ error in
behavior.afterFailure(error)
})
}
}
step 1 - find the similarities in the problem
step 2 - develop the abstraction
step 3 - build commonalities and tools
step 4 - extract
step 5 - reap the benefits
step 1 - find the similarities in the problem
step 2 - develop the abstraction
step 3 - build commonalities and tools
step 4 - extract
step 5 - reap the benefits
final class AuthTokenHeaderBehavior: RequestBehavior {
let userDefaults = UserDefaults.standard
var additionalHeaders: [String: String] {
if let token = userDefaults.string(forKey: "authToken") {
return ["X-Auth-Token": token]
} else {
return [:]
}
}
}
final class AuthTokenHeaderBehavior: RequestBehavior {
let userDefaults = UserDefaults.standard
var additionalHeaders: [String: String] {
if let token = userDefaults.string(forKey: "authToken") {
return ["X-Auth-Token": token]
} else {
return [:]
}
}
}
final class AuthTokenHeaderBehavior: RequestBehavior {
let userDefaults = UserDefaults.standard
var additionalHeaders: [String: String] {
if let token = userDefaults.string(forKey: "authToken") {
return ["X-Auth-Token": token]
} else {
return [:]
}
}
}
final class AuthTokenHeaderBehavior: RequestBehavior {
let userDefaults = UserDefaults.standard
var additionalHeaders: [String: String] {
if let token = userDefaults.string(forKey: "authToken") {
return ["X-Auth-Token": token]
} else {
return [:]
}
}
}
final class AuthTokenHeaderBehavior: RequestBehavior {
let userDefaults = UserDefaults.standard
var additionalHeaders: [String: String] {
if let token = userDefaults.string(forKey: "authToken") {
return ["X-Auth-Token": token]
} else {
return [:]
}
}
}
final class NetworkActivityIndicatorBehavior: RequestBehavior {
// ...
}
final class BackgroundTaskBehavior: RequestBehavior {
// ...
}
step 1 - find the similarities in the problem
step 2 - develop the abstraction
step 3 - build commonalities and tools
step 4 - extract
step 5 - reap the benefits
step 1 - find the similarities in the problem
step 2 - develop the abstraction
step 3 - build commonalities and tools
step 4 - extract
step 5 - reap the benefits
decoupling
decoupling is important because it allows you to
build strong boundaries, which allows you to
handle separate components separately
what do i mean

by “handle”?
you can test it
final class AuthTokenHeaderBehavior: RequestBehavior {
let userDefaults = UserDefaults.standard
var additionalHeaders: [String: String] {
if let token = userDefaults.string(forKey: "authToken") {
return ["X-Auth-Token": token]
} else {
return [:]
}
}
}
you can reuse it
final class NetworkActivityIndicatorBehavior: RequestBehavior {
// ...
}
you can glance at it
final class ErrorLogger: RequestBehavior {
let logger = Logger.default
func afterFailure(error: Error) {
logger.error(“Request failed: (error)")
}
}
“There is no abstract art.  You must
always start with something. 
Afterward you can remove all
traces of reality.” – Pablo Picasso
dotSwift - From Problem to Solution

dotSwift - From Problem to Solution

  • 1.
  • 3.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { let urlRequest = URLRequestBuilder(request: request).urlRequest return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) } }
  • 4.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { let urlRequest = URLRequestBuilder(request: request).urlRequest return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) } }
  • 5.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { let urlRequest = URLRequestBuilder(request: request).urlRequest return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) } }
  • 6.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { let urlRequest = URLRequestBuilder(request: request).urlRequest return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) } }
  • 7.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { let urlRequest = URLRequestBuilder(request: request).urlRequest return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) } }
  • 8.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { let urlRequest = URLRequestBuilder(request: request).urlRequest return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) } }
  • 9.
  • 10.
    how do weknow it’s testable?
  • 11.
  • 12.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { let urlRequest = URLRequestBuilder(request: request).urlRequest return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) } }
  • 13.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { let urlRequest = URLRequestBuilder(request: request).urlRequest return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) } }
  • 14.
    final class NetworkClient{ let session: URLSessionProtocol = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { let urlRequest = URLRequestBuilder(request: request).urlRequest return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) } }
  • 15.
    final class NetworkClient{ let session: URLSessionProtocol init(session: URLSessionProtocol = URLSession.shared) { self.session = session } func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { let urlRequest = URLRequestBuilder(request: request).urlRequest return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) } }
  • 16.
  • 17.
    what happens whenwe try to add a feature?
  • 18.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { let urlRequest = URLRequestBuilder(request: request).urlRequest return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) } }
  • 19.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) } }
  • 20.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) } }
  • 21.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier)
  • 22.
    } var identifier: UIBackgroundTaskIdentifier? identifier= UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) } }
  • 23.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier)
  • 24.
    class RequestCounter { staticlet shared = RequestCounter() var counter = 0 { didSet { UIApplication.shared.isNetworkActivityIndicatorVisible = counter == 0 } } } final class NetworkClient { let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } })
  • 25.
    if let authToken= UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } }
  • 26.
    if let authToken= UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } }
  • 27.
    class RequestCounter { staticlet shared = RequestCounter() var counter = 0 { didSet { UIApplication.shared.isNetworkActivityIndicatorVisible = counter !== 0 } } } final class NetworkClient { let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) !-> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200!!..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } final class NetworkClient { let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) !-> Promise<Output> { let urlRequest = URLRequestBuilder(request: request).urlRequest return session.data(with: urlRequest) .then({ data, response in guard (200!!..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) } }
  • 28.
  • 29.
  • 30.
    class RequestCounter { staticlet shared = RequestCounter() var counter = 0 { didSet { UIApplication.shared.isNetworkActivityIndicatorVisible = counter !== 0 } } } final class NetworkClient { let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) !-> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200!!..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } }
  • 31.
    class RequestCounter { staticlet shared = RequestCounter() var counter = 0 { didSet { UIApplication.shared.isNetworkActivityIndicatorVisible = counter !== 0 } } } final class NetworkClient { let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) !-> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200!!..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } }
  • 32.
    class RequestCounter { staticlet shared = RequestCounter() var counter = 0 { didSet { UIApplication.shared.isNetworkActivityIndicatorVisible = counter !== 0 } } } final class NetworkClient { let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) !-> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200!!..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } }
  • 33.
    class RequestCounter { staticlet shared = RequestCounter() var counter = 0 { didSet { UIApplication.shared.isNetworkActivityIndicatorVisible = counter !== 0 } } } final class NetworkClient { let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) !-> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200!!..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } }
  • 34.
  • 35.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } }
  • 36.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } }
  • 37.
    final class NetworkClient{ let session = URLSession.shared let application = UIApplication.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = application.beginBackgroundTask(expirationHandler: { if let identifier = identifier { application.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { application.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } }
  • 38.
    final class NetworkClient{ let session = URLSession.shared let application = UIApplication.shared let defaults = UserDefaults.standard func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = defaults.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = application.beginBackgroundTask(expirationHandler: { if let identifier = identifier { application.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { application.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } }
  • 39.
    final class NetworkClient{ let session = URLSession.shared let application = UIApplication.shared let defaults = UserDefaults.standard let counter = RequestCounter.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = defaults.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = application.beginBackgroundTask(expirationHandler: { if let identifier = identifier { application.endBackgroundTask(identifier) } }) counter.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { application.endBackgroundTask(identifier) } counter.counter -= 1 }) } }
  • 40.
    final class NetworkClient{ let session = URLSession.shared let application = UIApplication.shared let defaults = UserDefaults.standard let counter = RequestCounter.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = defaults.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = application.beginBackgroundTask(expirationHandler: { if let identifier = identifier { application.endBackgroundTask(identifier) } }) counter.counter += 1
  • 41.
    final class NetworkClient{ let session: URLSessionProtocol = URLSession.shared let application = UIApplication.shared let defaults = UserDefaults.standard let counter = RequestCounter.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = defaults.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = application.beginBackgroundTask(expirationHandler: { if let identifier = identifier { application.endBackgroundTask(identifier) } }) counter.counter += 1
  • 42.
    final class NetworkClient{ let session: URLSessionProtocol = URLSession.shared let application: UIApplicationProtocol = UIApplication.shared let defaults = UserDefaults.standard let counter = RequestCounter.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = defaults.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = application.beginBackgroundTask(expirationHandler: { if let identifier = identifier { application.endBackgroundTask(identifier) } }) counter.counter += 1
  • 43.
    final class NetworkClient{ let session: URLSessionProtocol = URLSession.shared let application: UIApplicationProtocol = UIApplication.shared let defaults: UserDefaultsProtocol = UserDefaults.standard let counter = RequestCounter.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = defaults.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = application.beginBackgroundTask(expirationHandler: { if let identifier = identifier { application.endBackgroundTask(identifier) } }) counter.counter += 1
  • 44.
    final class NetworkClient{ let session: URLSessionProtocol = URLSession.shared let application: UIApplicationProtocol = UIApplication.shared let defaults: UserDefaultsProtocol = UserDefaults.standard let counter: RequestCounterProtocol = RequestCounter.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = defaults.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = application.beginBackgroundTask(expirationHandler: { if let identifier = identifier { application.endBackgroundTask(identifier) } }) counter.counter += 1
  • 45.
    final class NetworkClient{ let session: URLSessionProtocol let application: UIApplicationProtocol let defaults: UserDefaultsProtocol let counter: RequestCounterProtocol init(session: URLSessionProtocol = URLSession.shared, application: UIApplicationProtocol = UIApplication.shared, defaults: UserDefaultsProtocol = UserDefaults.standard, counter: RequestCounterProtocol = RequestCounter.shared) { self.session = session self.application = application self.defaults = defaults self.counter = counter } func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = defaults.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") }
  • 46.
    you don’t haveto live like this!
  • 47.
  • 48.
  • 49.
  • 52.
  • 53.
    step 1 -find the similarities in the problem step 2 - develop the abstraction step 3 - build commonalities and tools step 4 - extract step 5 - reap the benefits
  • 54.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by time
  • 55.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by time headers - before - after
  • 56.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by time headers - before - after
  • 57.
    final class NetworkClient{ let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by time headers - before - after
  • 58.
    final class NetworkClient{ let session = URLSession.shared let headers = Headers() let before = Before() let after = After() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by time headers - before - after
  • 59.
    final class NetworkClient{ let session = URLSession.shared let headers = Headers() let before = Before() let after = After() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest headers.add(urlRequest) var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by time headers - before - after
  • 60.
    final class NetworkClient{ let session = URLSession.shared let headers = Headers() let before = Before() let after = After() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest headers.add(urlRequest) before.execute() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by time headers - before - after
  • 61.
    final class NetworkClient{ let session = URLSession.shared let headers = Headers() let before = Before() let after = After() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest headers.add(urlRequest) before.execute() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ after.execute() }) } } by time headers - before - after
  • 62.
    we have aproblem here, which is that before and after rely on a piece of state, identifier
  • 63.
    final class NetworkClient{ let session = URLSession.shared let headers = Headers() let before = Before() let after = After() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by time
  • 64.
    what other wayscan we slice up our code?
  • 65.
    final class NetworkClient{ // by time let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by time headers - before - after
  • 66.
    final class NetworkClient{ // by time let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by time headers - before - after
  • 67.
    final class NetworkClient{ // by time let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } final class NetworkClient { // by responsibility let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by time by responsibility headers - before - after
  • 68.
    final class NetworkClient{ // by responsibility let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by responsibility
  • 69.
    final class NetworkClient{ // by responsibility let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by responsibility auth token - backgrounding - counter
  • 70.
    final class NetworkClient{ // by responsibility let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by responsibility auth token - backgrounding - counter
  • 71.
    final class NetworkClient{ // by responsibility let session = URLSession.shared func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by responsibility auth token - backgrounding - counter
  • 72.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest if let authToken = UserDefaults.standard.string(forKey: "authToken") { urlRequest.addValue(authToken, forHTTPHeaderField: "X-Auth-Token") } var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by responsibility auth token - backgrounding - counter
  • 73.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.add(to: urlRequest) var identifier: UIBackgroundTaskIdentifier? identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } }) RequestCounter.shared.counter += 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by responsibility auth token - backgrounding - counter
  • 74.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.add(to: urlRequest) backgrounding.prepare() 
 RequestCounter.shared.counter -= 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ if let identifier = identifier { UIApplication.shared.endBackgroundTask(identifier) } RequestCounter.shared.counter -= 1 }) } } by responsibility auth token - backgrounding - counter
  • 75.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.add(to: urlRequest) backgrounding.prepare() 
 RequestCounter.shared.counter -= 1 return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.release() RequestCounter.shared.counter -= 1 }) } } by responsibility auth token - backgrounding - counter
  • 76.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.add(to: urlRequest) backgrounding.prepare() 
 counter.increment() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.release() RequestCounter.shared.counter -= 1 }) } } by responsibility auth token - backgrounding - counter
  • 77.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.add(to: urlRequest) backgrounding.prepare() 
 counter.increment() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.release() counter.decrement() }) } } by responsibility auth token - backgrounding - counter
  • 78.
  • 79.
    step 1 -find the similarities in the problem step 2 - develop the abstraction step 3 - build commonalities and tools step 4 - extract step 5 - reap the benefits
  • 83.
    step 1 -find the similarities in the problem step 2 - develop the abstraction step 3 - build commonalities and tools step 4 - extract step 5 - reap the benefits
  • 84.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.add(to: urlRequest) backgrounding.prepare() counter.increment() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.release() counter.decrement() }) } }
  • 85.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.add(to: urlRequest) backgrounding.prepare() counter.increment() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.release() counter.decrement() }) } } headers
  • 86.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.add(to: urlRequest) backgrounding.prepare() counter.increment() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.release() counter.decrement() }) } } headers before
  • 87.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.add(to: urlRequest) backgrounding.prepare() counter.increment() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.release() counter.decrement() }) } } headers before after
  • 88.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.addHeaders(to: urlRequest) backgrounding.prepare() counter.increment() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.release() counter.decrement() }) } } headers before after
  • 89.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.addHeaders(to: urlRequest) backgrounding.before() counter.increment() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.release() counter.decrement() }) } } headers before after
  • 90.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.addHeaders(to: urlRequest) backgrounding.before() counter.before() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.release() counter.decrement() }) } } headers before after
  • 91.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.addHeaders(to: urlRequest) backgrounding.before() counter.before() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.after() counter.decrement() }) } } headers before after
  • 92.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.addHeaders(to: urlRequest) backgrounding.before() counter.before() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.after() counter.after() }) } } headers before after
  • 93.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.addHeaders(to: urlRequest) backgrounding.before() counter.before() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.after() counter.after() }) } }
  • 94.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.addHeaders(to: urlRequest) backgrounding.before() counter.before() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.after() counter.after() }) } }
  • 95.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.addHeaders(to: urlRequest) backgrounding.before() counter.before() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.after() counter.after() }) } }
  • 96.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.addHeaders(to: urlRequest) backgrounding.before() counter.before() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.after() counter.after() }) } }
  • 97.
    protocol RequestSideEffect { funcaddHeaders(to request: URLRequest) func before() func after() }
  • 98.
    protocol RequestBehavior { funcaddHeaders(to request: URLRequest) func before() func after() }
  • 99.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.addHeaders(to: urlRequest) backgrounding.before() counter.before() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.after() counter.after() }) } }
  • 100.
    final class NetworkClient{ let session = URLSession.shared let authToken = AuthToken() let counter = Counter() let backgrounding = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.addHeaders(to: urlRequest) backgrounding.before() counter.before() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.after() counter.after() }) } }
  • 101.
    final class NetworkClient{ let session = URLSession.shared let authToken: RequestBehavior = AuthToken() let counter: RequestBehavior = Counter() let backgrounding: RequestBehavior = Backgrounding() func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.addHeaders(to: urlRequest) backgrounding.before() counter.before() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.after() counter.after() }) } }
  • 102.
    final class NetworkClient{ let session = URLSession.shared let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()] func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.addHeaders(to: urlRequest) backgrounding.before() counter.before() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.after() counter.after() }) } }
  • 103.
    final class NetworkClient{ let session = URLSession.shared let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()] func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest authToken.addHeaders(to: urlRequest) backgrounding.before() counter.before() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.after() counter.after() }) } }
  • 104.
    final class NetworkClient{ let session = URLSession.shared let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()] func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest behaviors.forEach({ $0.addHeaders(to: urlRequest) }) backgrounding.before() counter.before() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.after() counter.after() }) } }
  • 105.
    final class NetworkClient{ let session = URLSession.shared let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()] func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest behaviors.forEach({ $0.addHeaders(to: urlRequest) }) behaviors.forEach({ $0.before() }) return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ backgrounding.after() counter.after() }) } }
  • 106.
    final class NetworkClient{ let session = URLSession.shared let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()] func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest behaviors.forEach({ $0.addHeaders(to: urlRequest) }) behaviors.forEach({ $0.before() }) return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ behaviors.forEach({ $0.after() }) }) } }
  • 107.
    final class NetworkClient{ let session = URLSession.shared let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()] func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest behaviors.forEach({ $0.addHeaders(to: urlRequest) }) behaviors.forEach({ $0.before() }) return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ behaviors.forEach({ $0.after() }) }) } }
  • 108.
    final class NetworkClient{ let session = URLSession.shared let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()] func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest behaviors.forEach({ $0.addHeaders(to: urlRequest) }) behaviors.forEach({ $0.before() }) return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } return try JSONDecoder().decode(Output.self, from: data) }) .always({ behaviors.forEach({ $0.after() }) }) } }
  • 109.
    protocol RequestBehavior { funcaddHeaders(to request: URLRequest) func before() func after() }
  • 110.
    protocol RequestBehavior { varadditionalHeaders: [String: String] { get } func before() func after() }
  • 111.
    protocol RequestBehavior { varadditionalHeaders: [String: String] { get } func beforeSend() func after() }
  • 112.
    protocol RequestBehavior { varadditionalHeaders: [String: String] { get } func beforeSend() func afterSuccess(result: AnyResponse) func afterFailure(error: Error) } // comment
  • 113.
    protocol RequestBehavior { varadditionalHeaders: [String: String] { get } func beforeSend() func afterSuccess(result: AnyResponse) func afterFailure(error: Error) } // comment
  • 114.
    extension RequestBehavior { } protocolRequestBehavior { var additionalHeaders: [String: String] { get } func beforeSend() func afterSuccess(result: AnyResponse) func afterFailure(error: Error) } // comment
  • 115.
    extension RequestBehavior { varadditionalHeaders: [String: String] { return [:] } } protocol RequestBehavior { var additionalHeaders: [String: String] { get } func beforeSend() func afterSuccess(result: AnyResponse) func afterFailure(error: Error) } // comment
  • 116.
    extension RequestBehavior { varadditionalHeaders: [String: String] { return [:] } func beforeSend() { } } protocol RequestBehavior { var additionalHeaders: [String: String] { get } func beforeSend() func afterSuccess(result: AnyResponse) func afterFailure(error: Error) } // comment
  • 117.
    extension RequestBehavior { varadditionalHeaders: [String: String] { return [:] } func beforeSend() { } func afterSuccess(result: AnyResponse) { } } protocol RequestBehavior { var additionalHeaders: [String: String] { get } func beforeSend() func afterSuccess(result: AnyResponse) func afterFailure(error: Error) } // comment
  • 118.
    extension RequestBehavior { varadditionalHeaders: [String: String] { return [:] } func beforeSend() { } func afterSuccess(result: AnyResponse) { } func afterFailure(error: Error) { } } protocol RequestBehavior { var additionalHeaders: [String: String] { get } func beforeSend() func afterSuccess(result: AnyResponse) func afterFailure(error: Error) } // comment
  • 119.
    step 1 -find the similarities in the problem step 2 - develop the abstraction step 3 - build commonalities and tools step 4 - extract step 5 - reap the benefits
  • 123.
    a word on“don’t repeat yourself”
  • 124.
    similar code dissimilarcode similar underlying concepts dissimilar underlying concepts
  • 125.
    similar code dissimilarcode similar underlying concepts dissimilar underlying concepts most code
  • 126.
    similar code dissimilarcode similar underlying concepts probably already “abstracted” dissimilar underlying concepts most code
  • 127.
    similar code dissimilarcode similar underlying concepts probably already “abstracted” dissimilar underlying concepts a world of pain (false positive) most code
  • 128.
    similar code dissimilarcode similar underlying concepts probably already “abstracted” ripe for abstraction
 (false negative) dissimilar underlying concepts a world of pain (false positive) most code
  • 129.
    – sandi metz “duplicationis far cheaper than the wrong abstraction”
  • 130.
    step 1 -find the similarities in the problem step 2 - develop the abstraction step 3 - build commonalities and tools step 4 - extract step 5 - reap the benefits
  • 131.
    step 1 -find the similarities in the problem step 2 - develop the abstraction step 3 - build commonalities and tools step 4 - extract step 5 - reap the benefits
  • 132.
    struct CombinedRequestBehavior: RequestBehavior{ let behaviors: [RequestBehavior] var additionalHeaders: [String : String] { return behaviors.reduce([String: String](), { sum, behavior in return sum.merging(behavior.additionalHeaders, uniquingKeysWith: { $0 }) }) } func beforeSend() { behaviors.forEach({ $0.beforeSend() }) } func afterSuccess(result: AnyResponse) { behaviors.forEach({ $0.afterSuccess(result: result) }) } func afterFailure(error: Error) { behaviors.forEach({ $0.afterFailure(error: error) }) } }
  • 133.
    struct CombinedRequestBehavior: RequestBehavior{ let behaviors: [RequestBehavior] var additionalHeaders: [String : String] { return behaviors.reduce([String: String](), { sum, behavior in return sum.merging(behavior.additionalHeaders, uniquingKeysWith: { $0 }) }) } func beforeSend() { behaviors.forEach({ $0.beforeSend() }) } func afterSuccess(result: AnyResponse) { behaviors.forEach({ $0.afterSuccess(result: result) }) } func afterFailure(error: Error) { behaviors.forEach({ $0.afterFailure(error: error) }) } }
  • 134.
    with CombinedRequestBehavior, you cantreat arrays like regular objects
  • 135.
    final class NetworkClient{ let session = URLSession.shared let behaviors: [RequestBehavior] = [AuthToken(), Counter(), Backgrounding()] func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest behaviors.forEach({ $0.addHeaders(to: urlRequest) }) behaviors.forEach({ $0.beforeSend() }) return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } let result = try JSONDecoder().decode(Output.self, from: data) behaviors.forEach({ $0.afterSuccess(result) }) return result }) .catch({ error in behaviors.forEach({ $0.afterFailure(error) }) }) } }
  • 136.
    final class NetworkClient{ let session = URLSession.shared let behavior = CombinedRequestBehavior([AuthToken(), Counter(), Backgrounding()]) func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest behaviors.forEach({ $0.addHeaders(to: urlRequest) }) behaviors.forEach({ $0.beforeSend() }) return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } let result = try JSONDecoder().decode(Output.self, from: data) behaviors.forEach({ $0.afterSuccess(result) }) return result }) .catch({ error in behaviors.forEach({ $0.afterFailure(error) }) }) } }
  • 137.
    final class NetworkClient{ let session = URLSession.shared let behavior = CombinedRequestBehavior([AuthToken(), Counter(), Backgrounding()]) func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest behavior.addHeaders(to: urlRequest) behaviors.forEach({ $0.beforeSend() }) return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } let result = try JSONDecoder().decode(Output.self, from: data) behaviors.forEach({ $0.afterSuccess(result) }) return result }) .catch({ error in behaviors.forEach({ $0.afterFailure(error) }) }) } }
  • 138.
    final class NetworkClient{ let session = URLSession.shared let behavior = CombinedRequestBehavior([AuthToken(), Counter(), Backgrounding()]) func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest behavior.addHeaders(to: urlRequest) behavior.beforeSend() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } let result = try JSONDecoder().decode(Output.self, from: data) behaviors.forEach({ $0.afterSuccess(result) }) return result }) .catch({ error in behaviors.forEach({ $0.afterFailure(error) }) }) } }
  • 139.
    final class NetworkClient{ let session = URLSession.shared let behavior = CombinedRequestBehavior([AuthToken(), Counter(), Backgrounding()]) func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest behavior.addHeaders(to: urlRequest) behavior.beforeSend() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } let result = try JSONDecoder().decode(Output.self, from: data) behavior.afterSuccess(result) return result }) .catch({ error in behaviors.forEach({ $0.afterFailure(error) }) }) } }
  • 140.
    final class NetworkClient{ let session = URLSession.shared let behavior = CombinedRequestBehavior([AuthToken(), Counter(), Backgrounding()]) func send<Output: Codable>(request: Request<Output>) -> Promise<Output> { var urlRequest = URLRequestBuilder(request: request).urlRequest behavior.addHeaders(to: urlRequest) behavior.beforeSend() return session.data(with: urlRequest) .then({ data, response in guard (200..<300).contains(response.statusCode) else { throw StatusCodeError(statusCode: response.statusCode) } let result = try JSONDecoder().decode(Output.self, from: data) behavior.afterSuccess(result) return result }) .catch({ error in behavior.afterFailure(error) }) } }
  • 144.
    step 1 -find the similarities in the problem step 2 - develop the abstraction step 3 - build commonalities and tools step 4 - extract step 5 - reap the benefits
  • 145.
    step 1 -find the similarities in the problem step 2 - develop the abstraction step 3 - build commonalities and tools step 4 - extract step 5 - reap the benefits
  • 146.
    final class AuthTokenHeaderBehavior:RequestBehavior { let userDefaults = UserDefaults.standard var additionalHeaders: [String: String] { if let token = userDefaults.string(forKey: "authToken") { return ["X-Auth-Token": token] } else { return [:] } } }
  • 147.
    final class AuthTokenHeaderBehavior:RequestBehavior { let userDefaults = UserDefaults.standard var additionalHeaders: [String: String] { if let token = userDefaults.string(forKey: "authToken") { return ["X-Auth-Token": token] } else { return [:] } } }
  • 148.
    final class AuthTokenHeaderBehavior:RequestBehavior { let userDefaults = UserDefaults.standard var additionalHeaders: [String: String] { if let token = userDefaults.string(forKey: "authToken") { return ["X-Auth-Token": token] } else { return [:] } } }
  • 149.
    final class AuthTokenHeaderBehavior:RequestBehavior { let userDefaults = UserDefaults.standard var additionalHeaders: [String: String] { if let token = userDefaults.string(forKey: "authToken") { return ["X-Auth-Token": token] } else { return [:] } } }
  • 150.
    final class AuthTokenHeaderBehavior:RequestBehavior { let userDefaults = UserDefaults.standard var additionalHeaders: [String: String] { if let token = userDefaults.string(forKey: "authToken") { return ["X-Auth-Token": token] } else { return [:] } } }
  • 151.
  • 152.
    final class BackgroundTaskBehavior:RequestBehavior { // ... }
  • 153.
    step 1 -find the similarities in the problem step 2 - develop the abstraction step 3 - build commonalities and tools step 4 - extract step 5 - reap the benefits
  • 154.
    step 1 -find the similarities in the problem step 2 - develop the abstraction step 3 - build commonalities and tools step 4 - extract step 5 - reap the benefits
  • 155.
    decoupling decoupling is importantbecause it allows you to build strong boundaries, which allows you to handle separate components separately
  • 156.
    what do imean
 by “handle”?
  • 157.
  • 158.
    final class AuthTokenHeaderBehavior:RequestBehavior { let userDefaults = UserDefaults.standard var additionalHeaders: [String: String] { if let token = userDefaults.string(forKey: "authToken") { return ["X-Auth-Token": token] } else { return [:] } } }
  • 159.
  • 160.
  • 161.
  • 162.
    final class ErrorLogger:RequestBehavior { let logger = Logger.default func afterFailure(error: Error) { logger.error(“Request failed: (error)") } }
  • 164.
    “There is noabstract art.  You must always start with something.  Afterward you can remove all traces of reality.” – Pablo Picasso