Similar concepts JavaScript and React Native share with Swift: Part 1

Written by posttweetism | Published 2018/11/15
Tech Story Tags: javascript | swift | react | react-native | ios-development

TLDRvia the TL;DR App

Most blog posts that I’ve read on React Native have been from a JavaScript developer’s perspective on mobile. Instead, this will be from an iOS developer’s perspective on React Native & JavaScript.

I hope that any other developers coming from iOS & Swift into React Native will find this useful. Likewise, any JS developers curious about Swift, may also find this reference useful. The aim is not to focus on which way is better or worse, but to highlight the similarities and differences, so that any other developers can facilitate their learning.

This also won’t cover every single similarity and difference. Originally I was intending to get close to that (yeah, right!), but I was going down the path of writing an encyclopedia and never publishing, rather than a blog post. Nor will the similarities be 100% alike or 100% non-existent, but mostly.

From JS, to iOS, then back to the future

In what felt like a previous lifetime, I was developing in primarily MAMP & AJAX for about 2 years after graduating CS studies. Then from about late 2010, I worked almost exclusively in native iOS (Objective-C & Swift), and a little bit of native Android. Right up until I decided to broaden my knowledge around all aspects of mobile development and joined Highline BETA in mid 2018.

At first, coming back into JavaScript was like Marty McFly returning to 1985 under the parallel world of Biff’s empire. It was a bit like…I vaguely recognize this world, I’m just not sure where I am. Not that it was bad (unlike Biff’s empire), it’s just that nothing made sense, despite it being the same world as I’d been in before.

So on day one, my fellow Highline BETA engineers suggested that before diving into React Native and FRP, that I should look into ES6 in JavaScript.

For any mobile devs unaware of ES6, here’s a quick breakdown without googling: basically the ECMAScript standard (which JS is based on) was rarely updated. This included a long gap of nothing and/or hesitation from 1999–2009, then another long gap from 2011–2015. It’s almost as if they were silently hoping that JS would just go away…when it was apparent it wasn’t, they finally figured out how to evolve the standard without introducing breaking changes across the WWW. They renamed the standard to ES6, and were free to set the precedent for ES7, ES8, & ES9 in consecutive years.

Perhaps a core concept to get used to in JavaScript, is not a core programming concept specifically, but a philosophical concept that anything goes. One of the values of React Native, is that it’s trying to standardize this — it’s trying to standardize a practise. I’ll try touching on ES6 concepts similar to Swift (using Axel Rauschmayer’s list as a guide, in no particular order), and then I’ll cover React Native in a follow-up blog post.

Similar concepts

1. Arrow functions

Basically like shorthand syntax, but for functions.

Declaring a function like:

function foo(param) {}

Is equivalent to declaring a function as a constant in ES6:

const arrowFoo = (param) => {}

From what I understand, arrow functions eventuated from the limitation in traditional JavaScript functions that prevented you from accessing the scope of objects via this, known as context, in the callback. Further complicating this is the execution context, which is applicable to scope not context, which prevents the original function that was executed binding to a different execution context.

It’s confusing, and I don’t want to get into any in-depth explanations because it will hijack this blog post when it’s already too long, but this link does a good job of explaining it.

Because Swift is a language on a platform that supports multi-threading, it didn’t have the same problem with execution contexts. But it still maintains thread safety through it’s careful use of the equivalent of this. For Swift’s version of this, self, we still have to use[weak self] in closures to guarantee it can be safely accessed. But this is more to do with safety and less to do with any inability to technically access instance methods & properties.

Additionally, because arrow functions in general return a function (or to better explain it, arrow functions are constants with a function as the value), they’re similar in declaring a constant as a closure in Swift:

let arrowFoo = (param: ParamType) -> ReturnType

2. Mutable & Constant Variables

JS used to have var, but now that is essentially deprecated in favor of let (reassignable) and const (single-assignable).

const setOnce = 101

let setWhenever = 202

There are scope differences between JS’s var and let, and while that’s not worth getting into within the context of Swift comparisons, it’s worth touching slightly. let variables by default are limited to the scope of whatever they’re declared in, be it a function, a for loop, etc.

Swift’s equivalent is the inverse of JS. let is used for constants, while the var keyword is used for reassignable variables:

let setOnce = 101

var setWhenever = 202

This is a minor difference, but one that can be a habit hard to break, similar to driving a car in left-hand drive to right-hand drive.

Regarding the limited scope of JS’s _let_, in Swift functions _var_ and _let_ are both limited by scope, or in a closure it would be the equivalent of not prepending ___block_ to your variable, or in an object declaring it as private.

3. Object Literals

In JavaScript you’ve never been truly able to create objects, at least in the sense that you can in a strongly typed OOP language where an object is created with a class, constructor, etc. Objects in JS don’t inherit from an object by default, although they can use prototypal inheritance to enable inheritance through a factory pattern such as create().

