Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
141: Objective-C Interop Bridging Rules
You're going to run into this eventually: you're working on a modern Swift codebase, but there's a piece of "legacy" logic—maybe a complex calculation engine or a specialized storage wrapper—written in Objective-C that works perfectly. Rewriting it is a waste of time, so we bridge it. But bridging isn't magic; it's a set of rules that determine how types on one side of the fence map to types on the other.
Defining our legacy storage class
Let's imagine we have a class called LegacyUserPrefs. It handles some old-school plist persistence. I'll start by writing the Objective-C header. I want to track a username and a favorite color, and maybe a version number.
// LegacyUserPrefs.h
#import <Foundation/Foundation.h>
@interface LegacyUserPrefs : NSObject
@property (nonatomic, copy) NSString *username;
@property (nonatomic, copy) NSString *favoriteColor;
@property (nonatomic, assign) NSInteger version;
- (void)updateUsername:(NSString *)newName;
- (NSString *)getFormattedPreference:(NSString *)key;
@end
At first glance, this looks fine. But if we just import this into Swift, we're going to run into the "Optionality Problem." Objective-C doesn't have a native concept of non-optionals like Swift does; every object pointer can be nil. Swift, being type-safe, needs to know if these properties can be null or not.
Opening the door with the Bridging Header
To make LegacyUserPrefs visible to Swift, I need a bridging header. If you're using Xcode, it usually asks to create one for you when you add an Objective-C file to a Swift project. If not, you create a ProjectName-Bridging-Header.h file and point to it in your Build Settings.
Inside that header, I just add the import:
#import "LegacyUserPrefs.h"
Now, the Swift compiler scans that header and "translates" the Objective-C declarations into Swift signatures. This is where the bridging rules kick in. NSString becomes String, and NSInteger becomes Int.
The Optionality Surprise
I tried to use the class in my Swift code immediately, and this is where I tripped up. I wrote this:
let prefs = LegacyUserPrefs()
print("Welcome back, \(prefs.username)!") // ❌ Error: Value of optional type 'String?' must be unwrapped
I forgot that because I didn't specify nullability in my Objective-C header, Swift defaults everything to Optional. Since username is an NSString *, Swift sees it as String?. I don't want to be force-unwrapping or optional-binding every single property from this legacy class if I know they'll always have a value.
I need to go back to the Objective-C header and use nonnull and nullable annotations. This is the "modern" way to write Objective-C for Swift interop.
// LegacyUserPrefs.h (Corrected)
#import <Foundation/Foundation.h>
@interface LegacyUserPrefs : NSObject
@property (nonatomic, copy, nonnull) NSString *username; // Swift: String
@property (nonatomic, copy, nullable) NSString *favoriteColor; // Swift: String?
@property (nonatomic, assign) NSInteger version; // Swift: Int (Primitives aren't optional)
- (void)updateUsername:(nonnull NSString *)newName;
- (nullable NSString *)getFormattedPreference:(nonnull NSString *)key;
@end
By adding nonnull to the username, I've told the Swift compiler: "Trust me, this will never be nil." Now, when I go back to Swift, the error vanishes. prefs.username is now a regular String.
Finalizing the Swift integration
Now that the bridging rules are working in our favor, the usage feels native. I can pass Swift strings directly into the Objective-C methods, and Swift handles the conversion to NSString behind the scenes.
let prefs = LegacyUserPrefs()
prefs.username = "DevMentor" // No more optionals here
prefs.favoriteColor = "Midnight Blue" // This is still String?
// This method returns a nullable NSString, so Swift makes it String?
if let formatted = prefs.getFormattedPreference("theme") {
print("Your theme is \(formatted)")
} else {
print("No theme set.")
}
One quick tip: if you ever see NSUInteger or NSInteger, they always bridge to UInt and Int. You don't need to worry about nullability for those because they are scalars, not objects. They can't be nil in Objective-C, so they can't be Optional in Swift.
📋 Practical Task
Bridging the NetworkLogger Utility
You have been handed a legacy Objective-C utility class called NetworkLogger. Currently, it is bridging to Swift, but it's creating "Optional Hell" because the original author didn't use nullability annotations. Every single property and method return value is appearing as an Optional in Swift, even though the documentation says they are guaranteed to exist.
Your task: Modify the NetworkLogger.h file provided below to use nonnull and nullable annotations so that the Swift code below it compiles without requiring force-unwraps (!) or optional binding (if let) for the guaranteed values.
// NetworkLogger.h
#import <Foundation/Foundation.h>
@interface NetworkLogger : NSObject
@property (nonatomic, copy) NSString *logDirectory; // Guaranteed to exist
@property (nonatomic, copy) NSString *lastErrorMessage; // Can be nil if no error occurred
@property (nonatomic, assign) NSInteger totalRequests; // Scalar
- (void)logRequest:(NSString *)url; // URL is guaranteed
- (NSString *)getLastLogEntry; // Can be nil if log is empty
@end
Requirement: Ensure that in Swift, logDirectory is a String, lastErrorMessage is a String?, the logRequest argument is a String, and getLastLogEntry returns a String?.
There are no comments for now.