클래스는 객체 지향 프로그래밍에서 중요한 개념 중 하나로, 데이터와 그 데이터를 조작하는 메서드를 하나의 단위로 캡슐화하는 구조다.
클래스는 객체를 생성하기 위한 설계도 또는 템플릿 역할을 하며, Objective-C에서 모든 클래스는 NSObject 라는 기본 클래스에서 파생된다.
헤더 파일(.h)에서는 클래스의 인터페이스를 선언하고, 구현 파일(.m)에서는 실제 기능을 구현한다.
// 선언부
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (void)greet;
- (void)celebrateBirthday;
@end
// 구현부
#import "Person.h"
@implementation Person
- (void)greet {
NSLog(@"Hello, my name is %@", self.name);
}
- (void)celebrateBirthday {
self.age += 1;
NSLog(@"Happy Birthday %@! You are now %ld years old.", self.name, (long)self.age);
}
@end
// 사용부
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.name = @"John";
person.age = 30;
[person greet];
[person celebrateBirthday];
}
return 0;
}
객체 지향 프로그래밍에서 초기화는 객체를 생성하고 그 객체의 상태를 설정하는 과정으로 이 과정은 객체가 생성될 때 일관된 상태로 시작되도록 보장하며, 객체의 멤버 변수나 프로퍼티에 초기값을 설정한다.
// 선언부
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
// 기본 초기화 메서드
- (instancetype)init;
// 메서드 선언
- (void)greet;
@end
// 구현부
#import "Person.h"
@implementation Person
// 기본 초기화 메서드
- (instancetype)init {
self = [super init];
if (self) {
_name = @"Unknown"; // 기본값 설정
_age = 0; // 기본값 설정
}
return self;
}
// 메서드 구현
- (void)greet {
NSLog(@"Hello, my name is %@ and I am %ld years old.", self.name, (long)self.age);
}
@end
// 사용부
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 기본 초기화 메서드를 사용하여 객체 생성
Person *person = [[Person alloc] init];
[person greet]; // 출력: Hello, my name is Unknown and I am 0 years old.
}
return 0;
}
커스텀 초기화는 객체를 생성 시 프로퍼티를 직접 설정할 수 있다.
// 선언부
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
// 커스텀 초기화 메서드
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
// 메서드 선언
- (void)greet;
@end
// 구현부
#import "Person.h"
@implementation Person
// 커스텀 초기화 메서드
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
self = [super init];
if (self) {
_name = name ?: @"Unknown"; // nil 대체값 설정
_age = age;
}
return self;
}
// 메서드 구현
- (void)greet {
NSLog(@"Hello, my name is %@ and I am %ld years old.", self.name, (long)self.age);
}
@end
// 사용부
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 커스텀 초기화 메서드를 사용하여 객체 생성
Person *person = [[Person alloc] initWithName:@"John" age:30];
[person greet]; // 출력: Hello, my name is John and I am 30 years old.
}
return 0;
}
프로퍼티: 객체의 상태를 외부에서 접근하고 수정할 수 있는 인터페이스를 제공하는데 사용된다. @property 선언을 통해 정의한다. 접근자 메서드(getter)와 설정자 메서드(setter)를 자동으로 생성하여, 클래스의 데이터에 대한 직접 접근을 제어한다.
@synthesize는 Objective-C에서 프로퍼티의 자동 생성된 인스턴스 변수를 정의하고, 기본 getter 및 setter 메서드를 구현하는 데 사용된다. 현재는 Xcode의 컴파일러가 자동으로 인스턴스 변수를 생성하고 기본 getter/setter 메서드를 제공하기 때문에, 코드에서 생략 가능하다.
멤버 변수: 클래스의 인스턴스가 가지는 데이터 저장소,
클래스 내부에서 데이터 저장과 관리에 사용된다. 일반적으로 클래스의 구현 파일(.m)에서 정의하고 @private, @protected 접근 제어자를 사용하여 외부에서 직접 접근할 수 없으며, 클래스 내부의 메서드를 통해 관리된다.
// 선언부
#import <Foundation/Foundation.h>
@interface Person : NSObject
// 프로퍼티 선언
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
// 퍼블릭 메서드 선언
- (void)publicMethod;
@end
// 구현부
#import "Person.h"
@implementation Person {
// 멤버 변수 정의
NSString *_internalIdentifier;
NSInteger _internalCount;
}
// 기본 초기화 메서드
- (instancetype)init {
self = [super init];
if (self) {
_name = @"Unknown";
_age = 0;
_internalIdentifier = @"DefaultID";
_internalCount = 0;
}
return self;
}
// 퍼블릭 메서드 구현
- (void)publicMethod {
NSLog(@"Name: %@, Age: %ld", self.name, (long)self.age);
}
@end
// 사용부
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.name = @"John";
person.age = 30;
NSLog(@"Name: %@", person.name);
NSLog(@"Age: %ld", (long)person.age);
[person publicMethod];
}
return 0;
}
readonly: 프로퍼티에 대한 읽기 전용 접근을 허용
readwrite: 프로퍼티에 대한 읽기와 쓰기 모두를 허용
atomic: 멀티 스레드 환경에서 동시에 프로퍼티에 접근하려 할 때, 자동으로 이 프로퍼티에 Lock 기능을 제공한다. 모든 프로퍼티에 사용 시 성능이 저하될 수 있어서 꼭 필요한 경우가 아니면 nonatomic을 사용한다.
이 외에도 메모리 관리 키워드가 있다.
ARC/MRC
@property (nonatomic, strong) NSString *name;
@property (nonatomic, weak) id<SomeDelegate> delegate;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, strong, readwrite) NSString *name;
@property (atomic, strong) NSString *name;
객체의 상태를 조작하거나 객체의 동작을 정의하는 데 사용된다.
인스턴스 메서드: - 기호로 선언하고, 특정 객체 인스턴스에서 호출되며, 객체의 상태를 조작하거나 객체에 대한 작업을 수행한다.
타입 메서드: +기호로 선언하고, 클래스 자체에서 호출되며, 인스턴스화 없이 클래스 이름으로 호출해서 클래스의 상태를 조작하거나 클래스에 관련된 작업을 수행한다.
// 선언부
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
// 인스턴스 메서드 선언
- (void)greet;
// 클래스 메서드 선언
+ (Person *)personWithName:(NSString *)name age:(NSInteger)age;
@end
// 구현부
#import "Person.h"
@implementation Person
// 인스턴스 메서드 구현
- (void)greet {
NSLog(@"Hello, my name is %@ and I am %ld years old.", self.name, (long)self.age);
}
// 클래스 메서드 구현
+ (Person *)personWithName:(NSString *)name age:(NSInteger)age {
Person *newPerson = [[Person alloc] init];
newPerson.name = name;
newPerson.age = age;
return newPerson;
}
@end
// 사용부
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 클래스 메서드 사용하여 Person 객체 생성
Person *person = [Person personWithName:@"John" age:30];
// 인스턴스 메서드 사용
[person greet]; // 출력: Hello, my name is John and I am 30 years old.
}
return 0;
}
클래스 간에 속성과 메서드를 공유할 수 있어 코드의 재사용성과 유지보수성을 높일 수 있다. 하나의 부모 클래스를 상속받을 수 있고, 자식 클래스는 부모 클래스의 속성과 메서드를 상속받아 사용할 수 있다. 자식클래스에서 부모클래스의 private 접근제어자와 구현부에는 접근 불가능하고, 메소드는 오버라이딩 가능하다. 부모 클래스의 메서드를 호출할 때는 super 키워드를 사용한다.
// Animal 클래스 선언부
#import <Foundation/Foundation.h>
@interface Animal : NSObject
@property (nonatomic, strong) NSString *name;
- (instancetype)initWithName:(NSString *)name;
- (void)makeSound;
@end
// Animal 클래스 구현부
#import "Animal.h"
@implementation Animal
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self) {
_name = name;
}
return self;
}
- (void)makeSound {
NSLog(@"Some generic animal sound");
}
@end
// Dog 클래스 선언부
#import "Animal.h"
@interface Dog : Animal
- (void)fetch;
@end
// Dog 클래스 구현부
#import "Dog.h"
@implementation Dog
// swift와 다르게 override 키워드를 붙이지않고, 같은 이름으로 메소드를 정의하면 자동으로 오버라이딩된다.
- (void)makeSound {
NSLog(@"Woof! Woof!");
}
- (void)fetch {
NSLog(@"%@ is fetching!", self.name);
}
@end
// 사용부
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *myDog = [[Dog alloc] initWithName:@"Buddy"];
[myDog makeSound]; // 출력: Woof! Woof!
[myDog fetch]; // 출력: Buddy is fetching!
}
return 0;
}
카테고리를 사용하면 기존 클래스에 새로운 메서드를 추가하거나 메서드를 분리하여 코드의 조직화와 관리가 용이해진다. 프로퍼티 추가나, 기존 메서드를 오버라이드는 불가능하고 새로운 메서드를 추가하거나 기존 메서드의 동작을 확장하는 데 사용된다. 카테고리에 선언한 메서드는 원래 클래스의 인스턴스와 모든 하위 클래스에서 사용할 수 있다.
// 카테고리 선언부
#import <Foundation/Foundation.h>
@interface NSString (MyAdditions)
+ (NSString *)getString;
@end
// 카테고리 구현부
#import "NSString+MyAdditions.h"
@implementation NSString (MyAdditions)
+ (NSString *)getString {
return @"문자열 카테고리 추가";
}
@end
// 사용부
#import <Foundation/Foundation.h>
#import "NSString+MyAdditions.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 카테고리를 사용하여 추가된 클래스 메서드 호출
NSString *copyString = [NSString getString];
NSLog(@"Accessing Category: %@", copyString); // 출력: Accessing Category: 문자열 카테고리 추가
}
return 0;
}
주로 클래스의 private 인터페이스를 확장하는 데 사용된다. Extension은 클래스의 구현 파일(.m)에서만 사용되며, 다른 파일에서는 접근할 수 없고 해당 클래스 내에서만 접근할 수 있다.
// 선언부
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong, readonly) NSString *name; // readonly 프로퍼티
- (void)publicMethod:(NSString *)name; // public 메서드
@end
// 구현부
#import "Person.h"
// 클래스 확장
@interface Person ()
@property (nonatomic, strong, readwrite) NSString *name; // 확장에서 readwrite로 변경
@property (nonatomic, assign) NSInteger age; // private 속성
- (void)privateMethod; // private 메서드
@end
@implementation Person
- (instancetype)init {
self = [super init];
if (self) {
_name = @"Unknown";
_age = 0;
}
return self;
}
- (void)publicMethod:(NSString *)name {
self.name = name;
}
- (void)privateMethod {
NSLog(@"This is a private method.");
}
@end
// 사용부
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
// 초기값 확인
NSLog(@"Initial name: %@", person.name); // 출력: Initial name: Unknown
// 이름 설정을 위한 publicMethod 호출
[person publicMethod:@"John"];
// 변경된 이름 확인
NSLog(@"Updated name: %@", person.name); // 출력: pdated name: John
}
return 0;
}