All object literals are, essentially are key-value structures. An object is defined inline with a colon separating the key from the value, such as defining a coffee mug:

const CoffeeMug = {	millileters: 300,	artworkPath: "./images/coffee-mug-logo.png",	coffeeType: "espresso",	onRefill: refill()}

As you’d expect in an object, you can define any type to your properties, including other objects, arrays, or functions.

In Swift, this is equivalent to a couple of things.

Firstly, defining object literals are syntactically and functionally equivalent to declaring a Dictionary. Further, an Enumeration or Struct in Swift could be used to provide greater flexibility that a dictionary, but also with the benefit of not needing explicit constructors, getters, or setters.

Secondly, implicitly setting an object without defining its type through type inference. This has been a feature of JS since the beginning, but in Swift any type that is inferred will maintain that type through its lifecycle. This is perfectly valid in Swift, if we were going to declare it as a dictionary:

let CoffeeMug = [	"millileters": "300",	"artworkPath": "./images/coffee-mug-logo.png",	"coffeeType": "espresso",	"onRefill": "refill"]

Where you’d have to include type annotation, is if you wanted to retain those values as being of any type. Here’s a modified version with the millileters value set as an int, coffeeType set as an enumerated type, and refill set as a closure:

let refill: () -> () = { /** refill code here… */ }enum type { case espresso, drip, pourover }

let CoffeeMug: [String: Any] = [	"millileters": 300,	"artworkPath": "./images/coffee-mug-logo.png",	"coffeeType": type.espresso,	"onRefill": refill]

4. Template Literals

String interpolation has recently become possible in JS, removing the redundant need to append string with + to variables.

Template literals are the syntax needed for JavaScript’s string interpolation, achieved by `${...}`. Eg, the following string in reference to a person’s BMI:

const bmi = weight / height

const bmiReading = "Your BMI is: `${bmi}`"

The equivalent in Swift is:

let bmi = weight / height

let bmiReading = "Your BMI is: \(bmi)"

5. Modules

This is a way that JS encourages modular reuse of code. I’m not sure what predated modules (the script tag kind of seems similar, but different somehow), but to me this seems like a requirement for any modern programming language. Or maybe I’ve been in the world of Swift for too long where this is taken for granted.

For instance, you could declare and define a constant in its own file, with the intention of being an external module.

const ButtonModule = () => { ... }

export default ButtonModule

The export keyword is also a way to specify what is private or public to the internal module, or other modules using it. Although this is the kind of encapsulation you get with structs or classes, keep in mind that JS is best served when it’s not moulded into a class-based language. This is why modules serve the job here, it suits JS!

Having said that though, Swift doesn’t quite have the exact equivalent. If we run with the assumption I made earlier, where modules are JavaScript’s way of “encouraging” modular reuse of code, Swift doesn’t quite have something that guides you the same way.

The closest comparison in Swift is frameworks, but this doesn’t quite do justice to what purpose JS modules serve. Although frameworks can be light, they’re usually complete libraries rather than a single abstraction. Frameworks also have to be explicitly imported, although that’s where the comparison ends.

The closest comparison to single abstraction though, would perhaps be protocols. Protocols define what the behaviour or look could entail, without storing state that a struct or a class would. Any protocol you create doesn’t have to be explicitly imported either (unless dealing with Objective-C, which takes its cues from C’s header file imports).

6. Default & Rest Operators

Default parameters do have an equivalent in Swift. They work the same way, in that any parameter with a default value, can be omitted from the caller.

function find(searchTerm, sort=true) {...}

//You are able to call this as...find(“toronto”)

The equivalent in Swift is the same thing:

func find(_ searchTerm: String, _ sort: Bool=true) -> Bool {...}

//You are able to call this as...find(“toronto”)

Rest allows for an indefinite amount of arguments. When a parameter is prepended by  this implies that a number of arguments have been passed in as an array.

function outputArgLength(...groupedArguments) {return 'output: ' + groupedArguments.length}

outputArgLength(2, 4, 6) //output: 3

UPDATE:

In Swift, there is actually an equivalent to this called Variadic parameters (I just don’t tend to use it too much). The syntax is just slightly different with the _…_ appended to the type, rather than prepended to the argument name.

Keep in mind that parameters do need types in Swift, as it’s a strongly typed language. Although this can take the form of a generic instead of a primitive or object, but that’s a topic for outside of this blog post.

func outputArgLength(_ groupedArguments: Int...) -> String {return "output: \(groupedArguments.count)"}

outputArgLength(2, 4, 6)//output: 3

7. Promises

The reason for promises existing is pretty much a main reason why I ran away from JS to iOS all of those years ago — to alleviate what would later be called (but was frustratingly originally embraced) callback hell.

