xiaohongshu
xiaohongshu
convert-objc-swift
Convert Objective-C code to idiomatic Swift. Use when migrating Objective-C projects to Swift, translating Objective-C patterns to modern Swift, modernizing legacy codebases, or establishing gradual migration strategies with interop. Extends meta-convert-dev with Objective-C-to-Swift specific pat...
Full skill instructions
Convert Objective-C code to idiomatic Swift. This skill extends meta-convert-dev with Objective-C-to-Swift specific type mappings, idiom translations, interoperability patterns, and tooling for gradual migration.
meta-convert-dev - Foundational conversion patterns (APTV workflow, testing strategies)For general concepts like the Analyze → Plan → Transform → Validate workflow, testing strategies, and common pitfalls, see the meta-skill first.
meta-convert-devlang-objc-devlang-swift-devconvert-swift-objc| Objective-C | Swift | Notes |
|---|---|---|
NSString * | String | Swift String is value type |
NSInteger | Int | Platform-dependent signed integer |
BOOL | Bool | Direct mapping |
NSArray * | [Any] / [Element] | Generic array with type safety |
NSMutableArray * | [Element] | Use var for mutability |
NSDictionary * | [String: Any] / [Key: Value] | Generic dictionary |
id | Any / AnyObject | Type-erased reference |
id<Protocol> | any Protocol / some Protocol | Existential or opaque type |
nullable id | Optional<Any> / Any? | Explicit optionality |
instancetype | Self | Return type is class of receiver |
typedef void (^Block)() | () -> Void | Closure type |
@property (weak) | weak var | Weak reference |
@property (copy) | let (for strings) | Immutable by default |
[obj method:param] | obj.method(param) | Dot syntax |
id with specific types or generics| Objective-C | Swift | Notes |
|---|---|---|
BOOL | Bool | Direct mapping (true/false vs YES/NO) |
NSInteger | Int | Platform-dependent signed (32-bit or 64-bit) |
NSUInteger | UInt | Platform-dependent unsigned |
int | Int32 | Explicit 32-bit signed |
unsigned int | UInt32 | Explicit 32-bit unsigned |
long | Int (64-bit) / Int32 (32-bit) | Platform-dependent |
long long | Int64 | Explicit 64-bit signed |
float | Float | 32-bit floating point |
double | Double | 64-bit floating point (default) |
CGFloat | CGFloat | Platform-dependent float (bridged) |
char | CChar / Int8 | C char type |
unichar | UInt16 | Unicode character |
void | Void / () | Unit type |
instancetype | Self | Return type of current class |
| Objective-C | Swift | Notes |
|---|---|---|
NSString * | String | Bridged; Swift String is value type |
NSMutableString * | String (with var) | Swift strings mutable with var |
NSNumber * | NSNumber / specific type | Bridge to Int, Double, Bool when possible |
NSArray * | [Any] | Untyped array |
NSArray<Type *> * | [Type] | Generic array with type safety |
NSMutableArray * | [Element] (with var) | Mutable via var declaration |
NSDictionary * | [String: Any] | Untyped dictionary |
NSDictionary<K, V> * | [K: V] | Generic dictionary |
NSMutableDictionary * | [K: V] (with var) | Mutable via var |
NSSet * | Set<AnyHashable> | Untyped set |
NSSet<Type *> * | Set<Type> | Generic set |
NSData * | Data | Bridged value type |
NSDate * | Date | Bridged value type |
NSURL * | URL | Bridged value type |
NSError * | Error / NSError | Conforming to Error protocol |
| Objective-C | Swift | Notes |
|---|---|---|
id | Any? | Implicitly optional in Objective-C |
nullable id | Any? | Explicitly nullable |
nonnull id | Any | Non-optional |
_Nullable | ? | Optional |
_Nonnull | Non-optional | Default in Swift |
nil | nil | Same keyword, different semantics |
| Objective-C | Swift | Notes |
|---|---|---|
void (^)(void) | () -> Void | No parameters, no return |
NSInteger (^)(NSInteger) | (Int) -> Int | Single parameter |
void (^)(NSString *, NSError *) | (String, Error) -> Void | Multiple parameters |
typedef void (^Completion)() | typealias Completion = () -> Void | Type alias |
@escaping (implicit) | @escaping (explicit) | Escaping closures marked in Swift |
| Objective-C | Swift | Notes |
|---|---|---|
NSArray<T *> * | [T] | Generic array |
NSMutableArray<T *> * | var array: [T] | Mutable array |
NSDictionary<K, V> * | [K: V] | Generic dictionary |
NSMutableDictionary<K, V> * | var dict: [K: V] | Mutable dictionary |
NSSet<T *> * | Set<T> | Generic set |
NSMutableSet<T *> * | var set: Set<T> | Mutable set |
NSOrderedSet<T *> * | Custom (no direct equivalent) | Use array with uniqueness logic |
| Objective-C | Swift | Notes |
|---|---|---|
@interface Class : Super | class Class: Super | Class declaration |
@interface Class <Protocol> | class Class: Protocol | Protocol adoption |
@interface Class () (extension) | private extension Class | Class extension |
@interface Class (Category) | extension Class | Category → Extension |
@protocol Protocol | protocol Protocol | Protocol definition |
@property Type *prop | var prop: Type | Property |
@property (readonly) | let prop | Immutable property |
@property (weak) | weak var | Weak reference |
@property (copy) | let / var | Copy semantic (Swift strings already copy) |
enum TypeName | enum TypeName | Enumeration |
NS_ENUM(NSInteger, Name) | enum Name: Int | Integer-backed enum |
NS_OPTIONS(NSUInteger, Name) | struct Name: OptionSet | Option set |
typedef | typealias | Type alias |
struct | struct | Value type |
Objective-C:
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, weak) id<PersonDelegate> delegate;
@property (nonatomic, copy) NSString *bio;
@property (nonatomic, readonly) NSString *identifier;
@end
@implementation Person
@end
// Usage
Person *person = [[Person alloc] init];
person.name = @"Alice";
NSInteger age = person.age;
Swift:
class Person {
var name: String
var age: Int
weak var delegate: (any PersonDelegate)?
var bio: String // Swift strings already have value semantics
let identifier: String // readonly → let
init(name: String = "", age: Int = 0, identifier: String) {
self.name = name
self.age = age
self.identifier = identifier
self.bio = ""
}
}
// Usage
let person = Person(identifier: UUID().uuidString)
person.name = "Alice"
let age = person.age
Why this translation:
@property declarationstrong is default, weak explicit, copy unnecessary for value typeslet for immutable, var for mutableObjective-C:
@interface Calculator : NSObject
- (NSInteger)add:(NSInteger)a to:(NSInteger)b;
+ (instancetype)sharedCalculator;
- (void)performCalculation:(NSInteger)input
completion:(void (^)(NSInteger result, NSError *error))completion;
@end
@implementation Calculator
- (NSInteger)add:(NSInteger)a to:(NSInteger)b {
return a + b;
}
+ (instancetype)sharedCalculator {
static Calculator *shared = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shared = [[self alloc] init];
});
return shared;
}
@end
// Usage
Calculator *calc = [Calculator sharedCalculator];
NSInteger result = [calc add:5 to:10];
Swift:
class Calculator {
func add(_ a: Int, to b: Int) -> Int {
return a + b
}
static let shared = Calculator()
func performCalculation(
_ input: Int,
completion: @escaping (Result<Int, Error>) -> Void
) {
// Implementation
}
}
// Usage
let calc = Calculator.shared
let result = calc.add(5, to: 10)
Why this translation:
static let is thread-safe and simpler than dispatch_onceResult<T, E> over separate parameters@escaping must be explicit for closures that outlive function scopeObjective-C:
- (nullable NSString *)findUserName:(NSString *)userId {
User *user = [self.users objectForKey:userId];
return user ? user.name : nil;
}
// Usage
NSString *name = [self findUserName:@"123"];
if (name) {
NSLog(@"Name: %@", name);
} else {
NSLog(@"User not found");
}
// Nil messaging (safe)
NSString *upper = [name uppercaseString]; // Returns nil if name is nil
Swift:
func findUserName(_ userId: String) -> String? {
guard let user = users[userId] else {
return nil
}
return user.name
}
// Usage
if let name = findUserName("123") {
print("Name: \(name)")
} else {
print("User not found")
}
// Optional chaining
let upper = name?.uppercased() // nil if name is nil
// Nil coalescing
let displayName = findUserName("123") ?? "Unknown"
Why this translation:
if let / guard let for safe unwrapping?.) replaces Objective-C nil messaging??) provides defaults!) should be rare and justifiedObjective-C:
@protocol DataSourceDelegate <NSObject>
@required
- (NSInteger)numberOfItems;
@optional
- (void)didSelectItemAtIndex:(NSInteger)index;
@end
@interface DataSource : NSObject
@property (nonatomic, weak) id<DataSourceDelegate> delegate;
@end
@implementation DataSource
- (void)loadData {
if ([self.delegate respondsToSelector:@selector(numberOfItems)]) {
NSInteger count = [self.delegate numberOfItems];
}
if ([self.delegate respondsToSelector:@selector(didSelectItemAtIndex:)]) {
[self.delegate didSelectItemAtIndex:0];
}
}
@end
Swift:
protocol DataSourceDelegate: AnyObject {
func numberOfItems() -> Int
func didSelectItem(at index: Int) // Optional methods not directly supported
}
// For optional protocol methods, use separate protocols or default implementations
extension DataSourceDelegate {
func didSelectItem(at index: Int) {
// Default implementation (makes it optional)
}
}
class DataSource {
weak var delegate: (any DataSourceDelegate)?
func loadData() {
guard let delegate = delegate else { return }
let count = delegate.numberOfItems()
delegate.didSelectItem(at: 0) // Safe to call due to default implementation
}
}
Why this translation:
AnyObject for class-only protocols (enables weak references)@required/@optional - use protocol extensions for default implementationsany Protocol for existential types (heterogeneous collections)Objective-C:
typedef NS_ENUM(NSInteger, HTTPStatus) {
HTTPStatusOK = 200,
HTTPStatusNotFound = 404,
HTTPStatusServerError = 500
};
typedef NS_OPTIONS(NSUInteger, FilePermissions) {
FilePermissionsRead = 1 << 0,
FilePermissionsWrite = 1 << 1,
FilePermissionsExecute = 1 << 2
};
// Usage
HTTPStatus status = HTTPStatusOK;
FilePermissions perms = FilePermissionsRead | FilePermissionsWrite;
Swift:
enum HTTPStatus: Int {
case ok = 200
case notFound = 404
case serverError = 500
}
struct FilePermissions: OptionSet {
let rawValue: UInt
static let read = FilePermissions(rawValue: 1 << 0)
static let write = FilePermissions(rawValue: 1 << 1)
static let execute = FilePermissions(rawValue: 1 << 2)
}
// Usage
let status = HTTPStatus.ok
let perms: FilePermissions = [.read, .write]
// Pattern matching
switch status {
case .ok:
print("Success")
case .notFound:
print("Not found")
case .serverError:
print("Server error")
}
Why this translation:
NS_ENUM → Swift enum with raw valueNS_OPTIONS → OptionSet struct for bitwise optionsObjective-C:
- (BOOL)loadDataFromFile:(NSString *)path error:(NSError **)error {
NSData *data = [NSData dataWithContentsOfFile:path options:0 error:error];
if (!data) {
return NO;
}
// Process data
return YES;
}
// Creating custom errors
- (BOOL)validateUser:(User *)user error:(NSError **)error {
if (user.name.length == 0) {
if (error) {
*error = [NSError errorWithDomain:@"com.example.validation"
code:100
userInfo:@{
NSLocalizedDescriptionKey: @"Name is required"
}];
}
return NO;
}
return YES;
}
// Usage
NSError *error = nil;
BOOL success = [self loadDataFromFile:@"data.txt" error:&error];
if (!success) {
NSLog(@"Error: %@", error.localizedDescription);
}
Swift:
enum ValidationError: Error {
case nameRequired
case invalidFormat(String)
}
func loadData(from path: String) throws -> Data {
return try Data(contentsOfFile: path)
}
func validateUser(_ user: User) throws {
guard !user.name.isEmpty else {
throw ValidationError.nameRequired
}
}
// Usage with do-catch
do {
let data = try loadData(from: "data.txt")
// Process data
} catch {
print("Error: \(error.localizedDescription)")
}
// Usage with try?
if let data = try? loadData(from: "data.txt") {
// Process data
}
// Usage with Result
func loadDataResult(from path: String) -> Result<Data, Error> {
Result { try Data(contentsOfFile: path) }
}
Why this translation:
throws) instead of error pointersError protocol (usually enums)do-catch for error handlingtry? for optional conversionResult<T, E> for explicit error valuesObjective-C:
// NSString+Validation.h
@interface NSString (Validation)
- (BOOL)isValidEmail;
@end
// NSString+Validation.m
@implementation NSString (Validation)
- (BOOL)isValidEmail {
NSString *pattern = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", pattern];
return [predicate evaluateWithObject:self];
}
@end
// Usage
NSString *email = @"[email protected]";
if ([email isValidEmail]) {
NSLog(@"Valid email");
}
Swift:
extension String {
var isValidEmail: Bool {
let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let predicate = NSPredicate(format: "SELF MATCHES %@", pattern)
return predicate.evaluate(with: self)
}
}
// Usage
let email = "[email protected]"
if email.isValidEmail {
print("Valid email")
}
Why this translation:
Objective-C:
typedef void (^CompletionBlock)(NSData *data, NSError *error);
@interface NetworkManager : NSObject
- (void)fetchDataWithCompletion:(CompletionBlock)completion;
@end
@implementation NetworkManager
- (void)fetchDataWithCompletion:(CompletionBlock)completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [self loadData];
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(data, nil);
}
});
});
}
// Avoiding retain cycles
- (void)setupCompletion {
__weak typeof(self) weakSelf = self;
self.completion = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSomething];
}
};
}
@end
Swift:
typealias CompletionBlock = (Result<Data, Error>) -> Void
class NetworkManager {
func fetchData(completion: @escaping CompletionBlock) {
DispatchQueue.global().async {
let data = self.loadData()
DispatchQueue.main.async {
completion(.success(data))
}
}
}
// Modern async/await (preferred)
func fetchData() async throws -> Data {
return try await withCheckedThrowingContinuation { continuation in
fetchData { result in
continuation.resume(with: result)
}
}
}
// Avoiding retain cycles
var completion: (() -> Void)?
func setupCompletion() {
completion = { [weak self] in
guard let self = self else { return }
self.doSomething()
}
}
}
Why this translation:
@escaping required for stored closuresResult<T, E> over separate data/error parameters[weak self] / guard let self pattern cleaner than weak-strong danceObjective-C to Swift interoperability is exceptional, enabling gradual migration - the primary migration strategy for large codebases. You can mix Objective-C and Swift in the same project, calling code bidirectionally.
Unlike most language conversions, Objective-C → Swift supports:
┌─────────────────────────────────────────────────────────────┐
│ OBJECTIVE-C → SWIFT MIGRATION │
├─────────────────────────────────────────────────────────────┤
│ Phase 1: SETUP │
│ • Enable "Use Swift" in Xcode project │
│ • Create bridging header (auto-generated) │
│ • Import essential Objective-C headers │
├─────────────────────────────────────────────────────────────┤
│ Phase 2: IDENTIFY │
│ • Start with leaf classes (fewest dependencies) │
│ • Identify pure data models (easy to convert) │
│ • Map protocols and categories │
├─────────────────────────────────────────────────────────────┤
│ Phase 3: CONVERT │
│ • Convert one class at a time │
│ • Add @objc attribute to maintain Objective-C visibility │
│ • Test after each conversion │
├─────────────────────────────────────────────────────────────┤
│ Phase 4: MODERNIZE │
│ • Remove @objc where not needed │
│ • Adopt Swift-only features (optionals, value types) │
│ • Refactor to protocol-oriented design │
├─────────────────────────────────────────────────────────────┤
│ Phase 5: COMPLETE │
│ • Remove bridging header when all Objective-C gone │
│ • Pure Swift codebase │
└─────────────────────────────────────────────────────────────┘
When you add the first Swift file to an Objective-C project, Xcode creates a bridging header.
ProjectName-Bridging-Header.h:
// Import Objective-C headers to expose them to Swift
#import "MyObjectiveCClass.h"
#import "DataModel.h"
#import "NetworkManager.h"
// Framework imports
#import <SomeFramework/SomeFramework.h>
Rules:
Objective-C class:
// Person.h
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (void)greet;
@end
// Person.m
@implementation Person
- (void)greet {
NSLog(@"Hello, I'm %@", self.name);
}
@end
Import in bridging header:
// ProjectName-Bridging-Header.h
#import "Person.h"
Use from Swift:
// No import needed - bridging header makes it available
let person = Person()
person.name = "Alice"
person.age = 30
person.greet()
Swift class with @objc:
// User.swift
@objc class User: NSObject {
@objc var username: String
@objc var email: String
@objc init(username: String, email: String) {
self.username = username
self.email = email
super.init()
}
@objc func validate() -> Bool {
return !username.isEmpty && !email.isEmpty
}
}
Use from Objective-C:
// Import generated header
#import "ProjectName-Swift.h"
// Use Swift class
User *user = [[User alloc] initWithUsername:@"alice" email:@"[email protected]"];
BOOL isValid = [user validate];
Key points:
@objc attribute exposes Swift declarations to Objective-CProjectName-Swift.h header| Attribute | Purpose | Example |
|---|---|---|
@objc | Expose to Objective-C runtime | @objc func method() |
@objc(name) | Custom Objective-C name | @objc(customName) func swiftName() |
@objcMembers | Expose all members of class | @objcMembers class MyClass |
@nonobjc | Hide from Objective-C | @nonobjc func swiftOnly() |
@IBOutlet | Interface Builder outlet | @IBOutlet weak var label: UILabel! |
@IBAction | Interface Builder action | @IBAction func buttonTapped() |
Foundation types bridge automatically between Objective-C and Swift:
| Objective-C | Swift | Bridging |
|---|---|---|
NSString | String | Automatic, toll-free |
NSArray | [Any] | Automatic |
NSDictionary | [AnyHashable: Any] | Automatic |
NSSet | Set<AnyHashable> | Automatic |
NSNumber | Int, Double, Bool | Automatic (context-dependent) |
NSData | Data | Automatic |
NSDate | Date | Automatic |
NSURL | URL | Automatic |
NSError | Error | Automatic (protocol conformance) |
Bridging is free - no performance cost for toll-free bridging.
Objective-C protocol:
@protocol Drawable <NSObject>
- (void)draw;
@optional
- (void)setColor:(UIColor *)color;
@end
Use from Swift:
// Conforms to Objective-C protocol
class Circle: NSObject, Drawable {
func draw() {
print("Drawing circle")
}
func setColor(_ color: UIColor) {
// Optional method
}
}
Swift protocol exposed to Objective-C:
@objc protocol Vehicle {
@objc var speed: Double { get }
@objc func accelerate()
@objc optional func honk() // Optional methods need @objc protocol
}
Objective-C base class:
@interface Animal : NSObject
@property (nonatomic, strong) NSString *name;
- (void)makeSound;
@end
Swift subclass:
class Dog: Animal {
var breed: String = ""
override func makeSound() {
print("\(name ?? "") barks!")
}
func wagTail() {
print("Wagging tail")
}
}
You can add Objective-C categories to Swift classes:
Swift class:
@objc class Calculator: NSObject {
@objc func add(_ a: Int, to b: Int) -> Int {
return a + b
}
}
Objective-C category:
@interface Calculator (Advanced)
- (NSInteger)multiply:(NSInteger)a by:(NSInteger)b;
@end
@implementation Calculator (Advanced)
- (NSInteger)multiply:(NSInteger)a by:(NSInteger)b {
return a * b;
}
@end
Objective-C dynamic behavior:
SEL selector = @selector(greet);
if ([person respondsToSelector:selector]) {
[person performSelector:selector];
}
Swift equivalent:
// Using @objc and Selector
@objc class Person: NSObject {
@objc func greet() {
print("Hello")
}
}
let selector = #selector(Person.greet)
if person.responds(to: selector) {
person.perform(selector)
}
// Modern Swift: prefer protocols and type safety
protocol Greeter {
func greet()
}
if let greeter = person as? Greeter {
greeter.greet()
}
Swift features NOT available in Objective-C:
| Swift Feature | Workaround |
|---|---|
| Generics | Use specific types or id |
| Protocols with associated types | Use type-erased wrappers |
| Tuples | Use structs or separate parameters |
| Enums with associated values | Use separate classes or NS_ENUM |
| Value types (struct) | Use classes (NSObject subclass) |
Optionals (except via _Nullable) | Use nullable annotations |
| Protocol extensions | Expose via concrete class |
Example: Type-erased wrapper for generic protocol:
// Swift generic protocol (not @objc compatible)
protocol Container {
associatedtype Element
func add(_ element: Element)
}
// Type-erased wrapper for Objective-C
@objc class AnyContainer: NSObject {
private let _add: (Any) -> Void
init<C: Container>(_ container: C) {
self._add = { element in
if let typedElement = element as? C.Element {
container.add(typedElement)
}
}
}
@objc func add(_ element: Any) {
_add(element)
}
}
Mixed language targets:
Bridging header (Objective-C → Swift):
ProjectName/ProjectName-Bridging-Header.hGenerated interface (Swift → Objective-C):
ProjectName-Swift.hModule name (for Swift imports):
ProjectName// XCTest works with both languages
class MixedTests: XCTestCase {
func testObjectiveCClass() {
let person = Person() // Objective-C class
person.name = "Alice"
XCTAssertEqual(person.name, "Alice")
}
func testSwiftClass() {
let user = User(username: "bob", email: "[email protected]")
XCTAssertTrue(user.validate())
}
}
From Objective-C tests:
@import XCTest;
#import "ProjectName-Swift.h"
@interface MixedTests : XCTestCase
@end
@implementation MixedTests
- (void)testSwiftClass {
User *user = [[User alloc] initWithUsername:@"alice" email:@"[email protected]"];
XCTAssertTrue([user validate]);
}
@end
Both languages use Automatic Reference Counting, but Swift adds value semantics.
| Objective-C | Swift | Notes |
|---|---|---|
@property (strong) | var (default) | Strong reference |
@property (weak) | weak var | Weak reference |
@property (copy) | let / var | Swift Strings/Arrays already have value semantics |
@property (assign) | var (value types) | Primitive types |
__weak | weak | Weak in closures |
__strong | Default | Strong in closures |
__unsafe_unretained | unowned(unsafe) | Rarely used |
Objective-C:
@interface Parent : NSObject
@property (strong) Child *child;
@end
@interface Child : NSObject
@property (weak) Parent *parent; // Weak to break cycle
@end
// Block cycle
__weak typeof(self) weakSelf = self;
self.completion = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doWork];
}
};
Swift:
class Parent {
var child: Child?
}
class Child {
weak var parent: Parent? // Weak to break cycle
}
// Closure cycle
completion = { [weak self] in
guard let self = self else { return }
self.doWork()
}
// Unowned (when you know reference always exists)
completion = { [unowned self] in
self.doWork() // Crashes if self is deallocated
}
Objective-C (reference types):
NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@"A", @"B", nil];
NSMutableArray *array2 = array1; // Same object
[array2 addObject:@"C"];
// array1 also contains "C"
Swift (value types):
var array1 = ["A", "B"]
var array2 = array1 // Copy on write
array2.append("C")
// array1 still ["A", "B"], array2 is ["A", "B", "C"]
Why this matters:
Objective-C:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSData *data = [self heavyComputation];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUIWithData:data];
});
});
Swift (GCD):
DispatchQueue.global().async {
let data = self.heavyComputation()
DispatchQueue.main.async {
self.updateUI(with: data)
}
}
Swift (modern async/await):
Task {
let data = await heavyComputation()
await MainActor.run {
updateUI(with: data)
}
}
// Async function
func heavyComputation() async -> Data {
// Computation
return data
}
Objective-C:
- (void)fetchUserWithId:(NSString *)userId
completion:(void (^)(User *user, NSError *error))completion {
[self.network GET:@"/users" parameters:@{@"id": userId} success:^(id response) {
User *user = [User fromJSON:response];
completion(user, nil);
} failure:^(NSError *error) {
completion(nil, error);
}];
}
Swift (callback style):
func fetchUser(
id: String,
completion: @escaping (Result<User, Error>) -> Void
) {
network.get("/users", parameters: ["id": id]) { result in
switch result {
case .success(let data):
let user = User(from: data)
completion(.success(user))
case .failure(let error):
completion(.failure(error))
}
}
}
Swift (async/await - preferred):
func fetchUser(id: String) async throws -> User {
let data = try await network.get("/users", parameters: ["id": id])
return User(from: data)
}
// Usage
Task {
do {
let user = try await fetchUser(id: "123")
print("Got user: \(user.name)")
} catch {
print("Error: \(error)")
}
}
Objective-C (manual synchronization):
@interface BankAccount : NSObject
@property (atomic, assign) double balance;
@end
@implementation BankAccount
- (void)deposit:(double)amount {
@synchronized(self) {
self.balance += amount;
}
}
- (BOOL)withdraw:(double)amount {
@synchronized(self) {
if (self.balance >= amount) {
self.balance -= amount;
return YES;
}
return NO;
}
}
@end
Swift (actor - automatic synchronization):
actor BankAccount {
private var balance: Double = 0
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) -> Bool {
guard balance >= amount else {
return false
}
balance -= amount
return true
}
}
// Usage (automatically synchronized)
let account = BankAccount()
Task {
await account.deposit(amount: 100)
let success = await account.withdraw(amount: 50)
}
Problem: Crashes when value is nil
Objective-C:
// Nil messaging is safe
NSString *name = [user name]; // Returns nil if user is nil
NSString *upper = [name uppercaseString]; // Returns nil if name is nil
Swift (bad):
let name = user!.name // Crashes if user is nil
let upper = name!.uppercased() // Crashes if name is nil
Swift (good):
guard let user = user else { return }
let name = user.name
let upper = name?.uppercased() ?? ""
Problem: Swift declarations not visible to Objective-C
// Bad: Not visible to Objective-C
class MyClass {
func myMethod() { }
}
// Good: Visible to Objective-C
@objc class MyClass: NSObject {
@objc func myMethod() { }
}
Objective-C:
- (id)fetchData {
return @{@"key": @"value"};
}
Swift (avoid):
func fetchData() -> Any {
return ["key": "value"]
}
Swift (prefer):
func fetchData() -> [String: String] {
return ["key": "value"]
}
Objective-C:
NSArray *array = [NSArray arrayWithObjects:@"A", nil];
NSMutableArray *mutable = (NSMutableArray *)array; // Dangerous cast
[mutable addObject:@"B"]; // Runtime error
Swift:
let array = ["A"] // Immutable
// var mutable = array as! [String] // No such thing as "mutable cast"
var mutable = array // Copy
mutable.append("B") // Safe, modifies copy
Objective-C:
NSArray *items = @[@"A", [NSNull null], @"C"];
NSString *second = items[1]; // NSNull object
// [second uppercaseString]; // Crashes - NSNull doesn't respond
Swift:
// Use optionals
let items: [String?] = ["A", nil, "C"]
let second = items[1] // Optional<String>
let upper = second?.uppercased() // Safe
// Or filter nils
let nonNilItems = items.compactMap { $0 }
Objective-C:
@property (copy) NSMutableString *title; // Bad: copy makes it immutable
Swift:
// String already has value semantics
var title: String // No need for "copy"
// For reference types that should be copied
var items: [Item] {
didSet {
// Array automatically copies on mutation
}
}
Objective-C:
[person performSelector:@selector(gret)]; // Typo: should be "greet"
// Runtime crash: unrecognized selector
Swift:
// Type-safe selector
#selector(Person.greet) // Compiler error if method doesn't exist
Problem: Unnecessary bridging between types
Swift (inefficient):
let nsString: NSString = "Hello"
let swiftString = nsString as String // Bridge
let upper = swiftString.uppercased()
let nsUpper = upper as NSString // Bridge again
Swift (efficient):
let string = "Hello" // Swift String
let upper = string.uppercased()
// Use NSString only when required by API
| Tool | Purpose | Notes |
|---|---|---|
| Xcode migration assistant | Automated Swift conversion | Use as starting point, manual refinement needed |
swiftc | Swift compiler | Compiles Swift code |
| Swift REPL | Interactive Swift | swift command in terminal |
swift-demangle | Demangle Swift symbols | Debug symbol names |
| SwiftLint | Swift style linter | Enforce Swift conventions |
| Objective-C → Swift converter (Xcode) | Edit → Convert → To Modern Objective-C Syntax first | Improves conversion quality |
Before (Objective-C):
// User.h
@interface User : NSObject
@property (nonatomic, copy) NSString *username;
@property (nonatomic, copy) NSString *email;
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithUsername:(NSString *)username email:(NSString *)email age:(NSInteger)age;
@end
// User.m
@implementation User
- (instancetype)initWithUsername:(NSString *)username email:(NSString *)email age:(NSInteger)age {
self = [super init];
if (self) {
_username = [username copy];
_email = [email copy];
_age = age;
}
return self;
}
@end
After (Swift):
struct User {
let username: String
let email: String
let age: Int
}
// Auto-generated memberwise initializer:
// init(username: String, email: String, age: Int)
// Usage
let user = User(username: "alice", email: "[email protected]", age: 30)
Why this translation:
struct is simpler for pure datalet for immutable propertiesBefore (Objective-C):
// DataSource.h
@protocol DataSourceDelegate <NSObject>
@required
- (NSInteger)numberOfItems;
@optional
- (void)didSelectItemAtIndex:(NSInteger)index;
@end
@interface DataSource : NSObject
@property (nonatomic, weak) id<DataSourceDelegate> delegate;
- (void)loadData;
@end
// DataSource.m
@implementation DataSource
- (void)loadData {
if ([self.delegate respondsToSelector:@selector(numberOfItems)]) {
NSInteger count = [self.delegate numberOfItems];
NSLog(@"Loading %ld items", (long)count);
}
if ([self.delegate respondsToSelector:@selector(didSelectItemAtIndex:)]) {
[self.delegate didSelectItemAtIndex:0];
}
}
@end
// ViewController.m
@interface ViewController () <DataSourceDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
DataSource *dataSource = [[DataSource alloc] init];
dataSource.delegate = self;
[dataSource loadData];
}
- (NSInteger)numberOfItems {
return 10;
}
- (void)didSelectItemAtIndex:(NSInteger)index {
NSLog(@"Selected item at index %ld", (long)index);
}
@end
After (Swift):
protocol DataSourceDelegate: AnyObject {
func numberOfItems() -> Int
func didSelectItem(at index: Int)
}
// Default implementation makes method optional
extension DataSourceDelegate {
func didSelectItem(at index: Int) {
// Default: do nothing
}
}
class DataSource {
weak var delegate: (any DataSourceDelegate)?
func loadData() {
guard let delegate = delegate else { return }
let count = delegate.numberOfItems()
print("Loading \(count) items")
delegate.didSelectItem(at: 0)
}
}
class ViewController: UIViewController, DataSourceDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let dataSource = DataSource()
dataSource.delegate = self
dataSource.loadData()
}
func numberOfItems() -> Int {
return 10
}
func didSelectItem(at index: Int) {
print("Selected item at index \(index)")
}
}
Why this translation:
any DataSourceDelegate for existential typeBefore (Objective-C):
// NetworkManager.h
typedef void (^NetworkCompletion)(id response, NSError *error);
@interface NetworkManager : NSObject
+ (instancetype)sharedManager;
- (void)GET:(NSString *)path
parameters:(NSDictionary *)params
completion:(NetworkCompletion)completion;
@end
// NetworkManager.m
@implementation NetworkManager
+ (instancetype)sharedManager {
static NetworkManager *shared = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shared = [[self alloc] init];
});
return shared;
}
- (void)GET:(NSString *)path
parameters:(NSDictionary *)params
completion:(NetworkCompletion)completion {
NSURL *url = [NSURL URLWithString:path];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil, error);
});
return;
}
NSError *parseError = nil;
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
dispatch_async(dispatch_get_main_queue(), ^{
if (parseError) {
completion(nil, parseError);
} else {
completion(json, nil);
}
});
}];
[task resume];
}
@end
// Usage
[[NetworkManager sharedManager] GET:@"https://api.example.com/users"
parameters:@{@"page": @1}
completion:^(id response, NSError *error) {
if (error) {
NSLog(@"Error: %@", error.localizedDescription);
return;
}
NSArray *users = response[@"users"];
NSLog(@"Fetched %lu users", (unsigned long)users.count);
}];
After (Swift - callback style):
class NetworkManager {
static let shared = NetworkManager()
private init() {}
func get(
_ path: String,
parameters: [String: Any] = [:],
completion: @escaping (Result<Any, Error>) -> Void
) {
guard let url = URL(string: path) else {
completion(.failure(NetworkError.invalidURL))
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
return
}
guard let data = data else {
DispatchQueue.main.async {
completion(.failure(NetworkError.noData))
}
return
}
do {
let json = try JSONSerialization.jsonObject(with: data)
DispatchQueue.main.async {
completion(.success(json))
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
task.resume()
}
}
enum NetworkError: Error {
case invalidURL
case noData
}
// Usage (callback style)
NetworkManager.shared.get("https://api.example.com/users", parameters: ["page": 1]) { result in
switch result {
case .success(let response):
if let dict = response as? [String: Any],
let users = dict["users"] as? [[String: Any]] {
print("Fetched \(users.count) users")
}
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
After (Swift - modern async/await):
class NetworkManager {
static let shared = NetworkManager()
private init() {}
func get(_ path: String, parameters: [String: Any] = [:]) async throws -> Any {
guard let url = URL(string: path) else {
throw NetworkError.invalidURL
}
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONSerialization.jsonObject(with: data)
}
// Type-safe version with Codable
func get<T: Decodable>(_ path: String, parameters: [String: Any] = [:]) async throws -> T {
guard let url = URL(string: path) else {
throw NetworkError.invalidURL
}
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(T.self, from: data)
}
}
// Model
struct UsersResponse: Codable {
let users: [User]
}
struct User: Codable {
let id: Int
let name: String
let email: String
}
// Usage (async/await)
Task {
do {
let response: UsersResponse = try await NetworkManager.shared.get(
"https://api.example.com/users",
parameters: ["page": 1]
)
print("Fetched \(response.users.count) users")
} catch {
print("Error: \(error.localizedDescription)")
}
}
Why this translation:
static let simpler than dispatch_onceResult<T, E> more type-safe than separate parametersthrows instead of error parametersFor more examples and patterns, see:
meta-convert-dev - Foundational patterns with cross-language exampleslang-objc-dev - Objective-C development patternslang-swift-dev - Swift development patternsCross-cutting pattern skills:
patterns-concurrency-dev - GCD, async/await, actors across languagespatterns-serialization-dev - JSON, Codable, NSCoding across languagespatterns-metaprogramming-dev - Runtime, reflection, property wrappersRelated conversion skills:
convert-swift-objc - Reverse conversion (Swift → Objective-C)xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience