View
2
Download
0
Category
Preview:
Citation preview
SWIFT:um equilíbrio sofis.cado entre segurança, ergonomia, produ.vidade e preservação de legado
NA PRÁTICA
ENTIDADESstruct Ticket: Equatable { typealias Id = Tagged<Ticket, Int> typealias Participant = (firstName: String, lastName: String)
let identifier: Id let kind: String let price: NSDecimalNumber let participant: Participant
static func == (lhs: Ticket, rhs: Ticket) -> Bool { return lhs.identifier == rhs.identifier && lhs.kind == rhs.kind && lhs.price == rhs.price && lhs.participant == rhs.participant }}
MODELOS DE ESTADOenum OrderListState: Equatable { case loading case refreshing case loaded(data: [Order]) case failed(error: OrderListError)
static func == (lhs: OrderListState, rhs: OrderListState) -> Bool { switch (lhs, rhs) { case (.loading, .loading): return true case (.refreshing, .refreshing): return true case let (.loaded(lhsData), .loaded(lhsData)): return lhsData == lhsData case let (.failed(lhsError), .failed(rhsError)): return lhsError == rhsError default: return false } }}
INTERAÇÕES DE TELAenum OrderListAction: Action, Equatable { case startLoading case startRefreshing case load(data: [Order]) case append(data: [Order]) case fail(error: OrderListError)
static func == (lhs: OrderListAction, rhs: OrderListAction) -> Bool { // ... }}
TRANSIÇÕES DE ESTADOlet orderListReducer = Reducer<OrderListState, Action> { state, action in switch action { case OrderListAction.startLoading: return .loading case OrderListAction.load(let data): return .loaded(data: data) case OrderListAction.append(let data): if case .loaded(let previousData) = state { return .loaded(data: previousData + data) } else { return state } case OrderListAction.fail(let error): return .failed(error: error) default: return state }}
FLUXO DE DADO UNDIRECIONALstruct ApplicationState { // ...
let orderListState: OrderListState
// ...}
let applicationStore = Store<ApplicationState>( // A assinatura de `orderListReducer` precisa ser "promovida" // a Reducer<ApplicationState, Action> reducer: Reducer.combine( orderListReducer.lift(state: \ApplicationState.orderListState), // ... ))
// ...
let disposable = applicationStore.subscribe { applicationState in // ...}
TELAS DERIVADAS DO ESTADOclass OrderListViewController: UIViewController { // ... override func viewDidLoad() { super.viewDidLoad()
let disposable = applicationStore.subscribe { applicationState in self.orderListView.render(state: applicationState.orderListState) }
disposeBag.add(disposable) } // ...}
O QUE GANHAMOS COM ISSO?• Produ'vidade;
• Testabilidade;
• Previsibilidade;
INTEROPERABILIDADE
INTEROPERABILIDADE@interface ParticipantsSync : NSObject
@property (nonatomic, strong, readonly) NSArray<ParticipantChangeNew *> * _Nonnull participantsNew;@property (nonatomic, strong, readonly) NSArray<ParticipantChangeCancelled *> * _Nonnull participantsCancelled;@property (nonatomic, strong, readonly) NSArray<ParticipantChangeCheckIn *> * _Nonnull participantsCheckIn;@property (nonatomic, strong, readonly) NSArray<ParticipantChangeCheckOut *> * _Nonnull participantsCheckOut;@property (nonatomic, copy, readonly) NSDate * _Nonnull syncDate;
- (instancetype _Nullable)initWithJsonData:(NSData * _Nonnull)data;
@end
@interface ParticipantChangeNew : NSObject
@property (nonatomic, copy, readonly) NSString * _Nonnull ticketNumber;@property (nonatomic, copy, readonly) NSString * _Nonnull ticketTypeId;@property (nonatomic, copy, readonly) NSString * _Nonnull firstName;@property (nonatomic, copy, readonly) NSString * _Nullable lastName;@property (nonatomic, copy, readonly) NSString * _Nonnull email;@property (nonatomic, assign, readonly) BOOL checkIn;@property (nonatomic, copy, readonly) NSNumber * _Nullable time;
- (instancetype _Nullable)initWithDictionary:(NSDictionary * _Nonnull)dictionary;
@end
INTEROPERABILIDADEclass ParticipantSyncTests: XCTestCase { func test() { // ...
let sync = ParticipantsSync(jsonData: jsonData)
for change in sync.participantsNew { expect(change.checkIn).to(beTrue()) expect(change.time) > 1485956221 }
let firstParticipant = sync.participantsNew[0] expect(participant).toNot(beNil()) expect(participant!.ticketNumber).to(equal("PMHYYCAJ1X")) expect(participant!.ticketTypeId).to(equal("281600")) expect(participant!.firstName).to(equal("Guilherme")) expect(participant!.lastName).to(equal("De Andrade")) expect(participant!.checkIn).to(equal(false)) expect(participant!.time).to(beNil())
// ... }}
INTEROPERABILIDADE// Como é em Cstruct CGRect { CGPoint origin; CGSize size;};typedef struct CGRect CGRect;
INTEROPERABILIDADE// Como é importado em Swift (automaticamente)struct CGRect { var origin: CGPoint var size: CGSize
init() init(origin: CGPoint, size: CGSize)}
INTEROPERABILIDADE// Como é em CCGRect CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height) CF_SWIFT_NAME(CGRect.init(x:y:width:height:));BOOL CGRectContains(CGRect r1, CGRect r2) CF_SWIFT_NAME(CGRect.contains(self:_));CGRect CGRectIntersection(CGRect r1, CGRect r2) CF_SWIFT_NAME(CGRect.intersection(self:_));
INTEROPERABILIDADE// Como é em CCGRect CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height) CF_SWIFT_NAME(CGRect.init(x:y:width:height:));BOOL CGRectContains(CGRect r1, CGRect r2) CF_SWIFT_NAME(CGRect.contains(self:_));CGRect CGRectIntersection(CGRect r1, CGRect r2) CF_SWIFT_NAME(CGRect.intersection(self:_));
// Como é importado em Swiftextension CGRect { init (origin: CGPoint, size: CGSize) func contains(_ r2: CGRect) -> Bool func intersection(_ r2: CGRect) -> CGRect}
TYPE SAFETY
OPTIONALS
O #po Op#onal<T> é a representação em Swi7 da possibilidade de ausência de valor.
var optionalInt: Int? // Optional<Int>
OPTIONALS
Variáveis do ,po Int? podem receber valores do ,po Int mas também podem receber um valor especial nil que representa a ausência de valor.
optionalInt = nil // .noneoptionalInt = 1 // .some(1)
OPTIONALS
Os $pos Int? e Int estão relacionados, mas são dis$ntos para o compilador. Não é possível combinar diretamente valores de $po Int e $po Int? usando operadores built-in da linguagem, por exemplo, pois eles trabalham com valores de $pos iguais.
let nonOptionalInt: Int = 5
// Value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'?print(optionalInt + nonOptionalInt)
OPTIONALSUm valor do *po Int? pode ser interpretado como um valor do *po Int com um contexto semân*co adicional: a possibilidade de ausência. Para somar um valor do *po Int? a um valor do *po Int, o programador precisar "extrair" um valor do *po Int de dentro do contexto.
// Extração seguraif let safelyUnwrappedInt = optionalInt { print(safelyUnwrappedInt + nonOptionalInt)}
// Extração insegura e perigosalet explictlyUnwrappedInt = optionalInt!print(explictlyUnwrappedInt + nonOptionalInt)
OPTIONALSO #po Event da camada de modelo do app iOS da Sympla tem um campo image do #po URL?, pois alguns eventos não possuem imagem associada.
Na primeira do app, escrita em Objec2ve-C, este 2po foi definido sem verificações do compilador para a ausência do valor image em algumas instâncias de Event.
@interface Event: NSObject
// ...
@property (nonatomic, strong, readonly) NSString *name;@property (nonatomic, strong, readonly) NSURL * _Nonnull image;
// ...
@end
OPTIONALSA anotação _Nonnull é um hint para o compilador (e para programadores) que o valor pode estar ausente, mas esta ausência não é verificada formalmente em tempo de compilação.
@implementation EventTableViewCell
// ...
- (void) render:(Event *)event { // Possível bug [self.imageView setImageWithURL:event.image];}
// ...
@end
A primeira versão deste código não verificava se o campo event.image con4nha algum valor antes de ser usado. Neste caso, código não contemplou o requisito de negócio de mostrar um placeholder para eventos sem imagem.
OPTIONALSstruct Event { // ...
let title: String let image: URL?
// ...}
class EventTableViewCell: UITableViewCell { // ...
func render(event: Event) { // Erro de compilação! imageView.setImage(url: event.image) }
// ...}
TIPOS ALGÉBRICOS
TIPOS ALGÉBRICOS/* Structs são product types
|DateRange| = |DateRangeIdentifier| * |Date| * |Calendar| = 6 * |Date| * |Calendar|*/struct DateRange { private let identifier: DateRangeIdentifier private let base: Date private let calendar: Calendar}
// |DateRangeIdentifier| = 6enum DateRangeIdentifier { case today case tomorrow case thisWeek case thisWeekend case nextWeek case thisMonth}
TIPOS ALGÉBRICOS/* Enums são sum types
|FormFieldStatus| = 1 + |String?| + 1 = 1 + (1 + |String|) + 1 = 3 + |String|*/enum FormFieldStatus { case idle case invalid(message: String?) case valid}
// Lembrando que:typealias String? = Optional<String>
enum Optional<T> { case none case some(T)}
TIPOS ALGÉBRICOS// |SearchResultsState| = |[Event]| * |Bool| * |Error?| = |[Event]| * 2 * (1 + |Error|)struct SearchResultsState { let data: [Event] let isLoading: Bool let error: Error?}
TIPOS ALGÉBRICOSfunc render(state: SearchResultsState) { // ?}
• Sempre que o array de eventos for vazio, deve-se mostrar uma mensagem?
• Se o array não está vazio mas dados sendo carregados, o indicador de a<vidade tem precedência sobre a mensagem?
• A mensagem de erro deve ser exibida sempre que o carregamento falha ou somente se o array de eventos está vazio?
TIPOS ALGÉBRICOS// |SearchResultsState| = 1 + |[Event]| + |Error|// = 1 + |[]| + |[x:xs]| + |Error|enum SearchResultsState { case loading case loaded(data: [Event]) case failed(error: Error)}
func render(state: SearchResultsState) { switch state { case .loading: renderLoading() case .loaded(let data) where data.isEmpty: renderEmpty() case .loaded(let data): renderLoaded(events: data) case .failed(let error): renderFailed(error: error) }}
PHANTOM TYPES
PHANTOM TYPESstruct Tagged<Tag, RawValue> { let rawValue: RawValue
init (_ rawValue: RawValue) { self.rawValue = rawValue }}
struct Order { typealias Id = Tagged<Order, Int>
let identifier: Id let purchaseDate: Date let tickets: [Ticket] // ...}
PHANTOM TYPESstruct GetEditableParticipants: HTTPResource { private let orderIdentifier: Order.Id
init (orderIdentifier: Order.Id) { self.orderIdentifier = orderIdentifier }
var method: HTTPMethod { return .GET }
var parameters: [String: Any] { return [ "order": orderIdentifier.rawValue ] }
// ...}
PHANTOM TYPESvar editableParticipantsRequest: GetEditableParticipantslet order: Order = /* ... */let ticket: Ticket = /* ... */
/* error: cannot convert value of type 'Ticket.Id' (aka 'Tagged<Ticket, Int>') to expected argument type 'Order.Id' (aka 'Tagged<Order, Int>') */editableParticipants = GetEditableParticipants( orderIdentifier: ticket.identifier)
// OKeditableParticipants = GetEditableParticipants( orderIdentifier: order.identifier)
ALÉM DA APPLE
CLI#!/usr/bin/swift
import Foundationimport Socket
do { guard let serverSocket = try Socket.create(family: .inet6) else { fatalError("Impossible to create server socket") }
defer { serverSocket.close() }
guard let portArgument = CommandLine.arguments[1], let port = Int(portArgument) else { fatalError("\(portArgument) is not a valid port") }
try serverSocket.listen(port: port) print("Listening to port \(port)...")
let clientSocket = try serverSocket.acceptClientConnection() print("Accepted connection from: \(clientSocket.remoteHostname)")
defer { clientSocket.close() }
try clientSocket.write(from: "Hello, world!")} catch let error { print("Caught unexpected error: \(error.debugDescription)")}
PACOTESimport PostgreSQL
let database = PostgreSQL.Database( hostname: "localhost", database: "test", user: "root", password: "")
let results = try database.execute("SELECT * FROM users WHERE age >= $1", [.int(21)])
for result in results { print(result["first_name"]?.string) print(result["age"]?.int)}
APLICAÇÕES BACKEND// IBM Kitura
let router = Router()
router.get("/api/todos/:id") { request, response, next in guard let userId = request.parameters["id"] else { response.status(.badRequest) return }
todosRepository.get(userId: userId) { result in switch result { case .success(let todos): try response.status(.ok).send(json: JSON(todos.toDictionary())).end() case .error(let error): try response.status(.badRequest).end() Log.error(error.debugDescription) } }}
Kitura.addHTTPServer(onPort: 8080, with: router)Kitura.run()
CONCLUSÃO
PERGUNTAS
OBRIGADO
Fellipe Caetano
! @fellipecaetano_"
@fellipecaetano
✉
fellipe.caetano@sympla.com.br
Recommended