【iOSシングルトンパターン実装】ライティングが遅いのをキレながら待ってくれる娘「記事待ち娘」を作っていくぞ!その8【+リファクタリング】
最初から読みたい人はこちら。
ずっとリファクタリングしたいなと思っていたので、実装しました。もともとViewControllerに「音を鳴らす」という責務を置いていたのですが、これは間違っています。なんどもいうように、View(画面)とController(ボタンなど)の制御をすべきであり、ロジックを書く場所ではないからです。
つまり「このボタンが押された時に、このふるまい(メソッド)を実行する」というコードを1行だけ書けばすむように根本的にリファクタリング(書いたコードを整理すること)を行い、コードの可読性を上げていきます。
というわけで、WGModel(Waiting GirlのModel)というクラスを設計し、そこで時間の管理と音を鳴らすロジックをうつしていきます。
そうしていくことで、Viewは画面の制御、Controllerはボタン等の制御、Modelでロジックの制御ができ、MVCのシングルトンパターン(一つのデザインパターン)が完成するわけですね。コードをみていきましょう。
//! WGModel.h
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface WGModel : NSObject
+ (WGModel*)getSharedInstance;
- (NSString *)countTime;
//! 保持するデータ
@property (nonatomic) NSTimer *timer; //! タイムインターバル変数
@property (nonatomic) int countTimer; //! 経過秒数管理変数
@property (nonatomic) NSString *stringCountTimer; //! 経過秒数出力用文字列
@end
NS_ASSUME_NONNULL_END
//! WGModel.m
#import "WGModel.h"
@implementation WGModel
//! 直接数値を記述してパワーコードにならないよう、あとで定義付けを徹底すること(後で実装)
//! WGModel内で使い回すグローバル変数
NSString *voicePath;
NSURL *url;
NSError *error;
AVAudioPlayer *audio;
+ (WGModel *)getSharedInstance {
static dispatch_once_t pred;
static WGModel *_sharedInstance = nil;
dispatch_once(&pred, ^{
_sharedInstance = [[WGModel alloc] init];
});
return _sharedInstance;
}
- (NSString *)countTime {
if ( _countTimer == 5) {
voicePath = [[NSBundle mainBundle] pathForResource:@"60second_anger_girl" ofType:@"mp3"];
url = [NSURL fileURLWithPath:voicePath];
audio = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[audio play];
return @"30";
}
else if ( _countTimer == 10 ) {
voicePath = [[NSBundle mainBundle] pathForResource:@"180second_anger_girl" ofType:@"mp3"];
url = [NSURL fileURLWithPath:voicePath];
audio = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[audio play];
return @"60";
}
else if ( _countTimer == 15 ) {
voicePath = [[NSBundle mainBundle] pathForResource:@"300second_anger_girl" ofType:@"mp3"];
url = [NSURL fileURLWithPath:voicePath];
audio = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[audio play];
return @"180";
}
else if ( _countTimer == 20) {
voicePath = [[NSBundle mainBundle] pathForResource:@"random_poem_anger_girl" ofType:@"mp3"];
url = [NSURL fileURLWithPath:voicePath];
audio = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[audio play];
return @"0";
}
else if ( _countTimer == 25) {
voicePath = [[NSBundle mainBundle] pathForResource:@"random_poem_anger_girl" ofType:@"mp3"];
url = [NSURL fileURLWithPath:voicePath];
audio = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[audio play];
return @"0";
}
return @"";
}
@end
本当は返す文字列はenumなりdefine使って返すべきです。今はまだそこまでやってないです。で、こういうロジックにしておくとどうなるかというと、- (NSString *)countTimeメソッドをViewControllerで呼び出すだけで結果を受け取れるので便利です。ViewController側の処理はこんな感じ。
//! ViewController.h
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "WGModel.h"
@interface ViewController () {
AVAudioPlayer *audio; //! 女の子の声を制御するための変数
NSTimer *timer; //! タイムインターバル変数
int countTimer;
NSString *stringConuntTimer; //! 経過秒数出力用文字列
WGModel *model;
}
@property (weak, nonatomic) IBOutlet UILabel *wait30secondGirl;
@property (weak, nonatomic) IBOutlet UILabel *wait60secondGirl;
@property (weak, nonatomic) IBOutlet UILabel *wait180secondGirl;
@property (weak, nonatomic) IBOutlet UILabel *thankyouGirl;
@property (weak, nonatomic) IBOutlet UILabel *countTimerLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self timer];
//! 画像の初期化
[_wait30secondGirl setHidden:YES];
[_wait60secondGirl setHidden:YES];
[_wait180secondGirl setHidden:YES];
[_thankyouGirl setHidden:YES];
}
- (void)timer {
[NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(voiceChecker)
userInfo:nil
repeats:YES];
}
- (void)voiceChecker{
model = [WGModel getSharedInstance];
stringConuntTimer = [NSString stringWithFormat:@"%d秒",countTimer];
[_countTimerLabel setText:stringConuntTimer];
countTimer++;
[model setCountTimer:countTimer];
if ([[model countTime] isEqualToString:@"30"]){
[_wait30secondGirl setHidden:NO];
}
else if ([[model countTime] isEqualToString:@"60"]){
[_wait30secondGirl setHidden:YES];
[_wait60secondGirl setHidden:NO];
}
else if ([[model countTime] isEqualToString:@"180"]){
[_wait60secondGirl setHidden:YES];
[_wait180secondGirl setHidden:NO];
}
else {
}
}
- (IBAction)enter:(id)sender {
[_wait30secondGirl setHidden:YES];
[_wait60secondGirl setHidden:YES];
[_wait180secondGirl setHidden:YES];
[_thankyouGirl setHidden:NO];
}
@end
毎秒ごとにvoiceCheckerが起動し、WGModelで保持されている情報から条件分岐して適切な画像を出してくれます。非常に可読性のあるコードになりましたね。音を鳴らすロジックは、さっきも言ったようにWGModel内に記載しているので、ここではあくまで「表示する画像を制御」だけを責務としてもたせているわけです。
コメント書かなくても、だいたいわかるでしょ。理想的なコーティングです。
というわけで以上です。が。
今回はObjective-Cという言語で書いてますが、今はもうSwiftという言語が主流みたいですね。完全に自体遅れのことをやっています。悲しいです。勉強するにはコストがかかるので、ある程度作ったら業者さんにお願いして引き継ぎしようかなと思ってます。
だいたいのアプリの形はできたしね...。