Part 2: Swift basics
In the last article, we learned about certain topics of the swift. In this article, we will cover the various data storage types.
Here is prev article: https://medium.com/@varunkukade999/part-1-swift-6-0-beta-complete-basics-4732c8b3e57b
Topics covered:
1. Strings
2. Characters
3. Unicode, Unicode Scaler Values, Extended Grapheme Cluster
4. Substrings
5. Arrays
6. Sets
7. Dictionary
Strings:
You can define strings as follows:
let user1: String = "Varun";
let user2 = "";
let user3 = String();
Writing type as String is optional here as Swift automatically infers the type from the value assigned. But if value is not assigned then it's mandatory to give the type so that Swift already knows which type of value variable should expect. Also, you will get an error if you try to access unassigned strings.
Swift String only supports double quotation marks. Writing single quotation marks will throw an error.
let a = 'varun'
print(a)
Output: error: single-quoted string literal found, use '"'
You can write multiline string using triple quotation marks at the beginning and the end.
let softWrappedQuotation = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit
"""
print(softWrappedQuotation)
Output:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit
If you compare the input and output string, similar to the input, the output also shows the new line for each of sentence.
If you want to write multiline strings in the code just for good readability purposes and want to avoid string showing up in multiline format in output, you can use the “\” character at the end of that line.
let softWrappedQuotation = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. \
Duis aute irure dolor in reprehenderit
"""
print(softWrappedQuotation)
Output:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit
You don’t need to escape single quotation marks inside a string.
let a = "varun 'hi'"
print(a) //Prints varun 'hi'
But you will need to escape the double quotation marks by adding “\” before it.
let a = "My name is \"Varun\". "
print(a) //Prints My name is "Varun".
Of course, you can use \n to have a new line in a single line string. Similarly, you can use \t to have the horizontal tab for a string.
Check whether a string is empty as follows:
let a = ""
print(a.isEmpty) //Prints true
Strings created with var can be modified. Constant strings cannot be modified. + operator is used for concatenating two strings.
var string = "Apple";
string = string + " is red";
print(string) //Prints Apple is red
Value types vs Reference types:
First, check out this wonderful blog. https://www.swift.org/documentation/articles/value-and-reference-types.html
After going through the blog you must have understood what is difference between Value types and Reference types. By default String is a value type.
var a = "20";
var b = a;
b = "100";
print(b);
print(a)
In the above program when we assign a to b, a new copy of a is created and assigned to b. That means under the hood, now a and b points to different addresses in the memory. Hence if we change one of them, it won’t affect the other.
Integers are also a value type.
But if you want, you can make String a reference type using the combination of inout keyword and &.
func SwapValues(value1: inout String, value2: inout String){
value1 = "b";
value2 = "a";
}
var val1: String = "a";
var val2: String = "b";
SwapValues(value1: &val1, value2: &val2);
print(val1, val2) //Prints b a
Explanation of code: Normally when you pass a string as an argument to a function, a new copy is made and is passed. But if you wrote “&” for the argument while calling the function and the “inout” keyword in the function definition while accepting it, the string will be passed by reference. That means the passed string while calling the function and the received string in the function definition as a parameter now point to the same address in the memory. Hence changing the value of any of them will affect the value of other.
You can even include/compute expressions inside a string as follows:
let fruit = "apple";
let avgEverydayCount = 3;
let message = "I purchased \(avgEverydayCount * 30) apples this month.";
print(message) //Prints I purchased 90 apples this month.
Characters:
You can create new character as follows:
let newCharacter: Character = "a";
You can access each character in a string by iterating the string through the loop.
for character in "Dog!🐶" {
print(character)
}
// D
// o
// g
// !
// 🐶
You can construct a string using a character array as follows:
let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
let catString = String(catCharacters)
print(catString) // Prints "Cat!🐱"
You can append a character to a string as follows:
var welcome = "hello there";
let exclamationMark: Character = "!"
welcome.append(exclamationMark);
// welcome now equals "hello there!"
Unicode Scaler Values:
Computers fundamentally operate using binary data (bits: 0s and 1s). Every piece of information, including text, needs to be represented in binary form to be processed by a computer. Character encoding schemes provide a way to map characters to specific binary values. Encoding schemes standardize the representation of characters so that text can be consistently understood across different systems and devices. Without a standardized encoding, there would be no guarantee that a character encoded on one system would be correctly interpreted on another.
ASCII: The Starting Point
• Historical Context: ASCII was one of the earliest encoding schemes developed in the 1960s. At the time, computer memory and storage were limited, and ASCII’s 7-bit encoding (128 characters) was sufficient for most applications in the English-speaking world.
• Simplicity: ASCII is simple and covers the basic English alphabet, digits, and some control characters. This made it easy to implement and use in the early days of computing.
• Legacy Systems: Many older systems and protocols were built around ASCII, and it remains widely supported for backward compatibility.
Example:
• “A” -> 65
• “a” -> 97
Unicode: The Modern Solution
• Globalization: As computing became more global, the need to support multiple languages and scripts became evident. ASCII’s limited character set couldn’t accommodate this need.
• Comprehensive Coverage: Unicode was developed to address the limitations of ASCII by providing a unique code point for every character in all the world’s writing systems. This includes symbols, emojis, and special characters.
• Flexibility: Unicode supports different encoding formats (UTF-8, UTF-16, UTF-32) to balance between storage efficiency and ease of use.
Unicode Scaler Value: A Unicode scalar value is a unique 21-bit number for a character or modifier.
As example: U+0061
for LATIN SMALL LETTER A
("a"
). Here LATIN SMALL LETTER A is a name for this Unicode scaler value.
Similarly, every character/special symbol/emoji can be represented using the Unicode Scaler Value.
Extended Grapheme Cluster: An Extended Grapheme Cluster is a sequence of one or more Unicode Scaler Values that form a single human-readable character.
Examples:
The letter é
can be represented as a single Unicode scalar é
(LATIN SMALL LETTER E WITH ACUTE
, or U+00E9
). However, the same letter can also be represented as a pair of scalars — a standard letter e
(LATIN SMALL LETTER E
, or U+0065
), followed by the COMBINING ACUTE ACCENT
scalar (U+0301
). The COMBINING ACUTE ACCENT
scalar is graphically applied to the scalar that precedes it, turning e
into an é
when it’s rendered by a Unicode-aware text-rendering system.
In both cases, the letter é
is represented as a single Swift Character
value that represents an extended grapheme cluster. In the first case, the cluster contains a single scalar; in the second case, it’s a cluster of two scalars:
let eAcute: Character = "\u{E9}" // é
let combinedEAcute: Character = "\u{65}\u{301}" // e followed by ́
// eAcute is é, combinedEAcute is é
print(eAcute) //Prints é
print(combinedEAcute) //Prints é
If you try to compare these two strings, they will be the same:
print(eAcute == combinedEAcute) //Prints true
Why we can’t access characters in a string using integer indices?
In any programming language array elements/string characters are accessed using integer index a[0]. For any array, generally, a base address is assigned to the array, and elements in the array are placed next to each other.
Example: For array a, the base address of 2020 is assigned. When new elements are added they are placed next to each other. The memory address of the nth indexed element is calculated as:
a[0] address: Base address + (i * Size of int) = 2020 + (0 * 4) = 2020
a[1] address: Base address + (i * Size of int) = 2020 + (1 * 4) = 2024
a[2] address: Base address + (i * Size of int) = 2020 + (2 * 4) = 2028
This is how the element is accessed at a certain address. Here size of the int is important. As the size is the same for all ints it's easy to access elements using integer index in o(1) time using direct address calculation.
But as we saw each Unicode Scaler Value representing a certain character requires a different amount of memory to store. Hence we can’t use direct address calculation here. To access the i-th element here, it becomes inefficient for memory to use integer indices.
If you try to access Swift string using an integer index, you will get the error:
let greeting = "Hello, world!"
print(greeting[3]) //error: 'subscript(_:)' is unavailable: cannot subscript String with an Int, use a String.Index instead.
String Indices:
You can access the first indexed (0) element like this:
//access first element
let greeting = "Guten Tag!"
print(greeting[greeting.startIndex]) //Prints G
endIndex in Swift denotes the position after the last element in the string. before and after gives access to subsequent prev and next elements.
//access last element
let greeting = "Guten Tag!"
print(greeting[greeting.index(before: greeting.endIndex)]) //Prints !
//access index 1 element
print(greeting[greeting.index(after: greeting.startIndex)]) //Prints u
In the above example, endIndex denotes the next subsequent position of the index after “!”. Hence to access the last element, you would need to use before as before gives the index for “!”.
To not overuse before and after for accessing characters in long strings, provide your start index value and find any element by using offset.
//access element exist at index 7 from index 0
let greeting = "Guten Tag!"
let index = greeting.index(greeting.startIndex, offsetBy: 7);
print(greeting[index]) //Prints a
Negative indices for a string start from the last element. The last element has a -1 index, the 2nd last one has a -2, and so on.
//access last element
let name = "Varun";
print(name[name.index(name.endIndex, offsetBy: -1)]) // Prints n
If you try to access the index/character out of bounds, you will get the error:
let greeting = "Guten Tag!"
print(greeting[greeting.endIndex])
print(greeting.index(after: greeting.endIndex))
print(greeting.index(before: greeting.startIndex))
//all of above gives Fatal error: String index is out of bounds
Use the indices
property to access all of the indices of individual characters in a string.
let greeting = "Guten Tag!"
for index in greeting.indices {
print("\(greeting[index]) ", terminator: "")
}
//Output: G u t e n T a g !
Insert the character into a string:
var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
print(welcome) //Prints hello!
To add any other substring/more than 1 character in the certain string at a certain index:
welcome.insert(contentsOf: " there", at: welcome.index(before: welcome.endIndex))
print(welcome) //Prints hello there!
Remove the character from a string:
welcome.remove(at: welcome.index(before: welcome.endIndex))
print(welcome) //Prints hello there
Remove substring from string:
print(welcome) //Prints hello there
let lowerBound = welcome.index(welcome.endIndex, offsetBy: -6);
let higherBound = welcome.endIndex;
let range = lowerBound..<higherBound;
welcome.removeSubrange(range);
print(welcome) //Prints hello
The lower bound denotes the index -6 from the end and index 5 from the start.
The higher bound denotes index 11 which is just the subsequent index after the last element position.
Range denotes the index from 5 to 10.
Method removeSubrange remove the elements from index 5 to 10.
Find the Index of specific element:
let greeting = "Hello, world!";
let index = greeting.firstIndex(of: ",") ?? 2; //index is 5
firstIndex method calculates the index of the first occurrence of a provided element. As you can see we use the “??” operator as the firstIndex method returns optional and it may contain nil value if no occurrence is found in the string for the provided element.
Substrings:
You can use a range operators to create a substring out of string.
let greeting = "Hello, world!"
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex //represents 5
let beginning = greeting[..<index] //gets a substring from index 0 to 4
print(beginning) //Prints Hello
In Swift, for optimization, when you create a new substring, it reuses the memory of the original string.
Substrings aren’t suitable for long-term storage — because they reuse the storage of the original string, the entire original string must be kept in memory as long as any of its substrings are being used. Hence you can create another string instance from this substring for storing that substring on a long-term basis.
// Convert the result to a String for long-term storage.
let newString = String(beginning)
The diagram shows substring “Hello” references to the same memory location where the string “Hello, world!” is stored. Once a new string instance is created for a substring and if no other substrings refer to the original string, the original string memory is deallocated if not in use.
Check suffixes and prefix:
let str = "Hello World!";
print(str.hasSuffix("!")) //Prints true
print(str.hasPrefix("Hello")) //Prints true
Arrays:
Array Creation:
Create an empty array.
var someInts: [Int] = [];
While creating an empty array you need to give the type of array. Or else it will give the following error:
var someInts = [];
//error: empty collection literal requires an explicit type
There is no need to give the array type if you declare the array with one or more elements of the same type. Swift will automatically infer the type of array which is [Int].
var someInt = [1, 2];
Now as you gave Int type, only Int type elements can be stored inside the swift Array. If you try to store any other types of elements swift will give an error.
You can also create an array with an Array class by creating an instance of it.
var threeDoubles = Array(repeating: 0.0, count: 3)
Above will create an array with all three elements containing the value 0.0
Inserting elements in the array:
var someInt = [1, 2];
someInt.append(3)
print(someInt) // Prints [1, 2, 3]
.append will push the elements at last.
You can also add an element at the specified index. Here max index accepted by “at” is an index of the last element + 1. If you provide an index more than that swift throws an error.
var someInt = [1, 2, 3, 4];
someInt.insert(5, at: 4)
print(someInt) // Prints [1, 2, 3, 4, 5]
You cannot change/mutate a constant array defined with the “let” keyword.
let someInt = [1, 2];
someInt.append(3)
print(someInt)
Output: error: cannot use mutating member on immutable value: 'someInt' is a 'let' constant
Also, you can append another array :
var someInt = [1, 2];
shoppingList += [3, 4, 5, 6];
print(someInt) //Prints [1, 2, 3, 4, 5, 6]
Concatenate/combine two arrays:
var someInt = [1, 2];
var AnotherInt = [1, 4]
var mixed = someInt + AnotherInt
print(mixed) //[1, 2, 1, 4]
If you try to concat two arrays with different types, swift will throw the error:
var someInt = [1, 2];
var someFloat = [1.3, 4.4]
var mixed = someInt + someFloat
Output: error: binary operator '+' cannot be applied to operands of type '[Int]' and '[Double]'
Check is Array empty:
var someInt = [1, 2];
print(someInt.isEmpty); //Prints false
Check Array count:
var someInt = [1, 2];
print(someInt.count); //Prints 2
Arrays in Swift are always zero-indexed. The first item in the array has an index of
0
, not1
.
Replace the array element with another:
var someInt = [1, 2, 3, 4];
someInt[1] = 3
print(someInt); //Prints [1, 3, 3, 4]
Note that this is a replacement of an existing element present at a certain valid index and not an addition to the new index. Hence you cannot do something like this:
var someInt = [1, 2, 3, 4];
someInt[4] = 5
print(someInt);
Output: Fatal error: Index out of range
This threw an error because Swift tried to find the 4th index for replacement. But it couldn’t find it.
You can also replace more than 1 element at a time using the range operator:
var someInt = [1, 2, 3, 4];
someInt[1...2] = [8, 7];
print(someInt); ///Prints [1, 8, 7, 4]
someInt[1..<2] = [10];
print(someInt); ///Prints [1, 10, 7, 4]
someInt[...3] = [12, 13, 14, 14];
print(someInt); ///Prints [12, 13, 14, 14]
Remove element from the array:
var someInt = [1, 2, 3, 4];
let removedElement = someInt.remove(at: 0);
print(removedElement) //Prints 1
print(someInt) //Prints [2, 3, 4]
You can also remove the last element like this:
var someInt = [1, 2, 3, 4];
someInt.removeLast();
print(someInt) //Prints [1, 2, 3]
If you try to insert/replace/remove any elements whose index is out of bound, Swift will give a runtime error.
Iteration:
Finally, you can iterate over an array using a for-in loop:
var someInt = [1, 2, 3, 4];
for item in someInt {
print(item)
}
Output:
1
2
3
4
If you want to have access to both index and value:
var someInt = [1, 2, 3, 4];
for (index, value) in someInt.enumerated() {
print("Item \(index + 1): \(value)")
}
Output:
Item 1: 1
Item 2: 2
Item 3: 3
Item 4: 4
Sets:
A Set stores distinct values of the same type in a collection with no defined ordering. You can use a set instead of an array when the order of items isn’t important, or when you need to ensure that an item only appears once. You can have a Set of type String
, Int
, Double
, and Bool or Enum also.
Creation of set:
var letters = Set<Character>();
print(type(of: letters)) //Prints Set<Character>
You can even create a set using a similar syntax as arrays by ensuring the correct type.
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"];
If you don’t give the type, swift will infer the type as [String]. Hence it's important to give the type while creating a new set.
You don’t necessarily give the actual type between string, int, etc when you are creating a set with some values as Swift can infer the type.
This is okay:
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
print(type(of: favoriteGenres)) // Output: Set<String>
If you create a set with duplicate elements, the set will contain unique elements.
var integers: Set = [1, 2, 3, 4, 4]
print(integers) //Prints [1, 2, 3, 4]
You can empty a set like this:
var integers: Set = [1, 2, 3, 4, 4]
integers = [];
Insert item into Set:
var integers: Set = [1, 2, 3, 4]
integers.insert(5)
print(integers) //Prints [5, 2, 1, 3, 4]
If you try to insert an existing value, it won’t be added.
var integers: Set = [1, 2, 3, 4]
integers.insert(3)
print(integers) //Prints [1, 2, 3, 4]
Check if the set is empty
var integers: Set = [1, 2, 3, 4]
print(integers.isEmpty) //Prints false
Check Set Count:
var integers: Set = [1, 2, 3, 4]
print(integers.count) //Prints 4
Remove item from Set:
.remove method retunes an optional. If the value provided was found in the set, then optional contains a value. If the value was missing, optional contains nil. Hence it's necessary to safely handle the optional.
var integers: Set = [1, 2, 3, 4]
let item = integers.remove(4);
if let item {
print("Removed item ", item) //Prints Removed item 4
} else {
print("Item couldn't be found")
}
print(integers) //Prints [2, 3, 1]
Iteration Over Set:
var integers: Set = [1, 2, 3, 4]
for i in integers {
print(i)
}
Output:
3
1
2
4
here’s a simple program that takes input as an array with duplicate elements and creates a new array with unique elements using Set:
let numbers = [1, 2, 3, 2, 4, 3, 5, 1];
// Convert the array to a set to remove duplicates
let uniqueNumbersSet = Set(numbers);
// Convert the set back to an array
let uniqueNumbersArray = Array(uniqueNumbersSet);
print(uniqueNumbersArray); // Output: [2, 4, 5, 3, 1]
Various Operations on Set:
let integers1: Set = [1, 2, 3, 4]
let integers2: Set = [0, 2, 4, 6, 5]
//Union: Both set elements inluding their intersection elements only once.
print(integers1.union(integers2).sorted()) //[0, 1, 2, 3, 4, 5, 6]
//intersection: Intersection elements between 2 sets
print(integers1.intersection(integers2).sorted()) //[2, 4]
//Take integers1. Subtract/remove elements from integers1 which are part of integers2 also. Print integers1
print(integers1.subtracting(integers2).sorted()) //[1, 3]
//symmetricDifference: Both set elements excluding their intersection elements.
print(integers1.symmetricDifference(integers2).sorted()) //[0, 1, 3, 5, 6]
let integers1: Set = [1, 2, 3, 4]
let integers2: Set = [1, 2]
let integers3: Set = [1, 2, 3, 4, 5]
//If two sets are equal
print(integers1 == integers2) //false
//if every element of integers2 exist in integers1
print(integers2.isSubset(of: integers1)) //true
//if integers1 contains every element from integers2
print(integers1.isSuperset(of: integers2)) //true
//if no common elements between integers1 and integers2
print(integers1.isDisjoint(with: integers2)) //false
Dictionary:
The dictionary (Consider Map, Hashmap similar to other programming languages) stores key-value pairs with no defined order. You use a dictionary when you need to look up values based on their identifier, in much the same way that a real-world dictionary is used to look up the definition for a particular word.
Creation of dictionary:
var namesOfIntegers: [Int: String] = [:]
// namesOfIntegers is an empty [Int: String] dictionary
While creating an empty dictionary it is mandatory to give the type or else Swift will throw the error. Here keys should be of type Int and values should be of type String.
You can even exclude the type if you have declared the dictionary with some key-value pairs. This way swift will automatically infer the type.
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
If you create a dictionary with different types of values/keys, it will throw an error:
This is not allowed: 🚫
var airports = ["YYZ": "Toronto Pearson", "DUB": 1]
Output: error: heterogeneous collection literal could only be inferred
to '[String : Any]'; add explicit type annotation if this is intentional
You can use the same .isEmpty and .count methods for checking empty dictionary and checking dictionary key-value pairs count respectively.
Access dictionary value using key:
var keyValues: [String: String] = ["key1": "value1"]
let value1 = keyValues["key1"];
if let value1 {
print(value1)
} else {
print("Value couldn't be found for certain key")
}
Accessing a dictionary using subscript syntax returns optional. Because the dictionary may or may not contain that key.
Insertion/replacement of item in dictionary:
var airports: [String: String] = [:] //create empty dictionary
airports["LHR"] = "London" //add new key value pair
if let LHRValue = airports["LHR"] {
print(LHRValue) //Prints London
} else {
print("Value couldn't be found for LHR")
}
airports["LHR"] = "London Heathrow" //replace existing key value pair
if let LHRValue = airports["LHR"] {
print(LHRValue) //Prints London Heathrow
} else {
print("Value couldn't be found for LHR")
}
You can also use updateValue method for the insertion/updation of the key-value pair. If the key is not present, it adds a new key-value pair. If the key already exists, it replaces the value for that key.
It returns the old value of that key. Also as updateValue retunes optional you need to safely handle it.
var airports: [String: String] = [:] //create empty dictionary
if let LHRValue = airports.updateValue("London", forKey: "LHR") {
//this doesn't run as key "LHR" was not present prev and hence its value is nil
print("The LHRValue is \(LHRValue).")
} else {
//this runs and gets printed
print("This key was not present. Dictionary now has new key value pair for it.")
}
//if key is not present, it gets added as new key value pair.
print(airports) //["LHR": "London"]
//here as key is already present, value is updated for that key
if let LHRNewValue = airports.updateValue("London Heathrow", forKey: "LHR") {
print("The newLHRValue is \(LHRNewValue).") //Prints The newLHRValue is London.
}
Removal of key-value pair:
var airports: [String: String] = ["LHR": "London"];
airports["LHR"] = nil;
print(airports) // Prints [:]
Alternatively, remove a key-value pair from a dictionary with the removeValue(forKey:)
method. This method removes the key-value pair if it exists and returns the removed value or returns nil
if no value existed:
var airports: [String: String] = ["LHR": "London"];
if let removedValue = airports.removeValue(forKey: "LHR") {
print("The removed airport's name is \(removedValue).")
} else {
print("The airports dictionary doesn't contain a value for DUB.")
}
// Prints "The removed airport's name is London."
Iteration:
var airports: [String: String] = ["LHR": "London"];
for (airportCode, airportName) in airports {
print("\(airportCode): \(airportName)")
}
Output: LHR: London
for airportCode in airports.keys {
print("Airport code: \(airportCode)")
}
Output: Airport code: LHR
for airportName in airports.values {
print("Airport name: \(airportName)")
}
Output: Airport name: London
Arrays, Sets, and Dictionary are value types. This means that when you assign or pass them to a function, a copy of the data is made rather than a reference to the original data.
That’s it for this article. See you at the next one.
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