Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swift: um equilíbrio sofisticado entre segurança, ergonomia, produtividade e preservação de legado

Swift: um equilíbrio sofisticado entre segurança, ergonomia, produtividade e preservação de legado

O ano de 2014 trouxe inovação tecnológica sem precedentes para a Apple: nasceu Swift, uma linguagem de programação trazendo sintaxe moderna e concisa, um type system mais poderoso, suporte first class a técnicas de programação funcional e interoperabilidade total com bases de código legadas escritas em Objective-C. De 2014 a 2018, Swift passou de uma promessa para uma das linguagens de programação mais populares do mundo, graças em particular ao seu processo de evolução totalmente open source.

Nesta palestra, veremos com mais profundidade como Swift se diferencia em relação a Objective-C em três frentes: segurança, produtividade e facilidade de adoção.

Veremos como seu type system permite que certas classes de bugs e fontes de crash não ocorram em tempo de execução; como os playgrounds (uma feature exclusiva do ambiente de desenvolvimento Swift) permitem que UIs sejam prototipadas e pré-validadas em tempo real; e como a sintaxe da linguagem leva naturalmente a código mais conciso, legível e, mais importante, amigável, facilitando a adoção por novos desenvolvedores.

Por fim, faremos uma reflexão sobre o status do ecossistema Swift sob o prisma do desenvolvimento de aplicações enterprise.

Fellipe Caetano

May 11, 2018
Tweet

More Decks by Fellipe Caetano

Other Decks in Programming

Transcript

  1. ENTIDADES struct 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 } }
  2. MODELOS DE ESTADO enum 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 } } }
  3. INTERAÇÕES DE TELA enum 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 { // ... } }
  4. TRANSIÇÕES DE ESTADO let 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 } }
  5. FLUXO DE DADO UNDIRECIONAL struct 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 // ... }
  6. TELAS DERIVADAS DO ESTADO class OrderListViewController: UIViewController { // ...

    override func viewDidLoad() { super.viewDidLoad() let disposable = applicationStore.subscribe { applicationState in self.orderListView.render(state: applicationState.orderListState) } disposeBag.add(disposable) } // ... }
  7. 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
  8. INTEROPERABILIDADE class 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()) // ... } }
  9. INTEROPERABILIDADE // Como é em C struct CGRect { CGPoint

    origin; CGSize size; }; typedef struct CGRect CGRect;
  10. INTEROPERABILIDADE // Como é importado em Swift (automaticamente) struct CGRect

    { var origin: CGPoint var size: CGSize init() init(origin: CGPoint, size: CGSize) }
  11. INTEROPERABILIDADE // Como é em C CGRect 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:_));
  12. INTEROPERABILIDADE // Como é em C CGRect 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 Swift extension CGRect { init (origin: CGPoint, size: CGSize) func contains(_ r2: CGRect) -> Bool func intersection(_ r2: CGRect) -> CGRect }
  13. OPTIONALS O #po Op#onal<T> é a representação em Swi7 da

    possibilidade de ausência de valor. var optionalInt: Int? // Optional<Int>
  14. 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 // .none optionalInt = 1 // .some(1)
  15. 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)
  16. OPTIONALS Um 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 segura if let safelyUnwrappedInt = optionalInt { print(safelyUnwrappedInt + nonOptionalInt) } // Extração insegura e perigosa let explictlyUnwrappedInt = optionalInt! print(explictlyUnwrappedInt + nonOptionalInt)
  17. OPTIONALS O #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
  18. OPTIONALS A 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.
  19. OPTIONALS struct Event { // ... let title: String let

    image: URL? // ... } class EventTableViewCell: UITableViewCell { // ... func render(event: Event) { // Erro de compilação! imageView.setImage(url: event.image) } // ... }
  20. 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| = 6 enum DateRangeIdentifier { case today case tomorrow case thisWeek case thisWeekend case nextWeek case thisMonth }
  21. 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) }
  22. TIPOS ALGÉBRICOS // |SearchResultsState| = |[Event]| * |Bool| * |Error?|

    = |[Event]| * 2 * (1 + |Error|) struct SearchResultsState { let data: [Event] let isLoading: Bool let error: Error? }
  23. TIPOS ALGÉBRICOS func 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?
  24. 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) } }
  25. PHANTOM TYPES struct 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] // ... }
  26. PHANTOM TYPES struct 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 ] } // ... }
  27. PHANTOM TYPES var editableParticipantsRequest: GetEditableParticipants let 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 ) // OK editableParticipants = GetEditableParticipants( orderIdentifier: order.identifier )
  28. CLI #!/usr/bin/swift import Foundation import 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)") }
  29. PACOTES import 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) }
  30. 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()