Skip to main content

What does App Transport Security do?

· One min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

App Transport Security (a.k.a ATS) is Apple’s mechanism to ensure developer’s use HTTPS in their app’s communications.

As I’m sure you know, HTTPS is the secure variation of HTTP. In HTTPS, the same fundamentals of HTTP apply, but with the addition of using TLS (Transport Layer Security) to protect the privacy and integrity of the exchanged data from unauthorized parties and Man in the Middle attacks.

TLS relies on encrypting the communication both ways - from the client to the server and the server to the client. So, with App Transport Security, Apple is ensuring that developers use HTTPS (and as a result TLS) in order to ensure privacy and security for all apps and customers.

You can set NSAllowsArbitraryLoads to true in your application’s .plist which will disable the ATS requirement, but your app will likely be rejected unless you can provide a compelling justification to App Review.

What does Arrange, Act, and Assert mean with respect to unit tests?

· One min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Arrange, Act, and Assert describe the ideal structure for a unit test.

Firstly, you have to arrange all the necessary inputsand preconditions. Secondly, you perform some action or operation on the object to be tested.And finally, you assert that the expected outcome has occurred.

Here’s an example:

class EmailValidationTests: XCTestCase {
func testValidEmail() {
// Arrange
let testEmail = "aryaman@digitalbunker.dev"

// Act
let isValidEmail =EmailValidation.validate(testEmail)

// Assert
XCTAssertTrue(isValidEmail)
}

func testInvalidEmail() {
// Arrange
let testEmail = "aryaman@digitalbunker"

// Act
let isValidEmail =EmailValidation.validate(testEmail)

// Assert
XCTAssertFalse(isValidEmail)
}
}

This approach helps improve the readability of your tests and makes the expected behavior obvious.

What does copy on write mean?

· 2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

We know that when we’re working with types that use pass-by-value semantics, we’re passing around a copy of the value instead of a direct memory reference.

Now, imagine that the value type we’re working with is storing a lot of data - for example an UIImage.

If we aren't going to modify this array, it's pointless to duplicate it every time we pass it to a function or assign it to a new variable as this would introduce a lot of unnecessary overhead.

That’s why Swift employs a resource management technique called “copy on write”. This technique allows us to efficiently implement a “copy” operation on a modifiable resource.

If the resource in question is never modified, then there’s no need to create a duplicate of it. Instead, we can provide shared access to this resource until such time an operation attempts to modify it. Then, and only then, will Swift duplicate the value.

This approach lets the system significantly reduce the resource consumption of pass-by-value operations in exchange for adding a small overhead to resource-modifying operations.

This functionality is available by default in arrays and dictionaries, but you’ll have to explicitly add it to any new value types you introduce.

What does deadlock mean?

· 2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Deadlocks occur when threads sharing the same resource are waiting on one another to release access to the resource in order for them to complete their respective tasks.

Interviewer: “Explain deadlock and I’ll give you the job.”
Candidate: “Give me the job and I’ll explain deadlock to you.”

Simply put, deadlock describes a situation in which you have two or more entities waiting on one another and, as a result, no progress is made. A deadlock can occur between any two entities. For instance, your code could be waiting for a file system operation to complete and the file system could be waiting for your code to complete execution.

Here’s a simple example of a deadlock:

func deadLock() {
let queue = DispatchQueue(label: "deadlock-demo")

queue.async { // A
queue.sync { // B
print("B: Waiting on A to finish.")
}
print("A: Waiting on B to finish.")
}
}

Our queue is a serial queue so it’s going to run these blocks synchronously - one after the other.

In this case, the inner closure (B) can’t run until the outer closure (A) finishes. This is because A is holding the control of the current thread (remember it’s a serial queue). A can never finish its execution because it’s now waiting for B to finish running.

A depends on B and B depends on A and the thread stalls. One solution would be to change the queue type from serial to concurrent. This would allow the inner closure, B, to start without waiting for A to finish.

Be careful when working with serial queues as they can easily lead to deadlocks.

What does defer do?

· One min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

