-
Swift 개발자를 위한 Objective-C 문법 요약Apple🍎/Objective-C 2025. 5. 23. 15:34
1. 기본 구문 차이
파일 확장자와 구조
Swift와 Objective-C는 파일 구조부터 차이가 있습니다.
Swift
- 파일 확장자: .swift
- 단일 파일에 모든 코드 포함 가능
Objective-C
- 헤더 파일(.h) - 인터페이스 선언
- 구현 파일(.m) - 실제 구현 코드
예를 들어 Person 클래스를 생성할 때
Objective-C 헤더 파일 (Person.h)
#import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic, strong) NSString *name; @property (nonatomic, assign) NSInteger age; - (void)introduceYourself; @end
Objective-C 구현 파일 (Person.m)
#import "Person.h" @implementation Person - (void)introduceYourself { NSLog(@"Hello, my name is %@ and I am %ld years old", self.name, (long)self.age); } @end
Swift 파일 (Person.swift)
import Foundation class Person { var name: String var age: Int init(name: String, age: Int) { self.name = name self.age = age } func introduceYourself() { print("Hello, my name is \(name) and I am \(age) years old") } }
주요 차이점
- Objective-C는 헤더(.h)와 구현(.m) 파일을 분리합니다.
- Objective-C는 @interface와 @implementation 블록을 사용합니다.
- Swift는 더 간결한 구문을 가지며 헤더 파일이 필요하지 않습니다.
2. 변수와 상수 선언
Objective-C에서의 변수 선언
// 변수 선언 NSString *name = @"John"; NSInteger age = 30; CGFloat height = 175.5; BOOL isStudent = YES; // 포인터와 값 타입 NSString *text; // 객체형 (참조 타입) - 항상 포인터를 사용 NSInteger number; // 기본 값 타입 (포인터 사용 안함)
Swift에서의 변수와 상수
// 변수 선언 var name = "John" var age = 30 var height = 175.5 var isStudent = true // 상수 선언 let maxValue = 100
주요 차이점:
- Objective-C에서는 객체 타입에 항상 포인터(*)를 사용합니다.
- Objective-C는 const를 사용해 상수를 선언하지만, Swift는 let을 사용합니다.
- Objective-C에서 문자열은 @ 심볼로 시작합니다.
- Objective-C는 YES/NO를 사용하고, Swift는 true/false를 사용합니다.
3. 데이터 타입 비교
기본 데이터 타입
Objective-C Swift 설명
NSInteger, int Int 정수형 CGFloat, float, double Float, Double, CGFloat 부동 소수점 BOOL (YES/NO) Bool (true/false) 불리언 타입 char Character 문자 NSString * String 문자열 NSArray * Array<Element> 배열 NSDictionary * Dictionary<Key, Value> 딕셔너리(해시맵) NSSet * Set<Element> 집합 컬렉션 타입 예제
Objective-C 배열
// 불변 배열 NSArray *immutableArray = @[@"Apple", @"Banana", @"Orange"]; // 가변 배열 NSMutableArray *fruits = [NSMutableArray array]; [fruits addObject:@"Apple"]; [fruits addObject:@"Banana"]; [fruits insertObject:@"Orange" atIndex:1];
Swift 배열
// 배열 선언 let immutableArray = ["Apple", "Banana", "Orange"] // 가변 배열 var fruits = [String]() fruits.append("Apple") fruits.append("Banana") fruits.insert("Orange", at: 1)
Objective-C 딕셔너리
// 불변 딕셔너리 NSDictionary *person = @{ @"name": @"John", @"age": @30, @"isStudent": @NO }; // 가변 딕셔너리 NSMutableDictionary *settings = [NSMutableDictionary dictionary]; [settings setObject:@"Dark" forKey:@"theme"]; [settings setObject:@YES forKey:@"notifications"];
Swift 딕셔너리:
// 딕셔너리 선언 let person = [ "name": "John", "age": 30, "isStudent": false ] as [String: Any] // 가변 딕셔너리 var settings = [String: Any]() settings["theme"] = "Dark" settings["notifications"] = true
4. 함수와 메서드
Objective-C 메서드
// 인스턴스 메서드 (- 기호로 시작) - (NSString *)fullNameWithFirstName:(NSString *)firstName lastName:(NSString *)lastName { return [NSString stringWithFormat:@"%@ %@", firstName, lastName]; } // 클래스 메서드 (+ 기호로 시작) + (Person *)personWithName:(NSString *)name age:(NSInteger)age { Person *person = [[Person alloc] init]; person.name = name; person.age = age; return person; }
Swift 함수와 메서드
// 인스턴스 메서드 func fullName(firstName: String, lastName: String) -> String { return "\(firstName) \(lastName)" } // 정적 메서드 (타입 메서드) static func person(withName name: String, age: Int) -> Person { return Person(name: name, age: age) }
주요 차이점
- Objective-C에서는 -로 인스턴스 메서드, +로 클래스 메서드를 표시합니다.
- Objective-C 메서드는 매개변수 이름이 메서드 이름의 일부입니다 (예: initWithName:age:).
- Swift는 더 간결한 함수 선언 구문을 사용합니다.
- Objective-C에서는 반환 타입을 괄호 안에 명시합니다: - (ReturnType *).
메서드 호출 비교
Objective-C
// 메서드 호출 Person *john = [Person personWithName:@"John" age:30]; NSString *greeting = [john greetWithMessage:@"Hello" formal:YES]; // 여러 매개변수를 가진 메서드 호출 [self presentViewController:viewController animated:YES completion:^{ NSLog(@"Presentation completed"); }];
Swift
// 메서드 호출 let john = Person.person(withName: "John", age: 30) let greeting = john.greet(message: "Hello", formal: true) // 여러 매개변수를 가진 메서드 호출 present(viewController, animated: true) { print("Presentation completed") }
5. 클래스와 객체
Objective-C 클래스
// 헤더 파일 (MyClass.h) @interface MyClass : NSObject // 속성 @property (nonatomic, strong) NSString *title; @property (nonatomic, assign) NSInteger count; // 메서드 - (void)doSomethingWithParam:(NSString *)param; + (instancetype)sharedInstance; @end // 구현 파일 (MyClass.m) @implementation MyClass + (instancetype)sharedInstance { static MyClass *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; } - (void)doSomethingWithParam:(NSString *)param { NSLog(@"Doing something with %@", param); } @end
Swift 클래스
class MyClass { // 속성 var title: String? var count: Int = 0 // 싱글톤 static let shared = MyClass() // 메서드 func doSomething(with param: String) { print("Doing something with \(param)") } }
객체 초기화
Objective-C
// 할당과 초기화를 분리 Person *person = [[Person alloc] init]; person.name = @"John"; person.age = 30; // 또는 편의 메서드 사용 Person *person = [Person personWithName:@"John" age:30];
Swift
// 초기화 let person = Person(name: "John", age: 30) // 또는 타입 메서드 사용 let person = Person.person(withName: "John", age: 30)
6. 속성 (Properties)
Objective-C 속성
// 헤더 파일 @interface Person : NSObject // 속성 선언과 속성 특성 @property (nonatomic, strong) NSString *name; // 강한 참조 (ARC) @property (nonatomic, weak) UIView *parentView; // 약한 참조 (ARC) @property (nonatomic, assign) NSInteger age; // 값 타입 @property (nonatomic, copy) NSString *identifier; // 복사본 저장 // 읽기 전용 속성 @property (nonatomic, readonly) NSString *uuid; // 자동 getter/setter 생성 방지 @property (nonatomic, strong) NSString *customProp; @end // 구현 파일 @implementation Person // 커스텀 getter/setter - (void)setCustomProp:(NSString *)customProp { _customProp = [customProp uppercaseString]; } - (NSString *)customProp { return [_customProp stringByAppendingString:@"!"]; } @end
Swift 속성
class Person { // 일반 속성 var name: String var age: Int // 약한 참조 weak var parentView: UIView? // 계산 속성 var fullName: String { return "\(name) Smith" } // 프로퍼티 옵저버 var score: Int = 0 { willSet { print("Will change score from \(score) to \(newValue)") } didSet { print("Changed score from \(oldValue) to \(score)") } } // 지연 속성 lazy var expensiveResource: Resource = { return Resource() }() // 초기화 init(name: String, age: Int) { self.name = name self.age = age } }
속성 특성 설명
1. 스레드 안전성 관련 (Thread Safety)
atomic (기본값)
@property (atomic, strong) NSString *name; // 또는 단순히 @property (strong) NSString *name; // atomic이 기본값
atomic 속성은 getter와 setter가 원자적(atomic)으로 실행됩니다. 즉, 한 번에 하나의 스레드만 이 속성에 접근할 수 있습니다. 이는 스레드 안전성을 보장하지만 성능 오버헤드가 있습니다.
nonatomic (권장)
@property (nonatomic, strong) NSString *name;
nonatomic은 스레드 안전성을 포기하는 대신 더 나은 성능을 제공합니다. iOS 개발에서는 대부분의 UI 작업이 메인 스레드에서 실행되므로 일반적으로 nonatomic을 사용합니다.
// atomic - 내부적으로 이런 식으로 구현됨 - (NSString *)name { @synchronized(self) { return _name; } } - (void)setName:(NSString *)name { @synchronized(self) { _name = name; } } // nonatomic - 단순한 접근 - (NSString *)name { return _name; } - (void)setName:(NSString *)name { _name = name; }
2. 메모리 관리 관련 (Memory Management)
strong (기본값, ARC 환경)
@property (nonatomic, strong) NSArray *items;
strong은 "이 객체를 강하게 붙잡고 있겠다"는 의미입니다. 참조 카운트를 증가시키며, 이 속성이 해제되거나 다른 객체로 설정될 때까지 참조된 객체는 메모리에 유지됩니다.
// 내부 구현 개념 - (void)setItems:(NSArray *)items { [_items release]; // 기존 객체 해제 (ARC가 자동 처리) _items = [items retain]; // 새 객체 유지 (ARC가 자동 처리) }
weak
@property (nonatomic, weak) UIView *parentView; @property (nonatomic, weak) id<MyDelegate> delegate;
weak는 순환 참조(retain cycle)를 방지하기 위해 사용됩니다. 참조된 객체가 해제되면 이 속성은 자동으로 nil이 됩니다.
주요 사용 사례를 살펴보겠습니다
// 부모-자식 관계에서 순환 참조 방지 @interface Parent : NSObject @property (nonatomic, strong) NSArray<Child *> *children; // 부모가 자식들을 소유 @end @interface Child : NSObject @property (nonatomic, weak) Parent *parent; // 자식은 부모를 약하게 참조 @end // 델리게이트 패턴 @interface NetworkManager : NSObject @property (nonatomic, weak) id<NetworkDelegate> delegate; // 델리게이트는 보통 weak @end
assign
@property (nonatomic, assign) NSInteger count; @property (nonatomic, assign) CGFloat height; @property (nonatomic, assign) BOOL isVisible;
assign은 주로 기본 데이터 타입(primitive types)에 사용됩니다. 단순히 값을 할당하며, 참조 카운팅과 관련이 없습니다. 객체에 사용하면 위험할 수 있습니다.
// 위험한 사용 예시 - 하지 말 것! @property (nonatomic, assign) NSString *dangerousString; // 이렇게 되면... NSString *temp = @"Hello"; self.dangerousString = temp; // temp가 해제되면 dangerousString은 댕글링 포인터가 됨
copy
@property (nonatomic, copy) NSString *title; @property (nonatomic, copy) NSArray *itemsCopy;
copy는 할당할 때 객체의 복사본을 만듭니다. 특히 NSString과 같은 불변 객체나, 외부에서 변경될 수 있는 가변 객체를 안전하게 저장할 때 유용합니다.
copy의 중요성을 보여주는 예시입니다
// copy 없이 사용할 때의 문제점 @property (nonatomic, strong) NSMutableArray *dangerousArray; NSMutableArray *externalArray = [NSMutableArray arrayWithObjects:@"A", @"B", nil]; self.dangerousArray = externalArray; // 같은 객체를 참조 [externalArray addObject:@"C"]; // 외부에서 변경하면 우리 속성도 변경됨! // copy를 사용하면 안전 @property (nonatomic, copy) NSArray *safeArray; NSMutableArray *externalArray = [NSMutableArray arrayWithObjects:@"A", @"B", nil]; self.safeArray = externalArray; // 복사본이 생성됨 [externalArray addObject:@"C"]; // 외부 변경이 우리 속성에 영향 없음
3. 접근 제어 관련
readonly
@property (nonatomic, readonly) NSString *identifier; @property (nonatomic, readonly, strong) NSDate *createdAt;
readonly는 외부에서 setter를 사용할 수 없게 만듭니다. 클래스 내부에서는 직접 인스턴스 변수에 접근하여 값을 설정할 수 있습니다.
// 헤더 파일 @interface User : NSObject @property (nonatomic, readonly) NSString *userID; @end // 구현 파일 @implementation User - (instancetype)initWithUserID:(NSString *)userID { if (self = [super init]) { _userID = [userID copy]; // 내부에서는 직접 설정 가능 } return self; } @end
readwrite (기본값)
@property (nonatomic, readwrite, strong) NSString *name; // 또는 단순히 @property (nonatomic, strong) NSString *name;
readwrite는 getter와 setter 모두 생성합니다.
7. 제어 흐름
조건문
Objective-C
// if-else 문 if (age >= 18) { NSLog(@"Adult"); } else if (age >= 13) { NSLog(@"Teenager"); } else { NSLog(@"Child"); } // switch 문 switch (statusCode) { case 200: NSLog(@"Success"); break; case 404: NSLog(@"Not Found"); break; default: NSLog(@"Unknown status"); break; } // 3항 연산자 NSString *status = (age >= 18) ? @"Adult" : @"Minor";
Swift
// if-else 문 if age >= 18 { print("Adult") } else if age >= 13 { print("Teenager") } else { print("Child") } // switch 문 (break 필요 없음) switch statusCode { case 200: print("Success") case 404: print("Not Found") default: print("Unknown status") } // 3항 연산자 let status = (age >= 18) ? "Adult" : "Minor"
반복문
Objective-C
// for 루프 for (int i = 0; i < 10; i++) { NSLog(@"%d", i); } // 배열 열거 for (NSString *fruit in fruits) { NSLog(@"%@", fruit); } // while 루프 int count = 0; while (count < 10) { NSLog(@"%d", count); count++; } // do-while 루프 int value = 0; do { value++; NSLog(@"%d", value); } while (value < 5);
Swift
// for 루프 for i in 0..<10 { print(i) } // 배열 열거 for fruit in fruits { print(fruit) } // while 루프 var count = 0 while count < 10 { print(count) count += 1 } // repeat-while 루프 var value = 0 repeat { value += 1 print(value) } while value < 5
8. 메모리 관리
Objective-C의 ARC (Automatic Reference Counting)
// 강한 참조 @property (nonatomic, strong) NSObject *strongReference; // 약한 참조 (ARC가 0이 되면 nil이 됨) @property (nonatomic, weak) UIView *weakReference; // 미소유 참조 (ARC가 0이 되면 댕글링 포인터가 됨) @property (nonatomic, unsafe_unretained) NSObject *unsafeReference; // __block 변수 (블록 내에서 변수를 변경할 수 있게 함) __block NSInteger count = 0;
Swift의 ARC
// 강한 참조 (기본값) var strongReference: NSObject? // 약한 참조 weak var weakReference: UIView? // 미소유 참조 unowned var unownedReference: Controller // 클로저의 캡처 리스트 let closure = { [weak self, unowned controller] in self?.doSomething() controller.update() }
9. 블록과 클로저
Objective-C 블록
// 블록 타입 정의 typedef void(^CompletionBlock)(BOOL success, NSError *error); // 블록 변수 CompletionBlock completion = ^(BOOL success, NSError *error) { if (success) { NSLog(@"Operation succeeded"); } else { NSLog(@"Error: %@", error.localizedDescription); } }; // 메서드 내 블록 사용 - (void)downloadDataWithCompletion:(CompletionBlock)completion { // 비동기 작업... BOOL success = YES; NSError *error = nil; // 블록 호출 if (completion) { completion(success, error); } } // 호출 예시 [self downloadDataWithCompletion:^(BOOL success, NSError *error) { // 처리 }];
Swift 클로저
// 클로저 타입 정의 typealias CompletionHandler = (Bool, Error?) -> Void // 클로저 변수 let completion: CompletionHandler = { success, error in if success { print("Operation succeeded") } else if let error = error { print("Error: \(error.localizedDescription)") } } // 메서드 내 클로저 사용 func downloadData(completion: CompletionHandler?) { // 비동기 작업... let success = true let error: Error? = nil // 클로저 호출 completion?(success, error) } // 호출 예시 downloadData { success, error in // 처리 }
주요 차이점
- Objective-C 블록은 ^ 기호로 시작합니다.
- Swift 클로저는 더 간결한 구문을 사용합니다.
- Swift는 후행 클로저 구문을 지원합니다.
- Objective-C는 블록 내에서 외부 변수를 수정하기 위해 __block을 필요로 합니다.
10. 오류 처리
Objective-C 오류 처리
// NSError를 통한 오류 처리 - (BOOL)saveData:(NSData *)data error:(NSError **)error { if (!data) { if (error) { *error = [NSError errorWithDomain:@"MyApp" code:100 userInfo:@{NSLocalizedDescriptionKey: @"Data cannot be nil"}]; } return NO; } // 저장 로직... return YES; } // 메서드 호출 NSError *error = nil; BOOL success = [manager saveData:data error:&error]; if (!success && error) { NSLog(@"Save failed: %@", error.localizedDescription); } // @try/@catch를 사용한 예외 처리 @try { // 예외가 발생할 수 있는 코드 NSArray *array = @[@1, @2, @3]; id item = array[10]; // 예외 발생! } @catch (NSException *exception) { NSLog(@"Exception: %@", exception.reason); } @finally { // 항상 실행되는 코드 NSLog(@"Cleanup code"); }
Swift 오류 처리
// Error 프로토콜 구현 enum SaveError: Error { case nilData case diskFull case permissionDenied } // throws를 사용한 오류 처리 func saveData(_ data: Data?) throws -> Bool { guard let data = data else { throw SaveError.nilData } // 저장 로직... return true } // 호출 do { let success = try manager.saveData(data) print("Save successful") } catch SaveError.nilData { print("Save failed: Data is nil") } catch { print("Save failed: \(error)") } // try? 및 try! 사용 // 오류 무시, 실패 시 nil 반환 let success = try? manager.saveData(data) // 오류가 발생하지 않을 것이라고 확신할 때만 사용 // 오류 발생 시 앱 충돌 let definiteSuccess = try! manager.saveData(validData)
11. 프로토콜과 델리게이션
Objective-C 프로토콜
// 프로토콜 정의 (헤더 파일) @protocol DataSourceDelegate <NSObject> // 필수 메서드 - (NSInteger)numberOfItems; - (NSString *)titleForItemAtIndex:(NSInteger)index; // 선택적 메서드 @optional - (void)didSelectItemAtIndex:(NSInteger)index; @end // 프로토콜 채택 (헤더 파일) @interface MyViewController : UIViewController <DataSourceDelegate, UITableViewDelegate> @property (nonatomic, weak) id<DataSourceDelegate> dataSource; @end // 구현 (구현 파일) @implementation MyViewController - (void)viewDidLoad { [super viewDidLoad]; // 프로토콜 메서드 호출 NSInteger count = [self.dataSource numberOfItems]; // 선택적 메서드 호출 전 확인 if ([self.dataSource respondsToSelector:@selector(didSelectItemAtIndex:)]) { [self.dataSource didSelectItemAtIndex:0]; } } // 프로토콜 메서드 구현 - (NSInteger)numberOfItems { return 10; } - (NSString *)titleForItemAtIndex:(NSInteger)index { return [NSString stringWithFormat:@"Item %ld", (long)index]; } @end
Swift 프로토콜
// 프로토콜 정의 protocol DataSourceDelegate: AnyObject { // 필수 메서드 func numberOfItems() -> Int func title(forItemAt index: Int) -> String // 선택적 메서드 func didSelectItem(at index: Int) } // 기본 구현 제공 extension DataSourceDelegate { func didSelectItem(at index: Int) { // 기본 구현 print("Item selected at index \(index)") } } // 프로토콜 채택 class MyViewController: UIViewController, DataSourceDelegate, UITableViewDelegate { // 약한 참조로 델리게이트 선언 weak var dataSource: DataSourceDelegate? override func viewDidLoad() { super.viewDidLoad() // 프로토콜 메서드 호출 let count = dataSource?.numberOfItems() ?? 0 // 선택적 메서드는 기본 구현이 있으므로 항상 호출 가능 dataSource?.didSelectItem(at: 0) } // 프로토콜 메서드 구현 func numberOfItems() -> Int { return 10 } func title(forItemAt index: Int) -> String { return "Item \(index)" } }
12. Swift와 Objective-C 상호 운용성
Bridging Header (브리징 헤더)
Swift 프로젝트에서 Objective-C 코드를 사용하려면 브리징 헤더를 설정해야 합니다:
// MyProject-Bridging-Header.h #import "ObjCClass.h" #import "AnotherObjCClass.h"
@objc 및 @objcMembers
Swift 코드를 Objective-C에서 사용하려면 @objc 어트리뷰트를 사용합니다:
// 단일 메서드에 @objc 추가 class MySwiftClass { @objc func methodForObjC() { // Objective-C에서 호출 가능 } func swiftOnlyMethod() { // Objective-C에서 호출 불가 } } // 클래스 전체에 @objcMembers 추가 @objcMembers class MyObjCCompatibleClass { var name: String = "" func allMethodsAvailableToObjC() { // 모든 메서드가 Objective-C에서 호출 가능 } }
13. nil 처리와 옵셔널
Objective-C nil 처리
// nil 확인 if (person == nil) { NSLog(@"Person is nil"); } // nil 메시지 전송 (안전함, 아무 일도 일어나지 않음) [nil doSomething]; // 충돌 없음 // nil 체이닝 (수동으로 체크해야 함) if (person.address) { NSString *city = person.address.city; } // 조건부 초기화 NSString *name = person.name ?: @"Unknown";
Swift 옵셔널
// 옵셔널 선언 var name: String? var age: Int? // 옵셔널 강제 추출 (위험할 수 있음) let unwrappedName = name! // 옵셔널 바인딩 if let unwrappedName = name { print("Name: \(unwrappedName)") } // 가드 구문 guard let unwrappedAge = age else { print("Age is nil") return } // 옵셔널 체이닝 let city = person?.address?.city // nil 병합 연산자 let displayName = name ?? "Unknown"
주요 차이점
- Objective-C는 nil을 포인터에만 사용할 수 있지만, Swift는 모든 타입을 옵셔널로 만들 수 있습니다.
- Swift는 명시적인 옵셔널 타입(?)과 강제 추출(!)을 제공합니다.
- Swift는 안전한 옵셔널 체이닝과 바인딩을 제공합니다.
- Objective-C에서 nil 메시지 전송은 안전하지만, Swift에서 nil 옵셔널 강제 추출은 충돌을 일으킵니다.
'Apple🍎 > Objective-C' 카테고리의 다른 글
Objective-C의 Selector와 Dispatch Table (3) 2025.05.21 Objective-C: Small Talk과 Message 전달 패러다임 (0) 2025.05.21