Objective-CにおけるcopyとmutableCopyの仕組みと実践的な使い方

概要

Objective-Cでは、copyおよびmutableCopyメソッドを使用して、オブジェクトのコピーを作成できます。これらのメソッドは、元のオブジェクトを変更せずに、新しいオブジェクトで安全に操作を行うために不可欠です。しかし、これらのメソッドの動作は、元のオブジェクトが不変(immutable)か可変(mutable)かによって異なります。本記事では、文字列とカスタムオブジェクトにおけるこれらのメソッドの挙動を詳しく解説します。

文字列のコピー

文字列は、不変のNSStringと可変のNSMutableStringの2種類があります。これらの型に対するcopymutableCopyの動作は以下のようになります。

1. 不変文字列(NSString)のmutableCopy

mutableCopyは、元の不変文字列の内容を新しい可変文字列(NSMutableString)にコピーします。これは「深いコピー」です。

#import 

void immutableStringMutableCopy() {
    NSString *originalString = @"Hello, World!";
    NSLog(@"Original: %@", originalString);
    
    // 新しいNSMutableStringオブジェクトが作成される(深いコピー)
    NSMutableString *copiedString = [originalString mutableCopy];
    
    // copiedStringは元のオブジェクトと異なる
    NSLog(@"Are they the same? %d", originalString == copiedString);
    
    // copiedStringを変更しても、originalStringは影響を受けない
    [copiedString appendString:@" I am modified."];
    NSLog(@"Original after modification: %@", originalString);
    NSLog(@"Copied after modification: %@", copiedString);
    
    [copiedString release];
}

2. 不変文字列(NSString)のcopy

不変オブジェクトに対するcopyは、パフォーマンスを最適化するため、元のオブジェクト自身を返します。これは「浅いコピー」です。

void immutableStringCopy() {
    NSString *originalString = @"Hello, World!";
    NSLog(@"Original retain count: %lu", (unsigned long)[originalString retainCount]);
    
    // 元のオブジェクトがそのまま返される(浅いコピー)
    NSString *copiedString = [originalString copy];
    
    // copiedStringは元のオブジェクトと同じ
    NSLog(@"Are they the same? %d", originalString == copiedString);
    NSLog(@"Original retain count after copy: %lu", (unsigned long)[originalString retainCount]);
    
    [copiedString release];
}

3. 可変文字列(NSMutableString)のcopy

可変オブジェクトに対するcopyは、その内容を新しい不変文字列(NSString)にコピーします。これも「深いコピー」です。

void mutableStringCopy() {
    NSMutableString *originalString = [NSMutableString stringWithString:@"Initial value"];
    NSLog(@"Original: %@", originalString);
    
    // 新しいNSStringオブジェクトが作成される(深いコピー)
    NSString *copiedString = [originalString copy];
    
    // copiedStringは元のオブジェクトと異なる
    NSLog(@"Are they the same? %d", originalString == copiedString);
    
    // copiedStringは不変なので、appendはできない
    // [copiedString appendString:@"..."]; // これはコンパイルエラーになる
    
    [copiedString release];
}

4. 可変文字列(NSMutableString)のmutableCopy

可変オブジェクトに対するmutableCopyは、その内容を新しい可変文字列(NSMutableString)にコピーします。これも「深いコピー」です。

void mutableStringMutableCopy() {
    NSMutableString *originalString = [NSMutableString stringWithString:@"Initial value"];
    NSLog(@"Original: %@", originalString);
    
    // 新しいNSMutableStringオブジェクトが作成される(深いコピー)
    NSMutableString *copiedString = [originalString mutableCopy];
    
    // copiedStringは元のオブジェクトと異なる
    NSLog(@"Are they the same? %d", originalString == copiedString);
    
    // copiedStringを変更しても、originalStringは影響を受けない
    [copiedString appendString:@" and modified."];
    NSLog(@"Original after modification: %@", originalString);
    NSLog(@"Copied after modification: %@", copiedString);
    
    [copiedString release];
}

カスタムオブジェクトのコピー

独自のクラスでcopyメソッドを使用するには、NSCopyingプロトコルに準拠し、copyWithZone:メソッドを実装する必要があります。これにより、オブジェクトのプロパティも適切にコピーできます。

1. 基本的なカスタムオブジェクトのコピー

以下は、名前を保持するシンプルなStudentクラスの例です。名前はcopyプロパティとして宣言されているため、外部からの変更が内部の状態に影響を与えません。

// Student.h
#import 

@interface Student : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
- (instancetype)initWithName:(NSString *)name;
@end

// Student.m
#import "Student.h"

@implementation Student

- (instancetype)initWithName:(NSString *)name {
    self = [super init];
    if (self) {
        _name = name;
    }
    return self;
}

- (id)copyWithZone:(NSZone *)zone {
    // 新しいStudentインスタンスを作成し、プロパティをコピーする
    Student *copy = [[[self class] allocWithZone:zone] initWithName:self.name];
    return copy;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"[Student name: %@]", self.name];
}

@end
// 使用例
void customObjectCopy() {
    Student *student1 = [[Student alloc] initWithName:@"Alice"];
    NSLog(@"Student1: %@", student1);
    
    // student1のコピーを作成
    Student *student2 = [student1 copy];
    NSLog(@"Student2 (before modification): %@", student2);
    
    // student2の名前を変更
    student2.name = @"Bob";
    NSLog(@"Student2 (after modification): %@", student2);
    NSLog(@"Student1 (after student2 modification): %@", student1);
    
    [student2 release];
    [student1 release];
}

2. サブクラスとcopyWithZone:の実装

サブクラスで正しくコピーを行うためには、親クラスのcopyWithZone:メソッドを呼び出した後、サブクラス固有のプロパティをコピーする必要があります。また、[self class]を使用して、正しいクラスのインスタンスを生成することが重要です。

// GoodStudent.h
#import "Student.h"

@interface GoodStudent : Student
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
@end

// GoodStudent.m
#import "GoodStudent.h"

@implementation GoodStudent

- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
    self = [super initWithName:name];
    if (self) {
        _age = age;
    }
    return self;
}

- (id)copyWithZone:(NSZone *)zone {
    // 親クラスのcopyWithZone:を呼び出し、基底クラスのプロパティをコピー
    GoodStudent *copy = (GoodStudent *)[super copyWithZone:zone];
    // サブクラス固有のプロパティをコピー
    copy.age = self.age;
    return copy;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"[GoodStudent name: %@, age: %ld]", self.name, (long)self.age];
}

@end
// 使用例
void subclassCopy() {
    GoodStudent *goodStudent1 = [[GoodStudent alloc] initWithName:@"Charlie" age:20];
    NSLog(@"GoodStudent1: %@", goodStudent1);
    
    // GoodStudent1のコピーを作成
    GoodStudent *goodStudent2 = [goodStudent1 copy];
    NSLog(@"GoodStudent2 (before modification): %@", goodStudent2);
    
    // goodStudent2のプロパティを変更
    goodStudent2.name = @"David";
    goodStudent2.age = 22;
    NSLog(@"GoodStudent2 (after modification): %@", goodStudent2);
    NSLog(@"GoodStudent1 (after goodStudent2 modification): %@", goodStudent1);
    
    [goodStudent2 release];
    [goodStudent1 release];
}

タグ: Objective-C copy mutableCopy NSCopying 深いコピー

6月18日 21:01 投稿