Open17

objective-cの基礎を学ぶ

makumaakumakumaaku

具体的なこと

ヘッダーファイルと実装ファイル

objective-c以下の2つのファイルで構成される。(c++と同じ構成)

  • ヘッダーファイル(.h)
  • 実装ファイル(.m)

クラスの宣言(.h)

  • Objective-Cで作成されるクラスは全てNSObjectのサブクラスになる。(NSObjectがルートクラス)。NSStringNSArray
  • Objective-Cでは親クラスを省略して記述できない
@interface クラス名 : 親クラス名
@end

例: Sample.h

@interface Sample : NSObject
@end

インポート

NSObjectFoundation.frameworkというフレームワーク部分で定義されている。
=> NSObjectFoundation.frameworkのインポートが必要となる

#import <Foundation/Foundation.h>
...

クラスの実装(.m)

クラスの定義やメソッドの実装は.mファイルで行う。
クラスの実装は以下のようになる

@implementation クラス名
@end

実装ファイルには、クラスを宣言したヘッダーファイルをインポートする必要がある。

例: Sample.m

#import "Sample.h"
@implementation Sample
@end

プロパティ

Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
{
    // 名前
    NSString *_name;
    
    // 年齢
    NSInteger _age;
}
// 名前を取得する
- (NSString *)name;
// 名前を設定する
- (void)setName:(NSString *)name;
 
// 年齢を取得する
- (NSInteger)age;
// 年齢を設定する
- (void)setAge:(NSInteger)age;
@end
Person.m
#import "Person.h"
@implementation Person
// 名前を取得する
- (NSString *)name
{
    return _name;
}
// 名前を設定する
- (void)setName:(NSString *)name
{
    _name = name;
}
// 年齢を取得する
- (NSInteger)age
{
    return _age;
}
// 年齢を設定する
- (void)setAge:(NSInteger)age
{
    _age = age;
}
@end

ゲッター、セッター、アクセサー(メソッド)

このとき、インスタンス変数から値を取り出して返すメソッドを「ゲッター(Getter)」、インスタンス変数に値を代入するメソッドを「セッター(Setter)」と言います。そして、これらのメソッドをまとめて「アクセサー(メソッド)」とも呼びます。

  • (NSString *)nameメソッドは_nameのゲッター
  • (void)setName:(NSString *)name_nameのセッター

@propertyでインスタンス変数とアクセサーの自動生成

見出しの通りobjective-cでは@propertyを使用することで、インスタンス変数とアクセサーの自動生成ができる。

使い方

文末にセミコロンが必要

@property (オプション) 型 プロパティ名;

Person.hPerson.m@propertyを使って書き換え

Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
// 名前
@property (nonatomic) NSString *name;
// 年齢
@property (nonatomic) NSInteger age;
@end
Person.m
#import "Person.h"
@implementation Person
@end

実装ファイルがかなりスッキリ。

  • インスタンス変数は名前に_が付与されたものが自動生成される。(_name_age)
  • ゲッターメソッドは、メソッド名にそのままプロパティ名が入る。(- (NSString *)name- (NSInteger)age
  • セッターメソッドは、プロパティ名の頭にsetが付与され、プロパティ名の頭文字が大文字になる。(- (void)setName:- (void)setAge:

プロパティへのアクセス

ViewController.m
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // Personクラスのインスタンスを生成する
    Person *aPerson = [Person new];
    // nameに値を設定する
    aPerson.name = @"山田太郎";
    
    // nameから値を取得する
    NSString *aName = aPerson.name;
    
    NSLog(@"aName : %@", aName);
}
@end

アクセサーを自分で定義する場合

ゲッターNSString *)nameをカスタマイズする場合は以下のような感じになる。
_nameは自動生成されている。

Person.m
#import "Person.h"
@implementation Person
- (NSString *)name
{
    return [NSString stringWithFormat:@"%@ 様", _name];
}
@end

@propertyのオプション


引用
https://atmarkit.itmedia.co.jp/ait/articles/1404/09/news029_2.html

所有属性については、NSStringやNSArrayなどの既存のクラス、または独自に定義したクラスなどのオブジェクト型の場合はstrongが、それ以外の型の場合はassignがデフォルトで設定されるため、省略できます。

それ以外のオプションについては、スレッドセーフに関するオプションを省略した場合はatomicが、アクセス制御に関するオプションを省略した場合は、readwriteがデフォルトで設定されます。

以下2つは同じ

Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
// 名前
@property (nonatomic) NSString *name;
// 年齢
@property (nonatomic) NSInteger age;
@end
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
// 名前
@property (strong, nonatomic, readwrite) NSString *name;
// 年齢
@property (assign, nonatomic, readwrite) NSInteger age;
@end

