1. Basic Data Types
Swift has several basic data types.
1
2
3
4
5
| let age: Int = 25
let price: Double = 19.99
let isActive: Bool = true
let name: String = "Swift"
let letter: Character = "A"
|
Simple explanation:
1
2
3
4
5
| Int = whole number
Double = decimal number
Bool = true or false
String = text
Character = single character
|
2. Constants vs Variables
Swift uses let and var.
1
2
| let = constant value, cannot change
var = variable value, can change
|
Example:
1
2
3
4
| let maxLoginAttempts = 10
var currentLoginAttempt = 0
currentLoginAttempt += 1
|
Best practice:
1
2
| Use let by default.
Use var only when the value needs to change.
|
3. Type Inference
Swift can automatically detect the type.
1
2
3
4
| let count = 42 // Int
let pi = 3.14159 // Double
let name = "Swift" // String
let isReady = true // Bool
|
You can also write the type manually.
1
2
| var message: String
message = "Hello Swift"
|
Simple explanation:
1
2
| Type inference makes code shorter.
Explicit type declaration makes code clearer when needed.
|
4. Type Annotation
Type annotation means manually writing the data type.
1
2
3
4
| let username: String = "Tawheed"
let score: Int = 100
let rating: Double = 4.8
let isPremium: Bool = true
|
Use type annotation when:
1
2
3
| You want clear code
Swift cannot infer type
You want to avoid wrong type guessing
|
5. Type Safety
Swift is type-safe. It does not allow mixing incompatible types directly.
1
2
3
4
5
6
| let number = 3
let decimal = 0.14
let result = Double(number) + decimal
print(result)
|
Wrong:
1
| // let result = number + decimal
|
Simple explanation:
1
2
| Int and Double are different types.
Swift requires explicit conversion.
|
6. Type Conversion
Swift does not automatically convert types.
1
2
3
4
| let intValue = 10
let doubleValue = Double(intValue)
print(doubleValue)
|
String to Int:
1
2
3
4
| let numberText = "123"
let number = Int(numberText)
print(number ?? 0)
|
Int to String:
1
2
3
4
| let age = 25
let ageText = String(age)
print(ageText)
|
Comments are used to explain code.
Single-line comment:
1
2
| // This is a single-line comment
let name = "Swift"
|
Multi-line comment:
1
2
3
4
5
| /*
This is a multi-line comment.
It can contain many lines.
*/
let age = 25
|
8. Print Output
print() is used to show output.
1
2
| let name = "Swift"
print(name)
|
String interpolation:
1
2
| let age = 25
print("Age is \(age)")
|
9. Operators
Operators are used to perform operations.
Arithmetic Operators
1
2
3
4
5
6
7
8
| let a = 10
let b = 3
print(a + b) // Addition
print(a - b) // Subtraction
print(a * b) // Multiplication
print(a / b) // Division
print(a % b) // Remainder
|
Assignment Operator
1
2
3
4
5
6
| var count = 10
count += 1
count -= 1
count *= 2
count /= 2
|
Comparison Operators
1
2
3
4
5
6
7
8
9
| let a = 10
let b = 20
print(a == b)
print(a != b)
print(a > b)
print(a < b)
print(a >= b)
print(a <= b)
|
Logical Operators
1
2
3
4
5
6
| let isLoggedIn = true
let isPremium = false
print(isLoggedIn && isPremium) // AND
print(isLoggedIn || isPremium) // OR
print(!isLoggedIn) // NOT
|
Range Operators
Closed range:
1
2
3
| for number in 1...5 {
print(number)
}
|
Output:
Half-open range:
1
2
3
| for number in 1..<5 {
print(number)
}
|
Output:
10. Strings
String stores text.
1
2
3
4
| let name = "Swift"
let message = "Hello, \(name)"
print(message)
|
Empty String
1
2
3
4
5
| var text = ""
if text.isEmpty {
print("Text is empty")
}
|
String Count
1
2
| let name = "Swift"
print(name.count)
|
String Uppercase and Lowercase
1
2
3
4
| let name = "Swift"
print(name.uppercased())
print(name.lowercased())
|
String Contains
1
2
3
4
5
| let message = "I love Swift"
if message.contains("Swift") {
print("Found Swift")
}
|
Multi-line String
1
2
3
4
5
6
7
| let paragraph = """
Swift is powerful.
Swift is safe.
Swift is modern.
"""
print(paragraph)
|
11. Character
Character stores a single character.
1
2
| let letter: Character = "A"
print(letter)
|
Loop through characters in a string:
1
2
3
4
5
| let word = "Swift"
for char in word {
print(char)
}
|
12. Collections
Swift has three main collection types:
1
2
3
| Array = ordered list
Set = unique unordered values
Dictionary = key-value pairs
|
13. Array
Array stores ordered values.
1
2
| let numbers = [1, 2, 3, 4, 5]
print(numbers)
|
Explicit type:
1
| let names: [String] = ["John", "Sarah", "Alex"]
|
Access Array Item
1
2
3
| let names = ["John", "Sarah", "Alex"]
print(names[0])
|
Add Item
1
2
3
4
5
| var names = ["John", "Sarah"]
names.append("Alex")
print(names)
|
Remove Item
1
2
3
4
5
| var names = ["John", "Sarah", "Alex"]
names.remove(at: 1)
print(names)
|
Loop Array
1
2
3
4
5
| let names = ["John", "Sarah", "Alex"]
for name in names {
print(name)
}
|
Array Count
1
2
3
| let names = ["John", "Sarah", "Alex"]
print(names.count)
|
Check Empty Array
1
2
3
4
5
| let names: [String] = []
if names.isEmpty {
print("No names found")
}
|
14. Set
Set stores unique values.
1
2
3
| let numbers: Set<Int> = [1, 2, 2, 3, 4]
print(numbers)
|
Output contains unique values only.
Add Item to Set
1
2
3
4
5
| var numbers: Set<Int> = [1, 2, 3]
numbers.insert(4)
print(numbers)
|
Remove Item from Set
1
2
3
4
5
| var numbers: Set<Int> = [1, 2, 3]
numbers.remove(2)
print(numbers)
|
Set Operations
1
2
3
4
5
6
| let a: Set<Int> = [1, 2, 3]
let b: Set<Int> = [3, 4, 5]
print(a.union(b))
print(a.intersection(b))
print(a.subtracting(b))
|
Simple explanation:
1
2
3
| union = all values
intersection = common values
subtracting = values from first set not in second
|
15. Dictionary
Dictionary stores key-value data.
1
2
3
4
5
6
| let user = [
"name": "John",
"email": "john@example.com"
]
print(user["name"] ?? "")
|
Dictionary with Type
1
2
3
4
| var user: [String: String] = [
"name": "John",
"email": "john@example.com"
]
|
Add or Update Value
1
2
3
4
5
6
7
8
| var user = [
"name": "John"
]
user["email"] = "john@example.com"
user["name"] = "Alex"
print(user)
|
Remove Value
1
2
3
4
5
6
7
8
| var user = [
"name": "John",
"email": "john@example.com"
]
user.removeValue(forKey: "email")
print(user)
|
Loop Dictionary
1
2
3
4
5
6
7
8
| let user = [
"name": "John",
"email": "john@example.com"
]
for (key, value) in user {
print("\(key): \(value)")
}
|
16. Optionals
Optionals represent values that may or may not exist.
1
2
| var possibleNumber: Int? = 123
possibleNumber = nil
|
Simple explanation:
1
2
| Int = must contain an Int value
Int? = may contain Int or nil
|
Optional Binding with if let
1
2
3
4
5
6
7
| var possibleNumber: Int? = 123
if let number = possibleNumber {
print(number)
} else {
print("No number found")
}
|
Optional Binding with guard let
1
2
3
4
5
6
7
8
| func showNumber(_ possibleNumber: Int?) {
guard let number = possibleNumber else {
print("No number found")
return
}
print(number)
}
|
Use guard let when you want to exit early.
Default Value with ??
1
2
3
4
| let name: String? = nil
let greeting = "Hello, " + (name ?? "Guest")
print(greeting)
|
Force Unwrapping
Force unwrapping uses !.
1
2
3
| let name: String? = "Swift"
print(name!)
|
Warning:
1
2
| If value is nil, force unwrap causes crash.
Avoid force unwrap unless you are 100% sure.
|
Optional Chaining
Optional chaining safely accesses properties or methods.
1
2
3
4
5
6
7
8
9
10
11
| class User {
var profile: Profile?
}
class Profile {
var name: String = "Tawheed"
}
let user = User()
print(user.profile?.name ?? "No profile")
|
Implicitly Unwrapped Optional
1
2
3
| var username: String! = "Swift"
print(username)
|
Simple explanation:
1
2
3
| String! behaves like optional but can be used like normal value.
It can still crash if nil.
Use carefully.
|
17. Tuples
Tuple groups multiple values together.
1
2
3
4
| let httpError = (404, "Not Found")
print(httpError.0)
print(httpError.1)
|
Named Tuple
1
2
3
4
| let status = (code: 200, message: "OK")
print(status.code)
print(status.message)
|
Tuple Return from Function
1
2
3
4
5
6
7
8
| func getUser() -> (name: String, age: Int) {
return ("John", 25)
}
let user = getUser()
print(user.name)
print(user.age)
|
18. Control Flow
Control flow controls how code runs.
if else
1
2
3
4
5
6
7
| let age = 18
if age >= 18 {
print("Adult")
} else {
print("Minor")
}
|
else if
1
2
3
4
5
6
7
8
9
| let score = 85
if score >= 90 {
print("A")
} else if score >= 80 {
print("B")
} else {
print("C")
}
|
switch
1
2
3
4
5
6
7
8
9
10
| let day = "Friday"
switch day {
case "Friday":
print("Weekend is near")
case "Saturday":
print("Weekend")
default:
print("Normal day")
}
|
switch with range
1
2
3
4
5
6
7
8
9
10
11
12
| let score = 85
switch score {
case 90...100:
print("A")
case 80..<90:
print("B")
case 70..<80:
print("C")
default:
print("Failed")
}
|
for loop
1
2
3
| for number in 1...5 {
print(number)
}
|
while loop
1
2
3
4
5
6
| var count = 0
while count < 5 {
print(count)
count += 1
}
|
repeat while
1
2
3
4
5
6
| var count = 0
repeat {
print(count)
count += 1
} while count < 5
|
Simple explanation:
1
2
| while checks condition first.
repeat while runs at least once.
|
break
1
2
3
4
5
6
7
| for number in 1...10 {
if number == 5 {
break
}
print(number)
}
|
continue
1
2
3
4
5
6
7
| for number in 1...5 {
if number == 3 {
continue
}
print(number)
}
|
19. Functions
Function is a reusable block of code.
1
2
3
4
5
| func greet() {
print("Hello Swift")
}
greet()
|
Function with Parameter
1
2
3
4
5
| func greet(name: String) {
print("Hello, \(name)")
}
greet(name: "Tawheed")
|
Function with Return Value
1
2
3
4
5
6
7
| func add(a: Int, b: Int) -> Int {
return a + b
}
let result = add(a: 10, b: 20)
print(result)
|
Function with Multiple Return Values
1
2
3
4
5
6
7
| func getUserInfo() -> (name: String, age: Int) {
return ("John", 25)
}
let user = getUserInfo()
print(user.name)
|
External and Internal Parameter Names
1
2
3
4
5
| func greet(user name: String) {
print("Hello, \(name)")
}
greet(user: "Tawheed")
|
Simple explanation:
1
2
| user = external name used when calling
name = internal name used inside function
|
Omitting External Parameter Name
1
2
3
4
5
| func greet(_ name: String) {
print("Hello, \(name)")
}
greet("Tawheed")
|
Default Parameter Value
1
2
3
4
5
6
| func greet(name: String = "Guest") {
print("Hello, \(name)")
}
greet()
greet(name: "Tawheed")
|
Variadic Parameter
A variadic parameter accepts multiple values.
1
2
3
4
5
6
7
8
9
10
11
| func sum(_ numbers: Int...) -> Int {
var total = 0
for number in numbers {
total += number
}
return total
}
print(sum(1, 2, 3, 4))
|
In-Out Parameter
inout allows a function to modify a parameter.
1
2
3
4
5
6
7
8
| func increase(_ value: inout Int) {
value += 1
}
var count = 10
increase(&count)
print(count)
|
Simple explanation:
1
| Use & when passing inout parameter.
|
20. Closures
Closure is a block of code that can be stored or passed around.
1
2
3
4
5
| let greeting = {
print("Hello Closure")
}
greeting()
|
Closure with Parameter
1
2
3
4
5
| let greet: (String) -> Void = { name in
print("Hello, \(name)")
}
greet("Tawheed")
|
Closure with Return Value
1
2
3
4
5
| let add: (Int, Int) -> Int = { a, b in
return a + b
}
print(add(10, 20))
|
Short Closure Syntax
1
2
3
4
5
| let numbers = [3, 1, 4, 2]
let sortedNumbers = numbers.sorted { $0 < $1 }
print(sortedNumbers)
|
Simple explanation:
1
2
| $0 = first parameter
$1 = second parameter
|
Trailing Closure
1
2
3
4
5
6
7
| func performTask(action: () -> Void) {
action()
}
performTask {
print("Task completed")
}
|
Escaping Closure
An escaping closure runs after the function returns.
1
2
3
4
5
6
7
8
9
10
11
| var savedCompletion: (() -> Void)?
func loadData(completion: @escaping () -> Void) {
savedCompletion = completion
}
loadData {
print("Data loaded")
}
savedCompletion?()
|
Use cases:
1
2
3
4
| API callback
Delayed action
Stored closure
Async completion
|
Autoclosure
@autoclosure automatically converts an expression into a closure.
1
2
3
4
5
| func logMessage(_ message: @autoclosure () -> String) {
print(message())
}
logMessage("Hello Swift")
|
Simple explanation:
1
2
| You pass normal expression.
Swift wraps it as a closure automatically.
|
21. Enumerations
Enum represents fixed possible values.
1
2
3
4
5
6
7
| enum UserType {
case free
case premium
case admin
}
let type = UserType.premium
|
Enum with Switch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| enum PaymentStatus {
case pending
case completed
case failed
}
let status = PaymentStatus.completed
switch status {
case .pending:
print("Payment pending")
case .completed:
print("Payment completed")
case .failed:
print("Payment failed")
}
|
Enum with Raw Value
1
2
3
4
5
6
7
8
| enum Direction: String {
case north = "North"
case south = "South"
case east = "East"
case west = "West"
}
print(Direction.north.rawValue)
|
Enum with Associated Value
1
2
3
4
5
6
7
8
9
10
11
12
13
| enum ApiResult {
case success(data: String)
case failure(message: String)
}
let result = ApiResult.success(data: "User loaded")
switch result {
case .success(let data):
print(data)
case .failure(let message):
print(message)
}
|
Simple explanation:
1
2
| Raw value = predefined fixed value
Associated value = dynamic value attached to enum case
|
22. Structures
Struct is a value type.
1
2
3
4
5
6
7
8
| struct User {
let id: Int
var name: String
}
let user = User(id: 1, name: "Tawheed")
print(user.name)
|
Use struct for:
1
2
3
4
5
| Models
DTOs
UI state
Simple data
Value-based objects
|
Struct with Method
1
2
3
4
5
6
7
8
9
10
| struct User {
var name: String
func greet() {
print("Hello, \(name)")
}
}
let user = User(name: "Tawheed")
user.greet()
|
Mutating Method
Struct is value type. To modify its own property inside method, use mutating.
1
2
3
4
5
6
7
8
9
10
11
12
| struct Counter {
var count = 0
mutating func increase() {
count += 1
}
}
var counter = Counter()
counter.increase()
print(counter.count)
|
23. Classes
Class is a reference type.
1
2
3
4
5
6
7
8
9
10
11
12
| class UserManager {
var username = "Guest"
func updateName(_ name: String) {
username = name
}
}
let manager = UserManager()
manager.updateName("Tawheed")
print(manager.username)
|
Use class for:
1
2
3
4
5
6
| Shared reference
Inheritance
ObservableObject
Manager objects
Service objects
Objects with lifecycle
|
24. Struct vs Class
Main difference:
1
2
| Struct = Value type
Class = Reference type
|
Struct example:
1
2
3
4
5
6
7
8
9
10
11
| struct Profile {
var name: String
}
var profile1 = Profile(name: "A")
var profile2 = profile1
profile2.name = "B"
print(profile1.name) // A
print(profile2.name) // B
|
Class example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| class ProfileClass {
var name: String
init(name: String) {
self.name = name
}
}
let p1 = ProfileClass(name: "A")
let p2 = p1
p2.name = "B"
print(p1.name) // B
print(p2.name) // B
|
Simple explanation:
1
2
| Struct copies data.
Class shares the same object reference.
|
25. Properties
Properties store values inside struct, class, or enum.
Stored Property
1
2
3
4
| struct User {
let id: Int
var name: String
}
|
Computed Property
Computed property calculates value.
1
2
3
4
5
6
7
8
9
10
11
12
| struct Rectangle {
var width: Double
var height: Double
var area: Double {
return width * height
}
}
let rectangle = Rectangle(width: 10, height: 5)
print(rectangle.area)
|
Property Observer
Property observers observe value changes.
1
2
3
4
5
6
7
8
9
10
11
| var username: String = "Guest" {
willSet {
print("Will change to \(newValue)")
}
didSet {
print("Changed from \(oldValue) to \(username)")
}
}
username = "Tawheed"
|
Lazy Property
Lazy property is created only when first used.
1
2
3
4
5
6
7
8
9
10
11
| class DataManager {
lazy var data = loadData()
func loadData() -> String {
print("Loading data...")
return "Loaded Data"
}
}
let manager = DataManager()
print(manager.data)
|
Static Property
Static property belongs to the type, not object.
1
2
3
4
5
| struct AppConfig {
static let appName = "My App"
}
print(AppConfig.appName)
|
26. Methods
Methods are functions inside struct, class, or enum.
1
2
3
4
5
6
7
8
| struct Calculator {
func add(a: Int, b: Int) -> Int {
return a + b
}
}
let calculator = Calculator()
print(calculator.add(a: 10, b: 20))
|
Type Method
Type method belongs to type itself.
1
2
3
4
5
6
7
| struct MathHelper {
static func square(_ number: Int) -> Int {
return number * number
}
}
print(MathHelper.square(5))
|
27. Subscripts
Subscripts allow accessing values using square brackets.
1
2
3
4
5
6
7
8
9
10
11
| struct NumberList {
let numbers = [10, 20, 30]
subscript(index: Int) -> Int {
return numbers[index]
}
}
let list = NumberList()
print(list[0])
|
28. Initialization
Initializer creates an instance.
1
2
3
4
5
6
7
8
9
| struct User {
let name: String
init(name: String) {
self.name = name
}
}
let user = User(name: "Tawheed")
|
Default Initializer
1
2
3
4
5
6
7
| struct User {
var name = "Guest"
}
let user = User()
print(user.name)
|
Memberwise Initializer
Struct automatically gets memberwise initializer.
1
2
3
4
5
6
| struct User {
let id: Int
let name: String
}
let user = User(id: 1, name: "Tawheed")
|
Failable Initializer
Initializer can fail and return nil.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| struct User {
let age: Int
init?(age: Int) {
if age < 0 {
return nil
}
self.age = age
}
}
let user = User(age: -1)
print(user as Any)
|
29. Deinitialization
deinit runs when class instance is removed from memory.
1
2
3
4
5
6
7
8
9
10
11
12
| class User {
init() {
print("User created")
}
deinit {
print("User removed")
}
}
var user: User? = User()
user = nil
|
Note:
1
2
| deinit is only available for classes.
Structs do not have deinit.
|
30. Inheritance
Inheritance allows one class to inherit from another class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| class Animal {
func sound() {
print("Animal sound")
}
}
class Dog: Animal {
override func sound() {
print("Bark")
}
}
let dog = Dog()
dog.sound()
|
Simple explanation:
1
2
| Dog inherits Animal.
Dog overrides sound().
|
final Class
final prevents inheritance.
1
2
3
4
5
| final class PaymentManager {
func pay() {
print("Payment started")
}
}
|
31. Protocols
Protocol defines requirements.
1
2
3
4
5
6
7
8
9
10
11
12
| protocol Downloadable {
func download()
}
struct VideoDownloader: Downloadable {
func download() {
print("Download started")
}
}
let downloader = VideoDownloader()
downloader.download()
|
Protocol with Property
1
2
3
4
5
6
7
| protocol UserProtocol {
var name: String { get }
}
struct User: UserProtocol {
let name: String
}
|
Protocol Inheritance
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| protocol Readable {
func read()
}
protocol Writable {
func write()
}
protocol ReadWriteable: Readable, Writable {}
struct FileManager: ReadWriteable {
func read() {
print("Reading")
}
func write() {
print("Writing")
}
}
|
Protocol as Type
1
2
3
4
5
6
7
8
9
10
11
12
| protocol PaymentService {
func pay()
}
class StripePayment: PaymentService {
func pay() {
print("Paid with Stripe")
}
}
let paymentService: PaymentService = StripePayment()
paymentService.pay()
|
32. Extensions
Extension adds functionality to an existing type.
1
2
3
4
5
6
7
| extension String {
func addWelcomeText() -> String {
return "Welcome, \(self)"
}
}
print("Tawheed".addWelcomeText())
|
Extension with Computed Property
1
2
3
4
5
6
7
| extension Int {
var isEven: Bool {
return self % 2 == 0
}
}
print(10.isEven)
|
Protocol Extension
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| protocol Greetable {
var name: String { get }
}
extension Greetable {
func greet() {
print("Hello, \(name)")
}
}
struct User: Greetable {
let name: String
}
let user = User(name: "Tawheed")
user.greet()
|
33. Generics
Generics allow reusable code for different types.
1
2
3
4
5
6
7
| func printValue<T>(_ value: T) {
print(value)
}
printValue("Hello")
printValue(100)
printValue(true)
|
Generic Struct
1
2
3
4
5
6
7
8
9
| struct Box<T> {
let value: T
}
let stringBox = Box(value: "Swift")
let intBox = Box(value: 100)
print(stringBox.value)
print(intBox.value)
|
Generic Constraint
1
2
3
4
5
6
| func compareValues<T: Equatable>(_ a: T, _ b: T) -> Bool {
return a == b
}
print(compareValues(10, 10))
print(compareValues("A", "B"))
|
Simple explanation:
1
| T: Equatable means T must support equality check.
|
34. Access Control
Access control controls visibility.
1
2
3
4
5
| open = accessible and subclassable outside module
public = accessible outside module
internal = accessible inside module, default
fileprivate = accessible inside same file
private = accessible inside same scope/type
|
Example:
1
2
3
4
5
6
7
| public class ApiService {
private let token = "secret"
public func fetchData() {
print("Fetching data")
}
}
|
Best practice:
1
2
| Keep properties private when possible.
Expose only what is needed.
|
35. Type Casting
Type casting checks or converts object type.
1
2
3
4
5
6
7
8
9
10
11
12
| class Animal {}
class Dog: Animal {
func bark() {
print("Bark")
}
}
let animal: Animal = Dog()
if let dog = animal as? Dog {
dog.bark()
}
|
is Operator
1
2
3
| if animal is Dog {
print("Animal is Dog")
}
|
as? vs as!
1
2
| let dog1 = animal as? Dog // Safe optional cast
let dog2 = animal as! Dog // Force cast, can crash
|
Use as? more often.
36. Nested Types
Nested type means defining a type inside another type.
1
2
3
4
5
6
7
8
9
10
11
| struct User {
enum Role {
case admin
case customer
}
let name: String
let role: Role
}
let user = User(name: "Tawheed", role: .admin)
|
37. Error Handling
Swift handles errors with throws, do, try, and catch.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| enum LoginError: Error {
case invalidEmail
case invalidPassword
}
func login(email: String, password: String) throws {
if !email.contains("@") {
throw LoginError.invalidEmail
}
if password.count < 6 {
throw LoginError.invalidPassword
}
print("Login successful")
}
do {
try login(email: "test@example.com", password: "123456")
} catch {
print("Login failed: \(error)")
}
|
try, try?, try!
1
2
3
| try = normal error handling
try? = converts result to optional
try! = force try, crashes if error happens
|
Example:
1
| let result = try? login(email: "wrong", password: "123")
|
Avoid:
unless you are completely sure no error will happen.
38. Result Type
Result represents success or failure.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| enum NetworkError: Error {
case invalidResponse
}
func fetchData() -> Result<String, NetworkError> {
return .success("Data loaded")
}
let result = fetchData()
switch result {
case .success(let data):
print(data)
case .failure(let error):
print(error)
}
|
Use Result when:
1
2
| A function can succeed or fail
You want to return success/failure clearly
|
39. Assertions and Preconditions
Assertions help catch problems during development.
1
2
3
| let age = 20
assert(age >= 0, "Age cannot be negative")
|
Precondition checks important conditions.
1
| precondition(age >= 0, "Invalid age")
|
Simple difference:
1
2
| assert = mainly for debugging
precondition = important runtime condition
|
40. Memory Management and ARC
Swift uses ARC: Automatic Reference Counting.
ARC automatically removes class instances from memory when they are no longer used.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| class User {
let name: String
init(name: String) {
self.name = name
print("\(name) created")
}
deinit {
print("\(name) removed")
}
}
var user: User? = User(name: "Tawheed")
user = nil
|
Strong Reference
Strong reference keeps an object alive.
1
2
3
4
| class User {}
var user1: User? = User()
var user2 = user1
|
The object remains alive while strong references exist.
Retain Cycle
Retain cycle happens when two objects strongly reference each other.
Bad example:
1
2
3
4
5
6
7
| class User {
var profile: Profile?
}
class Profile {
var user: User?
}
|
Both objects keep each other alive.
weak Reference
Use weak to avoid retain cycle.
1
2
3
4
5
6
7
| class User {
var profile: Profile?
}
class Profile {
weak var user: User?
}
|
weak must be optional because it can become nil.
unowned Reference
Use unowned when reference should never become nil while used.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| class Customer {
var card: CreditCard?
deinit {
print("Customer removed")
}
}
class CreditCard {
unowned let customer: Customer
init(customer: Customer) {
self.customer = customer
}
}
|
Use carefully.
weak self in Closure
1
2
3
4
5
6
7
8
9
10
11
12
13
| class DownloadManager {
var onComplete: (() -> Void)?
func start() {
onComplete = { [weak self] in
self?.showSuccess()
}
}
func showSuccess() {
print("Download completed")
}
}
|
This prevents closure retain cycle.
41. Concurrency
Concurrency means doing multiple tasks without blocking the app.
Use cases:
1
2
3
4
5
| API call
Image loading
File processing
Database query
Background calculation
|
async Function
1
2
3
| func fetchUserName() async -> String {
return "Tawheed"
}
|
await
1
2
3
4
| Task {
let name = await fetchUserName()
print(name)
}
|
Simple explanation:
1
2
| async marks a function as asynchronous.
await waits for async result.
|
Task
Task starts asynchronous work.
1
2
3
| Task {
print("Async task started")
}
|
MainActor
Use MainActor for UI-related updates.
1
2
3
4
5
6
7
8
| @MainActor
class UserViewModel: ObservableObject {
@Published var username = "Guest"
func updateName() {
username = "Tawheed"
}
}
|
Simple explanation:
1
2
| UI updates should happen on main thread.
MainActor helps ensure that.
|
Task.detached
Detached task runs independently.
1
2
3
4
5
6
7
8
| Task.detached {
let result = heavyCalculation()
print(result)
}
func heavyCalculation() -> Int {
return (1...1000).reduce(0, +)
}
|
Use carefully because detached tasks do not inherit parent context.
async let
async let runs async work in parallel.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| func fetchName() async -> String {
return "Tawheed"
}
func fetchAge() async -> Int {
return 25
}
Task {
async let name = fetchName()
async let age = fetchAge()
let result = await "\(name), \(age)"
print(result)
}
|
Actor
Actor protects shared mutable state.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| actor Counter {
private var value = 0
func increase() {
value += 1
}
func getValue() -> Int {
return value
}
}
let counter = Counter()
Task {
await counter.increase()
let value = await counter.getValue()
print(value)
}
|
Simple explanation:
1
| Actor helps prevent data race.
|
42. Protocol-Oriented Programming
Swift strongly supports protocol-oriented programming.
Instead of depending on concrete classes, depend on protocols.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| protocol PaymentService {
func pay(amount: Double)
}
class StripePaymentService: PaymentService {
func pay(amount: Double) {
print("Paid \(amount) using Stripe")
}
}
class CheckoutViewModel {
private let paymentService: PaymentService
init(paymentService: PaymentService) {
self.paymentService = paymentService
}
func checkout() {
paymentService.pay(amount: 19.99)
}
}
|
Benefits:
1
2
3
4
| Cleaner architecture
Easier testing
Less coupling
Flexible implementation
|
43. Opaque Types
Opaque type hides the exact return type but keeps type safety.
1
2
3
4
5
6
7
8
9
10
11
12
13
| protocol Shape {
func draw()
}
struct Circle: Shape {
func draw() {
print("Drawing circle")
}
}
func makeShape() -> some Shape {
return Circle()
}
|
Simple explanation:
1
2
| some Shape means this function returns one specific type that conforms to Shape,
but caller does not need to know the exact type.
|
Common in SwiftUI:
1
2
3
| var body: some View {
Text("Hello")
}
|
44. Any Type
Any can store any type.
1
2
3
4
5
| let values: [Any] = [1, "Swift", true, 9.99]
for value in values {
print(value)
}
|
Use carefully.
1
2
| Any removes type safety.
Use specific types when possible.
|
45. AnyObject
AnyObject represents any class instance.
1
2
3
| class User {}
let object: AnyObject = User()
|
Simple explanation:
1
2
| Any = any type
AnyObject = any class type
|
46. Property Wrappers
Property wrappers add behavior to properties.
Simple example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| @propertyWrapper
struct Capitalized {
private var value: String = ""
var wrappedValue: String {
get {
value
}
set {
value = newValue.capitalized
}
}
}
struct User {
@Capitalized var name: String
}
var user = User()
user.name = "Tawheed"
print(user.name)
|
Simple explanation:
1
| Property wrapper wraps a property and controls how it stores or returns value.
|
Common SwiftUI property wrappers:
1
2
3
4
5
6
7
| @State
@Binding
@ObservedObject
@StateObject
@Environment
@EnvironmentObject
@AppStorage
|
47. Result Builders
Result builders help build complex nested values.
SwiftUI uses result builders to build views.
Simple concept:
1
2
3
4
5
6
7
8
9
10
| import SwiftUI
@ViewBuilder
func makeView(isLoggedIn: Bool) -> some View {
if isLoggedIn {
Text("Home")
} else {
Text("Login")
}
}
|
Simple explanation:
1
2
| @ViewBuilder allows multiple view expressions
to be combined into one result.
|
48. Pattern Matching
Pattern matching is commonly used with switch.
1
2
3
4
5
6
7
8
9
10
| let value = 10
switch value {
case 0:
print("Zero")
case 1...10:
print("Between 1 and 10")
default:
print("Other")
}
|
Pattern Matching with Tuple
1
2
3
4
5
6
7
8
9
10
11
12
| let point = (x: 0, y: 5)
switch point {
case (0, 0):
print("Origin")
case (0, _):
print("On Y axis")
case (_, 0):
print("On X axis")
default:
print("Somewhere else")
}
|
Pattern Matching with Enum
1
2
3
4
5
6
7
8
9
10
11
12
13
| enum ApiResult {
case success(String)
case failure(String)
}
let result = ApiResult.success("Loaded")
switch result {
case .success(let data):
print(data)
case .failure(let message):
print(message)
}
|
49. Where Clause
where adds extra condition.
1
2
3
4
5
6
7
8
| let number = 10
switch number {
case let x where x % 2 == 0:
print("Even number")
default:
print("Odd number")
}
|
50. Higher-Order Functions
Higher-order functions take closures.
Common examples:
1
2
3
4
5
6
7
| map
filter
reduce
sorted
compactMap
flatMap
forEach
|
map
Transforms values.
1
2
3
4
5
| let numbers = [1, 2, 3]
let doubled = numbers.map { $0 * 2 }
print(doubled)
|
filter
Filters values.
1
2
3
4
5
| let numbers = [1, 2, 3, 4, 5]
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers)
|
reduce
Combines values into one result.
1
2
3
4
5
6
7
| let numbers = [1, 2, 3, 4]
let total = numbers.reduce(0) { result, number in
result + number
}
print(total)
|
Short form:
1
| let total = numbers.reduce(0, +)
|
compactMap
Removes nil values and unwraps optionals.
1
2
3
4
5
| let texts = ["1", "2", "abc", "4"]
let numbers = texts.compactMap { Int($0) }
print(numbers)
|
Output:
sorted
1
2
3
4
5
| let numbers = [3, 1, 4, 2]
let sortedNumbers = numbers.sorted { $0 < $1 }
print(sortedNumbers)
|
51. Codable
Codable is used for JSON encoding and decoding.
1
2
3
4
| struct User: Codable {
let id: Int
let name: String
}
|
Decode JSON
1
2
3
4
5
6
7
8
9
10
| let json = """
{
"id": 1,
"name": "Tawheed"
}
""".data(using: .utf8)!
let user = try JSONDecoder().decode(User.self, from: json)
print(user.name)
|
Encode JSON
1
2
3
4
5
| let user = User(id: 1, name: "Tawheed")
let data = try JSONEncoder().encode(user)
print(String(data: data, encoding: .utf8) ?? "")
|
Custom CodingKey
1
2
3
4
5
6
7
8
9
| struct User: Codable {
let userId: Int
let userName: String
enum CodingKeys: String, CodingKey {
case userId = "user_id"
case userName = "user_name"
}
}
|
Simple explanation:
1
| Use CodingKeys when JSON key and Swift property name are different.
|
52. Date Handling
1
2
3
4
5
| import Foundation
let now = Date()
print(now)
|
1
2
3
4
5
6
7
8
| import Foundation
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let dateText = formatter.string(from: Date())
print(dateText)
|
Convert String to Date
1
2
3
4
5
6
| let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let date = formatter.date(from: "2026-05-20")
print(date as Any)
|
53. Accessing Files
Use FileManager.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| import Foundation
func saveText() {
let text = "Hello Swift"
let url = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
)[0].appendingPathComponent("note.txt")
try? text.write(to: url, atomically: true, encoding: .utf8)
print(url)
}
|
Read File
1
2
3
4
5
6
7
8
9
10
| func readText() {
let url = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
)[0].appendingPathComponent("note.txt")
let text = try? String(contentsOf: url, encoding: .utf8)
print(text ?? "")
}
|
54. Key Paths
Key paths reference properties.
1
2
3
4
5
6
7
8
9
10
11
12
13
| struct User {
let name: String
let age: Int
}
let users = [
User(name: "John", age: 25),
User(name: "Sarah", age: 30)
]
let names = users.map(\.name)
print(names)
|
Simple explanation:
1
| \.name means access name property from each user.
|
55. Equatable
Equatable allows checking equality.
1
2
3
4
5
6
7
8
9
| struct User: Equatable {
let id: Int
let name: String
}
let user1 = User(id: 1, name: "John")
let user2 = User(id: 1, name: "John")
print(user1 == user2)
|
56. Comparable
Comparable allows sorting custom types.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| struct User: Comparable {
let age: Int
static func < (lhs: User, rhs: User) -> Bool {
return lhs.age < rhs.age
}
}
let users = [
User(age: 30),
User(age: 20),
User(age: 25)
]
print(users.sorted())
|
57. Hashable
Hashable allows using custom types in Set or Dictionary keys.
1
2
3
4
5
6
7
8
9
10
11
| struct User: Hashable {
let id: Int
let name: String
}
let users: Set<User> = [
User(id: 1, name: "John"),
User(id: 1, name: "John")
]
print(users.count)
|
58. Custom Operators
Swift allows custom operators.
1
2
3
4
5
6
7
| infix operator **
func ** (lhs: Int, rhs: Int) -> Int {
return Int(pow(Double(lhs), Double(rhs)))
}
print(2 ** 3)
|
Use carefully.
1
| Custom operators can make code confusing if overused.
|
59. Macros
Macros generate code at compile time.
Simple concept:
1
2
| Macro helps reduce repeated code.
It expands during compilation.
|
Example of common Swift attribute-style usage:
1
2
3
4
| @Observable
class UserViewModel {
var name = "Guest"
}
|
Simple explanation:
1
2
3
| Macros are advanced.
You do not need to master them first.
Understand the concept before writing custom macros.
|
60. Swift Package Manager
Swift Package Manager is used to manage Swift dependencies.
Package file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // Package.swift
import PackageDescription
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"]
)
],
targets: [
.target(
name: "MyPackage"
),
.testTarget(
name: "MyPackageTests",
dependencies: ["MyPackage"]
)
]
)
|
Simple explanation:
1
2
| Swift Package Manager helps create reusable libraries
and add third-party packages.
|
61. Modules and Import
A module is a unit of code that can be imported.
1
2
| import Foundation
import SwiftUI
|
Examples:
1
2
3
4
| Foundation = dates, files, networking, collections utilities
SwiftUI = UI framework
UIKit = older iOS UI framework
Combine = reactive framework
|
62. Availability Check
Use availability check for OS-specific code.
1
2
3
4
5
| if #available(iOS 17.0, *) {
print("Use iOS 17 feature")
} else {
print("Fallback for older iOS")
}
|
63. Compiler Directives
Compiler directives help run code conditionally.
1
2
3
4
5
| #if DEBUG
print("Debug mode")
#else
print("Release mode")
#endif
|
Documentation comments help explain code.
1
2
3
4
5
6
7
8
| /// Adds two integer numbers.
/// - Parameters:
/// - a: First number.
/// - b: Second number.
/// - Returns: Sum of two numbers.
func add(a: Int, b: Int) -> Int {
return a + b
}
|
65. Naming Convention
Swift naming should be clear and readable.
Good:
1
2
3
| func calculateTotalPrice() {}
let userName = "Tawheed"
let isPremiumUser = true
|
Bad:
1
2
3
| func calc() {}
let usr = "Tawheed"
let flag = true
|
Best practice:
1
2
3
| Use clear names.
Boolean should start with is, has, can, should.
Function names should describe action.
|
66. Basic Swift Coding Style
Good style:
1
2
3
4
5
6
7
8
| struct User {
let id: Int
let name: String
func greet() {
print("Hello, \(name)")
}
}
|
Key rules:
1
2
3
4
5
6
7
| Use let by default
Avoid force unwrap
Keep functions small
Use meaningful names
Use guard for early return
Use protocols for abstraction
Keep code readable
|
67. Mini Practice Examples
Reverse String
1
2
3
4
5
| func reverseString(_ text: String) -> String {
return String(text.reversed())
}
print(reverseString("Swift"))
|
Check Even Number
1
2
3
4
5
| func isEven(_ number: Int) -> Bool {
return number % 2 == 0
}
print(isEven(10))
|
Find Maximum Number
1
2
3
4
5
| func findMax(_ numbers: [Int]) -> Int? {
return numbers.max()
}
print(findMax([10, 4, 20, 7]) ?? 0)
|
Remove Duplicates
1
2
3
4
5
| func removeDuplicates(_ numbers: [Int]) -> [Int] {
return Array(Set(numbers))
}
print(removeDuplicates([1, 2, 2, 3, 4, 4]))
|
Keep order:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| func removeDuplicatesKeepingOrder(_ numbers: [Int]) -> [Int] {
var seen = Set<Int>()
var result: [Int] = []
for number in numbers {
if !seen.contains(number) {
seen.insert(number)
result.append(number)
}
}
return result
}
print(removeDuplicatesKeepingOrder([1, 2, 2, 3, 4, 4]))
|
Count Character Frequency
1
2
3
4
5
6
7
8
9
10
11
| func characterCount(_ text: String) -> [Character: Int] {
var result: [Character: Int] = [:]
for char in text {
result[char, default: 0] += 1
}
return result
}
print(characterCount("hello"))
|
68. Swift Learning Order
Follow this order:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| 1. Variables and constants
2. Basic data types
3. Type inference
4. Operators
5. Strings
6. Arrays
7. Sets
8. Dictionaries
9. Optionals
10. Tuples
11. Control flow
12. Functions
13. Closures
14. Enums
15. Structs
16. Classes
17. Properties
18. Methods
19. Initializers
20. Protocols
21. Extensions
22. Generics
23. Error handling
24. ARC and memory management
25. Higher-order functions
26. Codable
27. Access control
28. Type casting
29. Concurrency
30. Actors
31. Result builders
32. Property wrappers
33. Macros
34. Swift Package Manager
35. Clean coding style
|
Key Takeaways
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| Use let by default.
Use var only when value changes.
Swift is strongly typed and safe.
Optionals prevent null-related crashes.
Avoid force unwrapping.
Use guard let for early exits.
Use structs for models and value data.
Use classes when reference behavior is needed.
Use protocols for abstraction and testability.
Use extensions to organize code.
Use generics for reusable code.
Use Codable for JSON.
Use async/await for modern asynchronous programming.
Use actors to protect shared state.
Use weak self to avoid closure retain cycles.
Keep code clean, readable, and simple.
|