Part 1: Swift basics
Why to learn Swift?
Native iOS and macOS Development: If you recently started a Mobile development journey and don’t have any prior experience in mobile development, Swift is a good language to begin with. It is a mobile-first language created by Apple for developing applications on iOS, macOS, watchOS, and tvOS. Also, it has very good community support. Swift is designed with future developments in mind. Apple is committed to evolving Swift, ensuring that it remains relevant and capable of addressing new challenges and technologies.
Hybrid App development: Recently there has been a shift towards hybrid development a lot due to its lower cost and measurable great performance. Hybrid app development also has huge community support. Flutter and React native are backed by Google and Meta respectively. From the perspective of a React Native developer, I found out that after one point of time, you get well versed with React native and you can build anything (which accesses OS features) provided the third-party plugin/library is available for that. Mainly all the third-party plugins are nothing but the Swift/kotlin code ( also C++ code in some instances) written to fulfill the goal of that plugin (for example image picker from the device).
When you run React native code and build the app, javascript code is run on a Javascript thread, and Native code ( Swift/kotlin code) is executed by a UI thread. Communication between the UI and JS thread is happening through the bridge(React Native Old Architecture). React Native's new architecture even supports the bridgeless mode by removing the bridge approach itself.
Native modules: If we want to access the core functionalities of the operating system like Camera, Sensors, Alarm, Bluetooth, photo gallery, etc javascript can’t help here as javascript doesn’t have access to this. But native languages like Kotlin( for Android), and Swift (for IOS) have access to these core functionalities. That’s how native modules help us here.
Native Modules Example: We can create a native module for accessing Android Camera in Swift, and code/implement the core OS functionalities in Swift language with all methods like openCamera, takePicture, etc. We will do this in the react native code base itself. Now JavaScript can call these native methods of Swift and Swift will further invoke core OS features. Hence swift acts as the intermediate medium between javascript and core OS functionalities.
Through these methods, data can be sent from javascript to Swift and Swift to javascript also. For example, if javascript needs to know the data for the image taken from the camera, Swift can pass it to javascript.
Different methods to exchange information: There are different methods through which data is exchanged between Swift and JavaScript. One is passing as a callback and invoking it, another is waiting for the promise to resolve and the other one is emitting an event.
Hence in case if you need to debug any issue/bug on the native side/plugin side, it becomes difficult to do that if you don’t know native languages. Also In case there is no library/plugin available for a certain feature / the available plugin is not well maintained/ it uses depreciated modules, and it becomes difficult for a developer who just knows react native to make some changes in the plugin/make new plugin from scratch. Hence after some point in time, it's a good idea for React native developers to start learning Swift / Kotlin.
In this article series, we will go over the complete basics of Swift (6.0 beta).
Note: You should follow this article series only if you worked on atleast one programming language in past. If you are a complete beginner just stepping the first foot in the world of programming, better to start following Swift’s official Documentation. Official documentation contains a in-depth explanation of each concept from the beginner’s point of view.
Why This Article?
Navigating through the official documentation can be overwhelming due to its depth and detailed explanations. While the documentation is an invaluable resource, it can sometimes be too complex and vast for those looking for a straightforward understanding. In this article, I’ve taken the core concepts from the official documentation and simplified them. My goal is to provide you with a clear and concise explanation, making it easier to grasp without wading through extensive content.
Here’s why you’ll find this article helpful:
• Concise Explanations: I’ve distilled the key points into easy-to-understand summaries.
• Simplified Concepts: Complex ideas are broken down into manageable pieces.
• Focused Content: Only the essential information you need to understand the topic, without overwhelming details.
Topics Covered:
1. Declaring constants and variables: let & var
2. How to print Output in the console?
3. How to add comments?
4. Integers (Int and UInt)
5. Float and Double
6. Decimal, Binary, Hexadecimal, Octal
7. Operators
8. Range Operators
9. Tuple
10. Optionals and ways to access optionals (Force unwrapping, optional binding, Nil Coalescing Operator)
11. Implicitly Unwrapped Optional
Declaring constants and variables:
There are two ways to store data in variables/constants in Swift: let and var
let constantValue = 10
var mutableValue = 0
let: Allow us to declare/store value that is constant and cannot be changed further. This can be used in cases where we know that certain values should not be changed in the scope of that program. If we try to change the constant value, we will get an error.
let constantValue = 10
var mutableValue = 0
constantValue = 20
Output:
Error: error: cannot assign to value: 'constantValue' is a 'let' constant
note: change 'let' to 'var' to make it mutable.
var: This allows us to declare/store value that can be changed any number of times.
Swift is a type-safe language. That means whichever value we assign to any variable, swift infers the type automatically. Int, Float, Double, Bool, String, and Character are some of the primary types in Swift.
We can even just declare the let/var variable and assign it value later. But in order to do so, we need to give it a type so that the variable is aware of the incoming type of value.
let constantValue: Int;
var mutableValue = 0
constantValue = 20
Here for constantValue, we have Int type and Swift understood that variable will have Int value in future.
If we don’t give the type while declaration, it throws us the error:
let constantValue;
var mutableValue = 0
constantValue = 20
Output: error: type annotation missing in pattern
Also in Swift, if the variable is declared with type but if we try to access it before assigning any value to it we will get an error as follows:
let constantValue: Int;
print(constantValue)
var mutableValue = 0
constantValue = 20
Output: error: constant 'constantValue' used before being initialized
When I assigned a new value to constantValue, it first checked if the value if of the same type that is given while declaration. If it finds that a different type of value is assigned than what is mentioned while declaration, it throws the error:
let constantValue: Int;
var mutableValue = 0
constantValue = true;
Output: error: cannot assign the value of type 'Bool' to type 'Int'.
You can declare multiple constants or multiple variables on a single line, separated by commas:
var x = 0.0, y = 0.0, z = 0.0
var red, green, blue: String
It's not always necessary to give type if you give the value. Because Swift infers the type. For example in the following case, you don’t need a type declaration:
let name = "Varun"
Also, one more important thing is you don’t need to add a semicolon always. Swift does that automatically.😉
In Swift, when you declare a variable with the same name at both the global and local scope, the local variable will take precedence within its scope. This is due to the concept of variable shadowing, where a local variable with the same name as a global variable “shadows” or hides the global variable within the local scope.
var globalVariable = "Global Scope"
func testFunction() {
var localVariable = "Local Scope"
print(globalVariable) // This will print "Global Scope"
print(localVariable) // This will print "Local Scope"
var globalVariable = "Shadowed Local Scope"
print(globalVariable) // This will print "Shadowed Local Scope"
}
testFunction()
print(globalVariable) // This will print "Global Scope"
How to print Output in the console?
let name = "Varun";
print(name); //Prints Varun
print("My name is \(name)") //Prints My name is Varun
For dynamic variable value, you just need to add value with “\” and “()”.
How to add comments?
For readability of code, sometimes it is necessary to add comments in the code as follows:
//Single line comment
/*
Multi line comment
Multi Line comment
*/
Integers:
Integers means numbers with no fractional component. In the programming world, integers are divided into two types Signed and Unsigned.
Signed: Numbers can be Positive (+1, +3), can be Negative (-1, -3), or can be Zero (0).
Unsigned: Numbers can be Positive (1, 3) or can be Zero (0).
For defining the type of number, Signed and Unsigned types further can be divided into 8-bit, 16-bit, 32-bit, and 64-bit.
If we have an 8-bit unsigned Integer type variable, the max value that can be stored in that variable is
2⁸ — 1 = 256 -1 = 255 (1111 1111). That means a range of values is 0 to 255.
Similarly, if we have 16 unsigned Integer type variable, the max value that can be stored in that variable is
2¹⁶ — 1 = 65536–1 = 65535 (1111 1111 1111 1111). That means a range of values is 0 to 65535.
Hence depending on the value and in which range it lies, there are different types of variables supported by Swift:
UInt8
UInt16
UInt32
UInt64
Int8
Int16
Int32
Int64
If you try to assign value to a type more than its max/min capacity to hold, swift throws the error:
let cannotBeNegative: UInt8 = -1
// UInt8 can't store negative numbers, and so this will report an error
let tooBig: Int8 = Int8.max + 1
// Int8 can't store a number larger than its maximum value,
// and so this will also report an error
For defining integer value, either you can use these types based on the max and min cap your integer value needs to be held OR you can go with standard types provided by Swift Int and UInt:
let i: Int = -2;
let j: UInt = 0;
Int and UInt have sizes that are the same as the platform’s native word size. In iOS 11 and later, all apps use the 64-bit architecture. That means on iOS 11 or later, Int Or UInt has a size of 64-bit.
Unless you need to work with a specific size of integer, always use Int
for integer values in your code. Even on 32-bit platforms, Int
can store any value between -2,147,483,648
and 2,147,483,647
, and is large enough for many integer ranges.
Float and Double:
For numbers with fractional components, float and double is used. Float
represents a 32-bit floating-point number and Double
represents a 64-bit floating-point number.
Float provides accuracy only up to 6 decimal digits.
let value: Float = 0.7767668788
print(value) //Prints 0.7767669
Double provides accuracy up to 15 decimal digits.
let value: Double = 0.7767668788
print(value) //Prints 0.7767668788
Also swift always chooses Double
(rather than Float
) when inferring the type of floating-point numbers.
let pi = 3.14159
// pi is inferred to be of type Double
Decimal, Binary, Hexadecimal, Octal:
Whenever you want to deal with binary, hexadecimal, or octal numbers in Swift, add a necessary prefix.
let decimalInteger = 17
let binaryInteger = 0b10001
let octalInteger = 0o21
let hexadecimalInteger = 0x11
print(decimalInteger, binaryInteger, octalInteger, hexadecimalInteger) //Prints 17 17 17 17
Operators:
If have learned at least one language before, you already know we have various operators in programming languages.
Swift supports the following:
- Addition (
+
) - Subtraction (
-
) - Multiplication (
*
) - Division (
/
) - Remainder ( %)
- Unary Minus (-) : Basically output of (-3) = -3 and output of -(-3) is 3.
- Equal to (
a == b
) - Not equal to (
a != b
) - Greater than (
a > b
) - Less than (
a < b
) - Greater than or equal to (
a >= b
) - Less than or equal to (
a <= b
) - Logical NOT (
!a
): The logical NOT operator (!a
) inverts a Boolean value so thattrue
becomesfalse
, andfalse
becomestrue
. - Logical AND (
a && b
) - Logical OR (
a || b
) - The ternary conditional operator ( ? :) — Similar to javascript, swift also supports this operator. Example:
let a = 20;
let b = 23;
let result = a < b ? "a less than b" : "b less than a";
Range Operators:
let lowerBound = 1;
let higherBound = 5;
for index in lowerBound...higherBound {
print("\(index)")
}
Output:
1
2
3
4
5
In the for loop, you can add a range instead of an array. Swift will automatically assign the value from range to index at the start and also for every iteration in the loop further.
If you have a range containing the same value for lower-bound and upper-bound, it will iterate for that one value only.
for index in 1...1 {
print("\(index)")
}
Note: higher-bound should be equal to or higher than lower-bound. Also higher-bound and lower-bound should be Integers only.
You can even exclude the last value in the range as follows: Here it iterated for 1, 2, 3, and 4 and excluded value 5 which is higherBound.
let lowerBound = 1;
let higherBound = 5;
for index in lowerBound..<higherBound {
print("\(index)")
}
Output:
1
2
3
4
In Swift, range operators can also be used to slice some data from the array.
let array = ["a", "b", "c", "d", "e"]
let slice = array[1...3]
print(slice) //Prints ["b", "c", "d"]
print(array) //Prints ["a", "b", "c", "d", "e"]
We used range to slice some elements of the array. It used range as indices and gave us the new array which includes the elements in that indices range.
For range [1…3], let’s say 1 is called the lower bound of range and 3 is called the higher bound of range. You can even exclude lower/higher bound.
If the lower bound is excluded, it will by default treat the lower bound as 0.
let array = ["a", "b", "c", "d", "e"]
let slice1 = array[...3]
print(slice1) // Prints ["a", "b", "c", "d"]
let slice2 = array[..<3] //this excludes the 3rd index and includes elements only upto index 2.
print(slice2) // Prints ["a", "b", "c"]
If a higher bound is excluded, it will by default treat a higher bound as the last index in the array.
let array = ["a", "b", "c", "d", "e"]
let slice = array[3...]
print(slice) // Prints ["d", "e"]
If both lower and higher bounds are excluded, it will by default treat the lower bound as 0 and the higher bound as the last index in the array. It will give us all the elements in an array.
let array = ["a", "b", "c", "d", "e"]
let slice = array[...]
print(slice) // Prints ["a", "b", "c", "d", "e"]
Note: You should exclude the bounds only when you are using range as indices of the array. In the following case, the program will run infinite times as it doesn't know when to stop.
for index in 1... {
print("\(index)")
}
Tuple:
In swift tuple data structure is used to store multiple types of values in one variable.
Let's say you want to store a student’s data such as roll number, and name, you can create a tuple for that
let Student = (33, "Varun");
Here tuple is created with type (Int, String)
You can create a tuple with any no of values and any number of types. Here you can even store students’ addresses, marks, etc.
You can access specific elements as follows:
print("The Roll number is \(Student.0)") // This accesses roll number 33
print("The Roll number is \(Student.1)") // This accesses name "Varun"
You can even destructure tuple as follows:
let (RollNumber, Name) = Student
print("The Roll number code is \(RollNumber)")
Note that a tuple is just a lightweight data structure and is not used for storing complex data. The most common use case of the tuple is when you want to return more than one value from a function.
Tuples can also be compared as follows:
let firstTuple = (1, "zebra");
let secondTuple = (1, "apple");
let result = firstTuple < secondTuple
print("\(result)") //Prints false
In order to compare any tuple they should have the same type and the same number of values.
Here tuples are compared from the leftmost side. If you find equal elements ignore them. Comparison is done only when we find two unequal elements.
For the above example, 1 and 1 are equal hence go ahead. Now “zebra” and “apple” is not equal hence we can compare them.
The first time when we find two unequal elements, the comparison is done and the result of that comparison is the final result.
Here “zebra” is larger than “apple” and hence the result will be false.
If you try to compare tuples of different types, swift will infer the type and you will get the error:
let firstTuple = (1, "zebra");
let secondTuple = ("one", "apple");
let result = firstTuple < secondTuple
print("\(result)")
//Output: error: cannot convert value of type '(String, String)' to specified type '(Int, String)'
If you try to compare tuples of different numbers of values, you will get the error:
let firstTuple = (1, "zebra");
let secondTuple = (1, "apple", "mango");
let result = firstTuple < secondTuple
print("\(result)")
output: binary operator '<' cannot be applied to operands of type '(Int, String)' and '(Int, String, String)'
Also if you try to compare two tuples where the comparison operator doesn’t support comparison of that tuple type, you will get an error.
let firstTuple = (1, true);
let secondTuple = (1, false);
let result = firstTuple < secondTuple
print("\(result)")
output: error: binary operator '<' cannot be applied to two '(Int, Bool)' operands
Here as the “<” operator couldn't compare boolean values, it gave us an error. But if you use the “==” operator here, it would give a valid output.
let firstTuple = (1, true);
let secondTuple = (1, false);
let result = firstTuple == secondTuple
print("\(result)")
Output: false
Optionals:
You can make any variable/constant optional by appending “?” to the type of that variable as follows:
let optionalValue1: Int? = 20;
let optionalValue2: Int? = nil;
The optional either contains a value of the provided type or nil (No value).
If you try to access the optional directly with some value, you will get the following output:
let optionalValue1: Int? = 20;
print(optionalValue1) //Prints Optional(20)
There are some ways to access the optionals:
Force Unwrapping: (Unsafe): You can directly force unwrap the optional to access its value. It uses an exclamation mark (!) to forcefully unwrap an optional. It assumes the optional contains a value and accesses it directly. But if the optional contains nil value, it causes a runtime crash and hence this method is risky and generally discouraged unless you are certain that the optional contains a value.
let optionalValue1: Int? = 20;
print(optionalValue1!) //Prints 20
let optionalValue1: Int? = nil;
print(optionalValue1!)
Output: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Optional binding: Optional binding safely unwraps an optional by checking if it contains a value. If it does, it assigns the value to a constant or variable.
let optionalValue1: Int? = 20;
if let optionalValue1 {
print("The optionalValue1 is: \(optionalValue1)")
} else {
print("The optionalValue1 was nil")
}
Output: The optionalValue1 is: 20
Here this is a minimized approach of optional binding where you can directly add optional value after if let and then if optional contains some value then if block is executed. If optional contains nil, else block is executed.
let optionalValue1: Int? = nil;
if let optionalValue1 {
print("The optionalValue1 is: \(optionalValue1)")
} else {
print("The optionalValue1 was nil")
}
Output: The optionalValue1 was nil
Nil Coalescing Operator (??): The nil coalescing operator provides a default value if an optional is nil.
let optionalString: String? = nil
let result = optionalString ?? "Test"
print(result) // Prints "Test"
?? operator first checks if the value is present or is nil. If value is present, it assigns that value to defaultString. Or else it goes for value after ?? (Default value) and assigns that value.
Implicitly Unwrapped Optional:
As we know optional can either contain nil or value. If we try to access optional which contains nil with force unwrapping, the application will crash.
Implicitly Unwrapped Optional is an optional itself. But to access it, you don’t need to force unwrap it or use optional binding or Nil Coalescing Operator.
Syntax:
let optionalString: String! = nil;
optionalString = "Test";
print(optionalString) // Prints "Test"
Here we defined Implicitly Unwrapped Optional by using “!” after the String type. Implicitly unwrapped optional may start its life with nil but by the time you need to access it, it should have a value.
Hence while accessing it you don’t need to use “!” to force unwrap it as it's already implicitly unwrapped. For any optional, if you are sure that by the time you access it, it will always have a value, you can convert that optional into implicitly unwrapped optional, and while accessing it every time you don’t need to use optional binding.
Note that even in the case of Implicitly Unwrapped Optional if you access optional containing nil, the application will crash. Hence be sure that there’s a value there before you use them.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
That’s it for this article. See you at the next one.
Here’s is a link to the next article:
https://medium.com/p/d4c6dc52b65f
If you found this tutorial helpful, don’t forget to give this post 50 claps👏 and follow 🚀 if you enjoyed this post and want to see more. Your enthusiasm and support fuel my passion for sharing knowledge in the tech community.
You can find more of such articles on my profile -> https://medium.com/@varunkukade999