The defer keyword allows us to specify code that should be executed only when we are leaving the current function’s scope.

It's commonly used for releasing a shared resource, closing a connection, or performing any last-minute cleanup.

In this case, we're using it to close the connection to a database:

class DatabaseManager {
func writeLineToDatabase(entry: String) {
let database = Database()
defer {
database.disconnect()
}
database.connect()
do {
try database.write(entry: "Hello world!")
} catch {
print("An error occurred!")
}
}
}

You’ll see that regardless of whether the:

try database.write(entry: "Hello world!")

call succeeds or fails, we will always close out the database connection without having to duplicate the disconnect() call. Lastly, we can use defer to write setup and cleanup code next to each other, even though they need to be executed at different times.

What does it mean for something to be thread safe?

· 2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Thread safety means that code functions correctly during simultaneous execution by one or more threads. It ensures that a shared resource can only be modified by one thread at a time, preventing unpredictable behavior and race conditions.

Thread safety is crucial in concurrent programming. Code is considered thread-safe when it functions correctly during simultaneous execution by one or more threads. More specifically, thread safety ensures that some shared resource can only be modified by one thread at any given time.

Consider a scenario where your application has multiple threads all trying to access the same shared resource. Some threads may simply be attempting to read from the resource while others are trying to modify it.

Now, the resulting state of your application is entirely dependent on whichever thread happens to finish execution first. In software development, we strive for determinism wherever possible.

Imagine a situation where two threads are attempting to modify the same document. In such a scenario, it’s possible for one thread to overwrite the changes made by the other. To make this operation thread-safe, you have to ensure synchronized access to the resource. In other words, you’ll need to make all of the other threads wait until the current thread modifying the resource finishes execution.

Keep in mind that the ordering of the threads' access to some shared resource is determined by the operating system’s scheduler. Since the scheduler dispatches tasks based on the current resource availability, task priority, etc., it may order operations differently from moment to moment. There’s no guarantee that the threads will always execute their tasks in a particular order, leading to potential race conditions.

Thread safety is generally discussed in relation to modifying a resource because multiple threads can read from a resource at the same time without causing changes to the resource, which typically doesn’t introduce issues.

In Bullets
  • Thread Safety: Ensures that code functions correctly during simultaneous execution by multiple threads.
  • Shared Resource: Only one thread can modify a shared resource at any time, preventing race conditions.
  • Synchronized Access: Necessary to ensure that modifications by multiple threads don’t conflict with each other.
  • Scheduler: Manages thread execution order, which can lead to race conditions if not handled properly.

What does it mean to be a first class function or type?

· 2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

A first-class citizen is an entity that can be passed as an argument, returned from a function, modified, or assigned to a variable. So, a programming language is said to have first-class functions if it treats functions as first-class citizens.

In simple terms, this means the language supports passing functions as arguments to other functions, returning functions from functions, and assigning them to variables or storing them in data structures.

As we'll see in the following examples, Swift treats functions as first-class citizens.

Storing Functions In Variables We can easily create a function (or in this case a closure definition) and assign it to a variable:

class FirstClassCitizens {
var doubleInput:((Int) -> Int)?

func setup() {
doubleInput = { value in
return value * 2
}
}
}

Passing Functions As Arguments We can also pass functions as arguments to other functions.

For example, whenever we specify an animation in Swift, the second parameter is actually accepting a function.

class FirstClassCitizens {
func setup() {
UIView.animate(withDuration: 1.0) {
// This is a function passed as a parameter
}
UIView.animate(withDuration: 1.0, animations:{
// Same as above but without trailingclosure syntax
})
}
}

Returning Functions From Functions We can also return a function from a function as well:

class FirstClassCitizens {
func sayHello(name: String)-> (() -> String) {
return {
"Hello, \(name)"
}
}
}

let returnedFunction = FirstClassCitizens().sayHello(name: "Aryaman")
print(returnedFunction()) // Hello, Aryaman

What does layoutSubviews() do?

· One min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

The default implementation of this function uses any constraints you have set to determine the size and position of all of the view’s subviews. Overriding this method allows you to perform a more precise layout of a view’s subviews by setting the frame rectangles of your subviews directly.