Promises are async callbacks conveniently encapsulated for catches or continuing on the flow. Anything that returns a promise, provides you with the ability to use chaining with .then (that passes in the successful result) or .catch(that passes in an error). Define a promise like such:

function refill() {  return new Promise(    function (resolve, reject) {	  resolve(result)	  reject(error)    }  )}

What if 2 or more async calls are executed subsequently (the promise equivalent of nesting)? Well, conveniently errors are propagated on until the error is handled:

refill().then(refillResult => {   return requestMoreBeans()}).then(moreBeansResult => {}).catch(error => {});

In Swift, closures fill the void of needing to execute a function asynchronously. A closure isn’t asynchronous itself, but this need can be filled with Grand Central Dispatch (GCD). The following is a basic example of how to execute a closure asynchronously:

DispatchQueue.global().async {  refill()  DispatchQueue.main.async {	//Perform UI related code on the main thread...  }}

While it’s possible to fall victim to callback hell in this implementation too, the habit in calling back to the main thread, as well as following good design patterns and SOLID principles, alleviates this problem.

GCD is FIFO by nature, but if you need more control over the dependencies and states of your queue, and the ability to pause and cancel operations, there’s OperationQueue. Although this might sound counter-intuitive, because OperationQueue technically uses GCD, and GCD is a lower level API, it’s often overkill to use OperationQueue. Because a Promise possesses a range of 3 states (pending, fulfilled, rejected), it’s probably more in line with an OperationQueue in nature, yet more similar to the lower level GCD syntactically.

Operations in OperationQueue are executed on a background thread by default, different to GCD where you have to specify this. Both GCD & OperationQueue provide an abstraction on creating threads directly (the async and sync decision making is left to you in GCD). Thread management is complicated enough with these abstractions, let alone without them, but it’s essential in mobile. If you’re working in Swift, you’re going to be using either or both of these libraries a lot.

8. Function types as return types

This needs an honourable mention, because the pattern of declaring a function that returns a function is very common in React & React Native. A similar objective can be achieved in Swift, and although I haven’t used it a lot, being inspired by React could form the basis of trying a similar approach the next time I dive into Swift.

The only difference in JavaScript, is you can declare a constant as a function without defining the function first (although this can be achieved with closures in Swift, as touched on earlier):

const refillDone = () => {//do stuff: refill the coffee, then return how long it took...return 0}

function refillToFull(full) {return refillDone}

In Swift, this isn’t to be confused with a closure as a parameter, which in a way allows a function to execute a function passed in as a parameter. The main difference being here that when a function is returned, the caller decides when or if to execute it. A closure however, is executed at the function’s discretion.

Using our refill example again. We will define refill as a function that returns a function. The function it returns will be another one we define, called refillDone, which returns a TimeInterval denoting the time taken to refill the coffee:

func refillDone() -> TimeInterval {//do stuff: refill the coffee, then return how long it took...return 0}

func refill(toFull full: Bool) -> () -> TimeInterval {return refillDone}

Concepts not so similar

1. Spread Operators

Spread iterates over all elements in an array as an argument (picture a for loop at the parameter level). There is no Swift equivalent.

function sumArguments(x, y, z) {  //You are appearing within the scope of the function as arguments...  return x + y + z}

//You are being passed in as an array...sumArguments([2, 4, 6])

2. Destructuring

Perhaps one of the weirder JS features. It can provide pattern matching to imply the setting of variables. Most appropriately in a set structure, such as an array.

There’s no real equivalent in Swift. Possibly the way that Ranges can be traversed is about the closest comparison.

From what I understand, say if you’re pulling the first, middle, and last items out of an array, you can use destructuring like the following:

var arrayToUse = [1, 2, 3, 4, 5]var [head,, middle,, tail] = arrayToUseconsole.log(head, middle, tail)// 1, 3, 5

To destructure an object, this syntax can be used (to assign to new variables name and city), because the variable names can be implied if the property names are matching:

var { name, city } = { name: “dwight”, city: “toronto” };console.log(name);// "dwight"console.log(city);// "toronto"

Special note for Swift developers — if you see any weirdness in how a variable is declared, you could probably refer to destructuring for answers: https://hacks.mozilla.org/2015/05/es6-in-depth-destructuring/

3. Generators

A function is a generator if it’s declared with * prepending the function name.

A generator provides a default way to reference an element in the set using next(), much like a LinkedList. So I guess if you’re using a list-like implementation, this would perhaps remove much of the boilerplate code.

function* incrementer() {  let index = 0  while(true)    yield index++}

let gen = incrementer()const first = gen.next().valueconst second = gen.next().value

In part 2 I’ll take a similar approach to comparing any similarities between React Native and Swift.

Special thanks to Rohan and Jeanette from Highline BETA for vetting the accuracy and content in this post, and to the whole team in educating me prior to this blog post.


Published by HackerNoon on 2018/11/15