明示的にnonatomicを指定しているのは、atomicを指定してもパフォーマンスを悪化させるだけで、ほとんどの場合にメリットが無いためです。ですので、よほどのことが無い限りはnonatomicを指定しておきましょう。

メソッド

メソッドの定義

- (戻り値の型)メソッド名
{
    // メソッドの実装
}

値を返す(return)

- (NSString *)nameメソッドは、「NSString型の値を返すnameという名前のメソッド」

// 名前を取得する
- (NSString *)name
{
    return _name;
}

戻り値はない場合はvoid

setNameメソッドは戻り値がないのでvoidを指定

// 名前を設定する
- (void)setName:(NSString *)name
{
    _name = name;
}

メソッドの引数とラベル

また、「- (void)setName:」メソッドでは、メソッド名の後に「コロン(:)」があります。これは、引数といって、メソッドに値を渡す必要がある場合に使用します。

引数が複数ある場合

ラベルは省略可能。

// 引数が3つの場合
- (戻り値の型)メソッド名:(引数の型)引数1 ラベル:(引数の型)引数2 ラベル:(引数の型)引数3

第一引数のラベルは省略されている。

- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile encoding:(NSStringEncoding)enc error:(NSError **)error;

使う場合

NSString *str = @"文字列";
NSError *error = nil;
[str writeToFile:@"write/to/path"
      atomically:YES
        encoding:NSUTF8StringEncoding
           error:&error];

プロパティとメソッドのスコープ

Objective-Cでは、他のクラスからプロパティへのアクセスやメソッドの呼び出しを行えるようにするために、それらの宣言をヘッダーファイルに記載します。

name, ageプロパティとdisplayName,displayAge,displayProfileの3つのメソッドは他のクラスから参照可能。
=> ヘッダーファイルに宣言されたプロパティやメソッドは他のクラスから参照可能(Public)になる。

Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
// 名前
@property (nonatomic) NSString *name;
// 年齢
@property (nonatomic) NSInteger age;
// 名前をログに出力する
- (void)displayName;
// 年齢をログに出力する
- (void)displayAge;
// 名前と年齢をログに出力する
- (void)displayProfile;
@end

プライペートなプロパティやメソッドの実装

実装ファイル側で宣言を行えばプライベートになる。

Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
// 名前
@property (nonatomic) NSString *name;
// 名前と年齢をログに出力する
- (void)displayProfile;
@end
Person.m
#import <Foundation/Foundation.h>
@interface Person ()
// 年齢
@property (nonatomic) NSInteger age;
// 名前をログに出力する
- (void)displayName;
// 年齢をログに出力する
- (void)displayAge;
@end
@implementation Person
……
@end

こうすることで、「age」プロパティと「- displayName」メソッド、「- displayAge」メソッドは他のクラスから参照できなくなります。

クラスエクステンション

上の例の実装(.m)ファイルに書かれている@interface Person () …… @endはクラスエクステンションと呼ばれる機能。

文字通りクラスを拡張する機能です。この機能を利用して疑似的にプライベートにすることが可能です。

なお、「@implementation …… @end」で実装されているメソッドに関しては、クラスエクステンション部分に宣言しなくてもヘッダーに記述しなければ、プライベート扱いになります。

selfとsuper

メソッド内で、自分自身のインスタンス変数やメソッドにアクセスするには、selfを使用します。JavaやC#でいうところの「this」のようなものです

- (void)displayProfile
{
    NSLog(@"私の名前は%@です。年齢は%d歳です。", self.name, self.age);
}

また、親クラスのインスンタンス変数やメソッドにアクセスするときは、superを使用します。これは、Javaでは同じく「super」、C#でいうところの「base」のようなものです。

参考

https://atmarkit.itmedia.co.jp/ait/articles/1404/09/news029.html

makumaakumakumaaku

メソッドの呼び出し方法2

メッセージ([]カッコで囲むアレ)でメソッドを呼び出すことも可能
=> @propertyを使った方がわかりやすそうなので、使わない方針でいきます

Dog *dog_A = [[Dog alloc] init];
[dog_A setHeight:100];
makumaakumakumaaku

インスタンス生成方法について

// Before
UILabel *label = [[[UILabel] alloc] init];
// After
UILabel *label = [UILabel new];
UILabel *label = UILabel.new;

いずれも同じ意味です。newはalloc+init呼び出しの短縮メソッドです。またプロパティー呼び出しを行うことで[UILabel new]記法を回避し、UILabel.newというRuby風の記述を実現しています。

参考記事を読んで色々考えた結果
=> UILabel *label = [[[UILabel] alloc] init];この形で良いかなと思いました。

参考
https://marycore.jp/prog/objective-c/class-method-new/

allocとinitについて

alloc

変数を生成するにあたってメモリを確保すること。

init

生成した変数にデータを保存できるようにすること。

https://qiita.com/kentarokentaro/items/6dea25565281b8ada0f8