Typically, you would only override this function if AutoLayout wasn’t offering the behavior you wanted.

According to Apple’s documentation:

You should not call this method directly. If you want to force a layout update, call the setNeedsLayout() method instead to do so prior tothe next drawing update. If you want to update the layout of your views immediately, call the layoutIfNeeded() method.

What does setNeedsDisplay() do?

· 2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Just like setNeedsLayout() tells the system the layout needs to be updated, setNeedsDisplay() informs the system that the view’scontent needs to be redrawn. This function queues the redrawing task and returns immediately. The view is only redrawn at the next drawing cycle at which point any and all views are updated.

Typically, you will only need to call setNeedsDisplay() if you are also overriding drawRect() in your implementation. As would be the case if you were working on a customUIControlor if your view contained some custom shapes or effects.

drawRect() is responsible for the actual rendering of the view and is called by the system whenever drawing is required. You should never call this function explicitly; letting the system manage the calls to this function helps avoid multiple redraws if one has already been queued.

Let’s say we have a DrawLineView which draws a linebetween 2 provided points. It might look something like this:

class DrawLineView: UIView {
var point1, point2: CGPoint!
// Only override drawRect() if you perform custom drawing.
// An empty implementation adversely affects performance
// during animation.
override func drawRect(rect: CGRect) {
// Draws a line from point1 to point2
let context = UIGraphicsGetCurrentContext()
CGContextMoveToPoint(context, point1.x, point1.y)
CGContextAddLineToPoint(context, point2.x,point2.y)
CGContextStrokePath(context)
}
}

If we wanted to update the location and the length of the line, simply updating the values of point1 and point2 will not suffice. Changing these properties will not automatically redraw the line with updated starting and ending points. So far all we’ve done is change the underlying data, but we still haven’t forced an update of the UI [forced a call todrawRect()].

As a result, we’ll need to call setNeedsDisplay() after updating point1 and point2:

drawLineView.point1 = CGPointMake(startDot.center.x, startDot.center.y);
drawLineView.point2 = CGPointMake(endDot.center.x, endDot.center.y);

drawLineView.setNeedsDisplay()

This call tosetNeedsDisplay()will in turn calldrawRect()which will actually redraw the line to reflect its new starting and ending locations.

Here’s an example courtesy offujianjin6471 on GitHub. It may help to check out this project and play around with it to better understand setNeedsDisplay()’s role.

What does the associatedtype keyword do?

· 2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Imagine we have the following protocol:

protocol Stack {
func push(x: Int)
func pop() -> Int?
}

This protocol would allow whatever entity conforms to it to have the functionality of a Stack.

But what if we wanted our Stack to work with Doubles or Strings? Our only option would be to duplicate the protocol and change the types:

protocol IntStack {
func push(x: Int)
func pop() -> Int?
}

protocol DoubleStack {
func push(x: Double)
func pop() -> Double?
}

Clearly, this approach would be a little silly and is obviously not scalable. Luckily, this is exactly the problem that associated types can help us solve.

The associatedtype keyword allows us to provide a placeholder for the type of the entity that will eventually implement this protocol.

This allows us to generalize our Stack:

protocol Stack {
associatedtype Element
func push(x: Element)
func pop() -> Element?
}

In the example above, we’ve declared this new type Element which doesn’t exist anywhere else in our code. Now, whenever we implement this protocol,Element will be replaced with the type of the entity implementing Stack.

class IntStack: Stack {
func push(x: Int) {
}

func pop() -> Int? {
}
}

class StringStack: Stack {
func push(x: Int) {
}

func pop() -> Int? {
}
}

The compiler is able to infer that Element should be an Int and a String respectively based on our implementations of push() and pop() and the type of the parameters we’ve specified.

You can always explicitly specify what the substituted type should be if you prefer:

class IntStack: Stack {
typealias Element = Int
func push(x: Int) {
}

func pop() -> Int? {
}
}

Now, our protocol is extremely extensible withoutus having to duplicate any code.