When the Fabric team at Twitter wrote their new iOS app, they turned to Swift. Why? Javi Soto walks through their decision, and tells why they picked a language that emphasizes stability and maintainability & supports dependency injection, code generation, MVVM, and error reporting.
Introduction (00:00)
I love what Realm does for the Swift community by hosting these meetups. I am humble that I get to speak here. [Ed. note: Thanks Javi! We are honored to host such illustrious speakers! 😊]
I am Javi. I am originally from Madrid, Spain, but I have been working in the Bay Area for around four years. Previously I worked at Pebble, the Kickstarter-funded smart watch company, on the iOS app. I started writing Swift when it first came out and last year my brother (Nacho, sitting in the audience) and I released our first app fully written in Swift. It was Swift 1.1 at the time, and it was an Apple Watch app to follow Chad’s broadcasts. Now I work in Twitter in San Francisco on the Fabric team.
I ❤️ talking about Swift. Building the (recently launched!) Fabric.app was fun. I simply wanted to share some of the things that I did differently when building this app (compared to all the apps that I built in the past).
What is Fabric.app? (02:01)
Fabric.app is a set of tools for mobile developers. The two main ones are Crashlytics and Answers. We set out to build a mobile app because we realized that sometimes important issues do not happen when you are at your desk, and many of us like to be able to keep an eye on how our app is doing no matter where we are.
This is what the Fabric dashboard looks like on the web. (See the slide above. 👆) We show you (a ton of) realtime and historic information about the usage and stability of your app. But we did not want to just replicate what you can see here in the mobile dashboard: we wanted the app to shine on mobile. And one of the great things that we can do on mobile is notifications. Fabric.app is able to alert you if there is something wrong about your app (e.g., the overall crashfree users are dropping very quickly). We also put attention into how noisy notifications can be: we offer fine-grained controls on how many notifications you want to receive, and you can even mute notifications for a couple hours or a couple of days (nobody wants to receive notifications about crashes when you are on vacation!).
Tools (03:35)
Dependency management (03:39)
There is much discussion recently in the iOS community about how many third-party libraries you should use in your app, or whether you should use any at all. In a big team, with the resources to solve every problem needed to build an app, maybe you want to minimize the number of external dependencies. But the Fabric app had a total of one engineer (for most of its development time), and it also started as a proof of concept. At the beginning we wanted to prove that Fabric.app could offer value, and we wanted to prove that quickly. We did not want to implement a networking library to prove that. The initial goal was to avoid reinventing the wheel. These are some of the problems that, regardless of the complexity in solving them, I did not want to spend additional time solving. Especially because many people have already done a fantastic job in solving them. The iOS open source community is fantastic. There is great code out there.
The Fabric.app uses both CocoaPods and Carthage. Using more than one dependency management system is not recommended. They will not be able to see each other’s dependency graph, and you may end up with both resolving the same dependency (and that added to your app twice). However (if you know what you are doing) both of them have advantages. I like both of them for different reasons.
CocoaPods (05:27)
CocoaPods is widely adopted. The libraries are easy to integrate because it modifies the Xcode project for you. But more importantly, it is easy to add a dependency only on debug builds. We used it to add some tools during development, but we did not want to ship it in the final binary. You could think: CocoaPods does everything and mostly every library supports CocoaPods, we could have stopped there. Why did we decide to add Carthage on top of that? The reason is: compilation speed. The Swift compiler is not as fast as the Objective-C compiler. And while it may get better, I believe that this may always hold true. Pre-compiling code that you are not constantly changing may always be a good idea. Pre-compilation of some of the libraries speeds up our total app compilation time.
Unfortunately there have been Xcode- and LLDB-related issues to using pre-compiled libraries that were compiled outside of your Xcode project for over a year. There are some trade-offs. In any case, in the near future I cannot wait until I can move over to the Swift Package Manager. I cannot wait to see how the Xcode integration turns out.
Interface Builder (07:01)
I am a big fan of Interface Builder. There is little layout code in the Fabric.app. Only some views must dynamically create some constraints, and we use the Cartography library for that because it simplifies the syntax.
Auto Layout (07:22)
Being able to set up Auto Layout constraints in Interface Builder reduced the amount of UI code. Despite all the complexities, one of the biggest wins of Auto Layout is self-sizing table view cells, which finally work well in iOS 9. In the pre–Auto Layout world, apps with multiple table views or multiple types of cells with dynamic heights require a non-trivial amount of manual and sizing layout code. And this is horrible for quick iteration, when you are just trying to add new views and move things around.
That is not to say that Auto Layout is perfect or easy to use (e.g., see the screenshot: if you plug in your phone to your Mac, look at the logs that Springboard is outputting). Not even Apple can get this right. It is really hard. Bit I believe it is worth it, for the most part.
Storyboards (08:28)
The first release of the Fabric.app, version 1.0, shipped with most of its UI in one storyboard. I started building the app with them because, honestly, I had not done it before and I wanted to give them a try. When my co-worker Josh joined the team, we quickly realized that it was not going to work well with more than one committer. In version 1.1 we moved everything over to separate XIB files.
UIStoryboard
(09:10)
Besides the well-known problems of putting all of your UI in the same file, I would stay away from storyboards.
This is the only API available to instantiate a view controller from a storyboard. Even if you are using segues, you are also using this API. This is what it looks like in a hypothetical view controller that you would instantiate:
class UIStoryboard {
func instantiateViewControllerWithIdentifier(_ identifier: String) -> UIViewController
}
We instantiate it and then we pass values to it. We set up a bunch of properties on it.
What does this look like on the view controller itself? You are going to end up with implicitly unwrapped optionals (and I am sure you do not like implicitly unwrapped optionals) - this means you cannot do proper dependency injection. For example, in this method we have no guarantee that other required parameters (e.g., this user session one) will be set at runtime.
let vc = mainStoryboard.instantiateViewControllerWithIdentifier("ApplicationOverviewID") as! ApplicationOverviewVC
vc.userSession = ...
vc.applicationID = ...
At that point, when this view controller is on the screen, we can either crash if we read that implicitly unwrapped optional and it’s nil, or we can leave the app broken if we check for nil and then do nothing (because there is not anything useful that we can do in many cases).
final class ApplicationOverviewVC {
// I hope you like "!"s...
var userSession: UserSession!
var applicationID: String!
func viewDidLoad() {
super.viewDidLoad()
self.userSession.foo()
}
}
I did write a hack in Objective-C that shipped in this 1.0 version, which worked around this issue. Interestingly, I had to do it in Objective-C because Swift would not let you do that (that tells you how much of a hack it was). But it also involved implicitly unwrapped optionals, so it was not safe either. If you are curious about it I can share the gist.
This is what I think view controllers ideally look like. The API in this view controller makes explicit to its user which parameters are required. And not providing a required parameter results in a compile-time error. Another benefit is that members can now be let
constants - it also simplifies implementation because we do not have to worry about the values changing after initialization.
final class ApplicationOverviewVC {
let userSession: UserSession
let applicationID: String
init(userSession: UserSession, applicationID: String) {
self.userSession = userSession
self.applicationID = applicationID
super.init(nibName: ..., bundle: ...)
}
func viewDidLoad() {
super.viewDidLoad()
// Compile-time guarantee of object fully initialized
self.userSession.foo()
}
}
No storyboards == better code.
fastlane (11:19)
I love fastlane. 💖 We use it in the app and we automate many tasks: automatically bumping the version build numbers, creating tags, building for beta app store, taking the screenshots, uploading the screenshots. I heavily recommend it, it saves us time during development and, even after we shipped the first version, continues to save us time.
The fastfile, which is where the configuration goes for the different tasks that we automate, is open source in this fastlane example we built.
ReactiveCocoa (06:46)
Fear not, I am not going to cover ReactiveCocoa. I encourage you to watch the talks that I have given previously at Swift Summit if you are interested in that topic. I wanted to mention that it plays a big role in the architecture of the Fabric.app.
I want to show one tiny example of how, with ReactiveCocoa, we are able to reuse and compose logic, in an easy way. In this example, we have created extension methods for ReactiveCocoa and this code sets up an asynchronous task which mutes all notifications in your account. By calling this method that we implemented on that task, it allows us to make sure that this request continues, even if the app is backgrounded.
self.accountService.muteAllNotifications(untilTime: date)
The code to implement this is written once and can be reused in any asynchronous task around the app. That is one simple call out to ReactiveCocoa.
self.accountService.muteAllNotifications(untilTime: date)
.continueWhenApplicationIsBackgrounded(taskName: "Muting notifications")
Architecture (13:25)
It is hard to describe in slides what the overall architecture of an app looks like, but I am going to mention a couple of things. Based on my past experience, I decided to take a pragmatic approach with the Xcode setup. Ideally I would like to split the code base into small isolated frameworks. But this introduces a huge amount of complexity in your Xcode setup. For that reason we separated most of the business logic into one single Fabric API.framework
. The idea behind this is to make a framework that does not depend on UIKit. Fabric API.framework
is, for the most part, all the networking and data models that the app needs to talk to the Fabric backend. It could be reused, perhaps, in a different architecture or platform.
Multiple screens in the Fabric.app are UITableView
s, and I wanted to cover how I handle that to reduce the amount of boilerplate. There is nothing hugely novel, the main purpose was to avoid massive view controllers. The pattern that I used could be called MVVM. Although I do not strictly follow it, I have taken inspiration from sources I read online. We put all the interesting logic that powers each screen in the view model. This keeps the view controller thin. Also, this view model makes the necessary network requests to pull the data needed for our particular view, and combines all of them into one struct. The API that it exposes is an observable view into that struct, that changes over time as the requests finish and then can be observed by the view controller.
GenericTableViewDataSource
(15:43)
But I was doing the same thing over and over: I was observing that struct and updating the contents of that table view. I came up with this abstraction. Many of the table views update asynchronously, as those requests finish in the background, and they also show different types of data in the same screen.
protocol TableSectionType {
associatedtype AssociatedTableRowType: TableRowType
var rows: [AssociatedTableRowType] { get }
var title: String? { get }
}
protocol TableRowType { }
This is some of the code that I wrote to generalize this. First, I started with two protocols to represent types that are sections, and types that are rows. A section is something that can return an array of rows and an optional title.
I created this data source that I can instantiate with the few things required to power one table view.
final class GenericTableViewDataSource<Elements,
SectionType: TableSectionType,
RowType: TableRowType
where SectionType.AssociatedTableRowType == RowType,
SectionType: Equatable,
RowType: Equatable>: NSObject, UITableViewDataSource
In the implementation of this GenericTableViewDataSource
, I can implement once all the logic to diff the changes from two snapshots of the data in the view, and update a table view with animations, only updating the rows and sections that changed. And this is what it looks like when you implement one of those table views.
enum ProjectIssueRow: TableRowType, Equatable {
case Loading
case NoIssues
case ErrorLoadingIssues
case ProjectIssue(Issue)
}
return GenericTableViewDataSource(
tableView: tableView,
tableViewData: observableProperty, // Observable<Elements>
computeSections: { elements in ... }, /// Pure function from `Elements` to `[SectionType]`
configureRow: { row, indexPath in ... } /// Function from `RowType` to `UITableViewCell`
)
You can implement your own type for a row, and we have a few different cases. For example, we can have a loading row; this is for a new feature that we just shipped in version 1.2 where we show you all the issues in your application. You need to pass the table view, the table view data, which is the observable property, using ReactiveCocoa. And then two important closures. The first one is a pure function that sets up the sections and rows that must be shown given a current state of the data. If the requests are still loading, it can set up a row with a spinner, if a request is failed it can show the appropriate error row. Lastly another closure that, given a row, returns a cell. I will show below how we easily dequeue table view cells in a type-safe way. If you want to see more of this code, I will be happy to share some of it online.
Better with Swift (18:08)
I want to mention three things that I love about Swift that were a huge help in building the first big app that I worked on in Swift after years of building apps in Objective-C: nullability, type-safe JSON parsing, and code generation.
Nullability (18:26)
Ayaka (who is sitting in the audience), wrote this great Tweet (If you do not follow her I recommend it: she has great Swift content and has given great talks):
Optionals are great because we can make things non-optional.
It is not simply a philosophical idea. Thanks to this idea, in Fabric.app we can prove things at compile time. And this eliminates the need to add verbose and redundant tests (and allows me to sleep better at night).
I want to show two examples. The first one has to do with user authentication. I want to show some abbreviated and simplified code that powers this screen. It is the list of apps in your account. Let’s look at this view controller.
final class ApplicationListViewController: BaseFabricTableViewController
It is the first screen that appears after you log in, and it displays the list of apps in your account. For this we need to access some object that encapsulates this user session. This is an oversimplified version of what that could look like. In my experience, this is a very common approach to solving this, using a global accessor. We have user session class and we could have a static method or var, it is a mutable singleton. But the problem (and Swift makes you realize that very quickly) is that property UserSession.currentUserSession
must return an optional because, before the user logs in, there is no user session.
final class ApplicationListViewController: BaseFabricTableViewController {
override viewDidLoad() {
super.viewDidLoad()
let session = UserSession.currentUserSession
if let session = session {
session.requestApplications()...
}
// or...
session!.requestApplications()...
}
When we get to this viewDidLoad() method and we get the user session, what do we do? Do we conditionally unwrap this user session, or do we use an exclamation mark? None of the two options are great. One would lead to the screen being broken, because in the else
case we cannot really do anything useful. This screen was not meant to be shown if you are in a locked in. It would essentially leave the app broken. And in the other one, it leads to a crash. That is obviously not great. And you could think, in the code outside of view controller we know that this is not presented before you log in, we are safe. But, are we safe with this? There could be a risk condition in the code that sets that currentUserSession
property and then presents the view controller. This could definitely go wrong.
final class ApplicationListViewController: BaseFabricTableViewController {
init(viewModel: ApplicationListViewModel)
}
final class ApplicationListViewModel {
init(fabricAPI: AuthenticatedFabricAPI)
}
public final class AuthenticatedFabricAPI {
public init(authResponse: AuthResponse)
}
public final class AuthResponse {
let accessToken: String
}
This is how I strongly recommend to approach this problem. In this code the view controller needs a view model, which will be in charge of making the network requests. The view model has to be instantiated with a not null (and that is important) AuthenticatedFabricAPI
. AuthenticatedFabricAPI
itself can be instantiated without an AuthResponse
struct, which itself needs an accessToken
, that is also not null. This means that we can prove, at compile time, that this view controller (which shows user data) can not be presented in this screen without first acquiring a user session and an access token to make authenticated requests. And that is great.
Let me quote Ayaka again:
Optionals are great because we can make things non-optional.
In the code above, nothing is optional. And this gives us a guarantee that things that are required will be present at run time.
Let’s look at that in a slightly different example. This is a recent development (something that I improved after I kept making the same mistakes over and over). Let’s look at the same view model again:
final class ApplicationListViewModel {
var applications: [Application]?
}
It exposes this applications
property that changes over time, and it is an array of application values to indicate that we may be in a state where the request has not yet finished. It is an optional array. If it is nil, it means that we have not finished the request yet. How can the view controller tell the request finished but failed? Will there be an empty array or will it be nil too? I am sure most of you think that nil is a better answer to that question but, for some reason in the past I had a different opinion and then I was confused about it. I think the underlying problem is the lack of explicitness; we are trying to represent many possible cases just using one optional. And that can be error prone. This is what I came up with:
enum DataLoadState<T> {
case Loading
case Failed
case Loaded(T)
}
final class ApplicationListViewModel {
var applications: DataLoadState<[Application]> = .Loading
}
It is not rocket science. 🚀 I took advantage of Swift enums, which are fantastic to represent values that can take only one of a finite set of forms. In this case we can be loading, have failed to load, or loaded data. Optional only has two cases and we wanted three; the UI can show the user whether we are loading, we have failed and we want to show an error, or that we have loaded successfully but we have an empty array of applications (we can tell that case very explicitly). When we read the applications variable, we can thus know if it is loading. And I am not saying that this is a great API and you should adopt it, but it simplified some of the code that I was writing that had to deal with this, making it clearer, safer, and harder to make a mistake.
Type-Safe JSON Parsing (24:47)
Do not worry: I am not introducing a new JSON parsing library (there are enough already!). Let’s look at a couple of JSON parsing anti-patterns.
public struct Application {
public var ID: String?
public var name: String?
public var bundleIdentifier: String?
public mutating func decode(j: [String: AnyObject]) {
self.ID = j["id"] as? String
self.name = j["name"] as? String
self.bundleIdentifier = j["identifier"] as? String
}
}
This is a very simple way of doing JSON Parsing. We can make all the members of our struct optional and when we parse we just set the ones that are present in the JSON dictionary and with the right type. These can’t crash, since we are checking the types, but we end up with a struct where some things may be nil. And this is less than ideal because, whenever we are implementing any part of the app that interacts with this struct, we need to constantly unwrap values. That means handle the case where some of them are nil, and it is obviously not clear what the best answer is in that case. It is also cumbersome. Remember, optionals are great because we can make things non-optional. Having to handle nil everywhere is not great.
This is an improvement, another very common way of doing JSON parsing. We change to not having optionals instead of a struct, and we have a static function that returns an optional application. If parsing any of the fields failed, we return nil. And this is already better than what we had before. We do not have to deal with nil members.
public struct Application {
public let ID: String
public let name: String
public let bundleIdentifier: String
public static func decode(j: [String: AnyObject]) -> Application? {
guard let ID = j["id"] as? String,
let name = j["name"] as? String,
let bundleIdentifier = j["identifier"] as? String else { return nil }
return Application(
ID: ID,
name: name,
bundleIdentifier: bundleIdentifier
)
}
}
But here is what is wrong with this code: if we fail to parse some of the fields, we have no way of knowing exactly what went wrong (e.g was a key missing, was the wrong type?). In the Fabric app we use the great Decodable library.
Code Generation (27:28)
Here is a snippet of code from the Fabric app:
import Decodable /// https://github.com/Anviking/Decodable
public struct Application: Decodable {
public let ID: String
public let name: String
public let bundleIdentifier: String
public static func decode(j: AnyObject) throws -> Application {
return try Application(
ID: j => "id",
name: j => "name",
bundleIdentifier: j => "identifier"
)
}
}
There are MANY great JSON libraries, and I do not particularly recommend this one. But I do advocate for using a library that allows you to create your model structs from JSON in a typesafe way. Detailed errors when we fail parsing is where Decoable excels. During development this saved us time that we did not need to spend debugging. And we are able to get that for free without sacrificing the complexity of the code that we needed to implement.
I want to show another cool tool that we used in the Fabric app to increase our confidence that every time we build, things are going to work. When Swift came out, there was a huge emphasis in safety. However, there were a couple of things that were unsafe. I refer to them as string typing.
/// Swift < 2.2
UIBarButtonItem(barButtonSystemItem: .Done, target: self, action: Selector("buttonTapped"))
// Swift 2.2
UIBarButtonItem(barButtonSystemItem: .Done, target: self, action: #selector(ViewController.buttonTapped))
One of them was creating selectors. In Objective-C we (at least) had the @selector
syntax, but in Swift it became worse than it used to be in Objective-C. Thankfully this was fixed in Swift 2.2, and now we have the new #selector
syntax, which verifies at compile time that we are creating a valid selector that exists in that class.
This is another example of string type API:
super.init(nibName: "ViewControllerNibName", bundle: nil)
let nib = UINib(nibName: "NibName", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "ReuseIdentifier")
let cell = tableView.dequeueReusableCellWithIdentifier("ReuseIdentifier", forIndexPath: indexPath) as! MyTableViewCell
let image = UIImage(named: "ImageName")!
Instantiating view controllers, getting nibs, registering nibs with cell reuse identifier, getting images from the bundle: this is incredibly fragile. As the number of types of cells in your application increases, especially if you want refactor any part of your app, the chances that something will break are high.
R.swift (29:56)
In the Fabric app we use this library called R.swift. This is a snippet from our open source fastfile (see slides).
Whenever we add a nib to our application or make any other change, we call in our command line fastlane code_gen
, which calls into R.swift and this automatically generates an R.generated.swift
file that contains:
- references to nibs
- reuse identifiers from cells
- images in the asset catalogs, and other file names
What does this enable? What does this look like when we use it?
super.init(nibResource: R.nib.myViewController)
class MyTableViewCell: UITableViewCell, ReusableNibTableViewCell {
/// This will even fail to compile if it's not the right cell
static let nibResource = R.nib.myTableViewCell
}
tableView.registerReusableNibCell(MyTableViewCell)
let cell = MyTableViewCell.dequeueFromTableView(tableView, indexPath)
let image = R.image.imageName
These are some example of lines of code that use that, some using helper APIs that I wrote to simplify that code. But it is all powered by having those strings auto-generated and assigned to constants that we can reference statically. Here we get compile-time verification for the names of the nibs, reuse identifiers, image names, etc. And this is fantastic, I would not be able to go back to hardcoded identifier strings anymore. One cool thing with cells: I got it to even verify that the nib resource that I was returning matched the type of the cell.
Error Reporting (30:42)
Often when we write code we call APIs that can fail and report an error. We looked at an example with JSON parsing, but this is another one:
do {
try fileManager.createDirectoryAtPath(path, withIntermediateDirectories: true, attributes: nil)
/// ...
}
catch {
print("Error: \(error)")
}
Say we are calling into this Foundation API that can throw an error. Often when we call into this API and get an error, we write this code that maybe will show an alert to the user. But often we just print it to the console and move on because we do not have a better thing to do. This is basically what I have done for years. But you always wonder, is this happening in production? And in this code it is not clear if we have something that we can do to prevent this error or work around if it happens, but it could be caused by some other code in your application. It could break in some version of the app, and we would have no way of finding out. Imagine that your app was crashing in production and you could not find out because you did not have a crash reporter. This is the state for all other types of errors that are non-fatal, they will not make your app crash but it could potentially be leading to a less-than-optimal experience for the user. This is why I was really happy when Crashlytics released the logged errors API:
do {
try fileManager.createDirectoryAtPath(path, withIntermediateDirectories: true, attributes: nil)
/// ...
}
catch {
Crashlytics.sharedInstance().recordError(error, withAdditionalUserInfo: userInfo)
}
Just one line of code (very similar to print
), trivial to implement, and I feel better knowing that if this error happens or any other error (in the Fabric app we log absolutely every error), I will be able to quantify it and know about it. In the Fabric app, we are also able to send push notifications if this happens… which is meta, if the notifications you are receiving is about an error in the Fabric application.
This is a real example of what that would look like in the Fabric app (see slides). For a JSON error, we can see that some JSON was a key (called stability
) that was missing from some JSON. I really encourage you to try this out. If you want to ask any questions about any part of the presentation or the application, we are pretty open about it.
If you are curious about how we build something please feel free to reach out to me on Twitter or send an email (javi@twitter.com, fabric-app-iOS@twitter.com).
Thank you!
Questions? (33:16)
Q: I was curious about your approach with view models. I am also a fan of view models, but I do not know what is the preferred way of implementing them. One thing that I like is that when I use a view model, I prefer to use pure simple object in the language, and I noticed that you used a very complex object such as an API. What is your consideration about that, because it makes a bit harder to test, because maybe you need now to mock an object to make a test?
Javi: I will be honest, we are not testing any of the view models on the application, we are testing some of the code that the view models themselves go into. But it is true, we are passing that AuthenticatedFrabricAPI
object into the view model. If you wanted to test that, you would have to make that a protocol, you can create a fake version of it, it is definitely a challenge, especially in Swift where maybe you cannot subclass that. I do not have a great answer, but I think if you keep your objects dumb and the API surface clear, it is easier to pass something that looks like the real object and does not send any requests. Another thing that we do when we want to mock a network request, they are not unit tests, but we do mock the network request using OHHTTPStubs. We let it go through all the networking code, and capture the request before it hits the network. That is another approach.
Q: Great talk, my question is how do you handle preconditions in the app? I think this might not be very common, but sometimes you have things that you may want to assert, but if the app goes to a wrong state, it might crash in production. How do you handle that conflict?
Javi: It is challenging, I am personally of the opinion to leave certain asserts and nils in production, and I used to ship with more assertions, but in Swift what I think ended up happening is there are more things I can verify at compile time. For example one of the things I would assert always in Objective-C every method that I wrote would have NSParameterAssert
to verify some objects that are required are not nil. In Swift I do not have to do that. And another thing was if there is an error or something that it is a condition that should not happen, I used to crash for that. Now I have the option of reporting that error, and at least learning about it, and not making the app crash. But if it is going to leave the app completely broken, I sometimes prefer to crash. I cannot come up with an example of that in the Fabric app, and then we have a couple of assertions that are only enabled in debug that verify that some method is called on the main thread, you know sometimes threading can get complicated, and you can accidentally call something on a background thread and you know that that hits the UI. There are a couple of those. Yes, there are not a ton of assertions, I think I was lucky that I was able to do things at compile time, for example with code generation, I do not have to assert if casting a UITableViewCell
returns me the wrong class, that always works.
Q: Great talk (like everyone said). I noticed in your view model you said you had a mutable changing property, but you also said you used ReactiveCocoa. I was wondering, if you could speak to why you did not make that an observable as well.
Javi: Let me go to the slide that I think you are referring to, this is fake code. I did not want to use ReactiveCocoa syntax, but using var
makes it clear that that is going to change over time, the real code would be let
, and any property of any application. Internally there is a mutable property, but that one is private, only we control our rights to it, and any property is an immutable view into the mutable property.
Q: I just wanted to ask about the graphs, what are you using to do the graphs?
Javi: I did not mention them at all. We used this library called JBChartView.
Q: I have a couple of questions about the non-fatal crashes, I missed the announcement of this feature, is that being reported even if your particular app does not crash?
Javi: Yes. It looks like a crash - it will keep track of stack trace, but they are sent as a different type of an event even if it does not crash. You can send as many as you want as the app is running.
Q: Cool. Please publish the Objective-C hack for Storyboards, I am curious because I had a couple of things on my own to write.
Javi: I will tweet about it.
Q: …and table view, I am not sure are you supporting iPads, or are you planning to? Because adaptivity and out of sizing and it just does not yet work for us for some reason. Javi: I am not super excited about it either. The app is iPhone only, we figured that most people would be using an iPhone and it is definitely a lot of complexity to support both. We decided to ship faster. I cannot speak about the plans to support or not iPad, but it will. I do not know how well the current setup with nibs will help, because it also depends on how different you want to make your iPad be. If you just want to make it bigger, I guess you know I can make that compile in five minutes. I do not know how well it will look.
Q: Have you tried it with collection view cells because that part does not work for us pretty much. Javi: Yes, I guess I have been using table views for so long that I am biased towards that. I have been running into some issues with table views, and I have been wondering maybe I should try with collection views, and I can work around those issues. Maybe one day I will trade up.
Q: Cool and the last question was, you mentioned that by passing the API clients to view controllers, you can make sure that is authenticated, how do you get it secure of the fact let’s say token expired while you have several controllers in the stack pushed on top of each other, and then how do you propagate this information back through? Javi: That is a really good question, it is tricky to do, and I will tell you how we did it, I do not know if it is the best way, or if there is a better way, I would have to learn about it. It was challenging to do. One thing is that that API client is shared around the app, which is maybe less than ideal. And it handles refreshing the token itself. And it does it in its own serial queues. If there are requests coming in, they will wait until the refresh happens. If your controllers do not have to worry about that, as long as they have an authenticated Fabric API object, that has a token, even if it is invalid at the time that you created it, it will be refreshed.
Q: We found that creates another issue, that is basically we tried to refresh it, let’s say once or twice, but then if credentials, if we banned an account, it will not succeed at all? Do you cover that case? Javi: No, but my idea, when we probably fix that, was to detect that case like if we were getting a bunch of 401
s in a row, pipe that somewhere that will log you out. And what we are doing when you click on logout, I took a very simple approach where I tear everything down. I changed the root view controller of the window, which de-allocates the view controller that is currently there and I allocate the login screen, and that is what is on screen we could do that.
Receive news and updates from Realm straight to your inbox