cocoaの最近のブログ記事

複数行対応のUITableViewCellを使用するときなど、自動的にセル高さを決定してくれるUITableViewAutomaticDimensionは非常に便利です。
便利なのですが画面回転などによりUITableViewの(内部Cellの)frameが変わってしまう場合、正しい高さを求めてくれなくなってしまいます。
UITableView.frameの変更に合わせて次のおまじないを唱えることで、簡単に対応することが可能です。

[UITableView beginUpdates];

[UITableView endUpdates];


NSMutableArrayに標準で用意されていない、項目の移動メソッドを拡張で実装します。

[NSMutableArray+MoveObject.h]

@interface NSMutableArray (MoveObject)


- (void)moveObjectFromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex;


@end


[NSMutableArray+MoveObject.m]

@implementation NSMutableArray (MoveObject)


- (void)moveObjectFromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex

{

if (fromIndex > toIndex) {

id dummy = [[[NSObject alloc] init] autorelease];

id obj = [self objectAtIndex:fromIndex];

[self insertObject:dummy atIndex:fromIndex];

[self removeObject:obj];

[self insertObject:obj atIndex:toIndex];

[self removeObject:dummy];

}

else if (toIndex > fromIndex) {

id obj = [self objectAtIndex:fromIndex];

[self removeObject:obj];

[self insertObject:obj atIndex:toIndex];

}

}


@end


使用例は次の通りです。
UITableViewでCellの移動を行い、データソースであるNSMutableArrayを適正な順序に並び替える処理を想定しています。

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath

{

[self.dataList moveObjectFromIndex:sourceIndexPath.row toIndex:destinationIndexPath.row];

}



通常UIViewに枠線を付ける場合、次のプロパティを設定するだけで実現可能です。
UIView.layer.borderColor = [UIColor whiteColor].CGColor;
UIView.layer.borderWidth = 1.0;
とても簡単ですが、このプロパティを使うと必ずUIViewの縁4辺に枠線が引かれます。
例えば左側1辺のみに枠線を付けるなどどいう事はできません。

今回は縁4辺それぞれに対して枠線の表示/非表示を切り替え可能なUIViewを設計します。

BorderExView.h

// 枠強調オプション

typedef NS_OPTIONS(NSUInteger, eBorderEmphasisOption)

{

BorderEmphasisOption_None = 0,

BorderEmphasisOption_Top = 1 << 0,

BorderEmphasisOption_Bottom = 1 << 1,

BorderEmphasisOption_Left = 1 << 2,

BorderEmphasisOption_Right = 1 << 3,

BorderEmphasisOption_All = NSUIntegerMax

};


@interface BorderExView : UIView


@property (nonatomic) eBorderEmphasisOption borderEmphasis;

@property (nonatomic, strong) UIColor* borderColor;

@property (nonatomic) float borderWidth;


@end


BorderExView.m

@interface BorderExView ()

{

eBorderEmphasisOption _borderEmphasis;

}


@end


@implementation BorderExView


@synthesize borderColor = _borderColor;

@synthesize borderWidth = _borderWidth;


@dynamic borderEmphasis;

- (eBorderEmphasisOption)borderEmphasis

{

return _borderEmphasis;

}

- (void)setBorderEmphasis:(eBorderEmphasisOption)borderEmphasis

{

_borderEmphasis = borderEmphasis;

[self setNeedsDisplay];

}


- (id)initWithFrame:(CGRect)frame

{

if (self = [super initWithFrame:frame]) {

_borderEmphasis = BorderEmphasisOption_None;

self.borderColor = [UIColor blackColor];

self.borderWidth = 1.0;

}

return self;

}


- (void)drawRect:(CGRect)rect

{

CGContextRef ctx = UIGraphicsGetCurrentContext();

CGContextSetLineWidth(ctx, self.borderWidth);

CGContextSetStrokeColorWithColor(ctx, self.borderColor.CGColor);

if (self.borderEmphasis & BorderEmphasisOption_Top) {

CGContextMoveToPoint(ctx, 0, 0);

CGContextAddLineToPoint(ctx, self.frame.size.width, 0);

CGContextStrokePath(ctx);

}

if (self.borderEmphasis & BorderEmphasisOption_Bottom) {

CGContextMoveToPoint(ctx, 0, self.frame.size.height);

CGContextAddLineToPoint(ctx, self.frame.size.width, self.frame.size.height);

CGContextStrokePath(ctx);

}

if (self.borderEmphasis & BorderEmphasisOption_Left) {

CGContextMoveToPoint(ctx, 0, 0);

CGContextAddLineToPoint(ctx, 0, self.frame.size.height);

CGContextStrokePath(ctx);

}

if (self.borderEmphasis & BorderEmphasisOption_Right) {

CGContextMoveToPoint(ctx, self.frame.size.width, 0);

CGContextAddLineToPoint(ctx, self.frame.size.width, self.frame.size.height);

CGContextStrokePath(ctx);

}

}


@end


使い方は次の通りです。

BorderExView* view = [[BorderExView alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];

[self.view addSubview:view];

view.backgroundColor = [UIColor yellowColor];

view.borderColor = [UIColor grayColor];

view.borderWidth = 5.0;

view.borderEmphasis = BorderEmphasisOption_Left | BorderEmphasisOption_Top;



UITapGestureRecognizerを登録したviewに対してUITableViewをaddSubviewした場合、セルをタップしてもUITapGestureRecognizerが優先されてしまいdidSelectRowAtIndexPathがコールされなくなってしまいます。
このような競合を解消するにはUIGestureRecognizerの次のプロパティを設定します。

UITapGestureRecognizer* viewTap = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(view_Tap:)] autorelease];

viewTap.cancelsTouchesInView = NO;

[self.view addGestureRecognizer:viewTap];

bringSubviewToFrontなどのメソッドでsubviewの表示順を操作することは可能ですが、subviewの数や配置の複雑度が増してくると処理が煩雑になりがちです。


例えば

 ・背景(2views)

 ・情報表示(5views)

 UIボタン群(5views~10views)

 ・広告を上下に1つずつ配置(2views?)

という構造を考えてみましょう。


背景は常に一番下に配置されますが、状況によりviewの表示順を入れ替える必要があります。

情報表示画面は常に最重要な画面を前面に表示する必要があり、頻繁に表示順の入れ替えが行われます。

UIボタン群は最前面に表示している情報表示画面を操作するためのもの、また情報表示画面を切り替えるためのものがあり、viewの数や表示順は状況により変わります。

UIボタンは常に情報表示画面より上に配置し、隠れないことが求められます。

広告はUIボタンを隠さない位置に表示され、常に最前面に配置します。サードパーティ製の広告SDKを用いるため、どのような実装がされているかは不明です。


このような複雑度を持つアプリの場合、もはやbringSubviewToFrontでなんとかしようとするのは現実的ではありません。

insertSubviewexchangeSubviewAtIndexを使うことで多少コード量は減らすことができますが、それでも煩雑であるという印象は解消できないでしょう。

ある情報表示画面を前面に配置したい場合、次の条件でsubviewsから挿入箇所を検索する必要があります。

 ・情報表示画面群の最前面

 ・UIボタン群の1つ背面である

UIボタン群の中で常に最背面にあるボタンが固定なら楽かもしれませんんが、UIボタンは状況により変わります。

もしかすると検索対象としたUIボタンインスタンスそのものがsubviewsに含まれていないかもしれません。


背景の入れ替えについても安易にsendSubviewToBackを用いるのは危険です。

なぜなら今後の仕様追加により背景viewが追加された場合に不具合の原因となる可能性が高くなるからです。


UIボタンの入れ替えも大変です。

広告のview構成が不明であるため、広告viewの1つ背面にinsertするのは危険です。(広告が読み込まれたタイミングでview構成が変わり、もしかしたらviewのインスタンスすら変わっているかもしれません)

そのため現在前面に表示されている情報表示画面を検索する必要が出てきます。


仕様変更も視野に入れて設計することを考えただけでウンザリしてきたのではないでしょうか。


今回はこのような複雑なsubview構成に対応するため、階層構造でsubviewを管理できるUIViewControllerを設計します。

階層は好きな数を自由に追加でき、階層に追加したsubviewはその階層内でのみ表示順の入れ替えが可能です。

たとえbringSubviewToFrontを使ったとしても、その階層より上の階層に存在するsubviewを上書きすることはありません。


先ほどの例を階層構造を用いて実装した場合のイメージを下図に示します。


Hierarchy.png


それぞれのsubviewは対応した階層にaddされます。

先ほどと同じくある情報表示画面を前面に配置したい場合の処理を考えてみましょう。

情報表示階層にaddされている情報表示画面はその上階層であるUIボタン階層を上書きすることはできません。

つまり情報表示画面を前面に表示したいのであれば、単純にbringSubviewToFrontを使うだけで実装することができます。


背景も前面に出したいsubviewbringSubviewToFrontするだけです。

bringSubviewToFrontによる実装であれば、今後背景viewが増えたとしても不具合は発生しません。


UIボタンの入れ変えはどうでしょうか。

UIボタン階層に存在するsubviewを一掃し、新しいUIボタンをUIボタン階層にaddするだけで完了です。


広告がどのようなview構造で実装されていたとしても、それは広告階層の中の問題です。

他の階層が気にする必要はなくなり、実装におけるリスクも随分軽減されます。


階層はUIViewで実装しますが、そのままUIViewを使ってしまうとタッチイベントがブロックされてしまいます。

UIボタン階層の上に広告階層が存在するため、すべてのタッチイベントは広告階層にブロックされUIボタンを押すことはできません。

この問題を解決するため、一切のタッチイベントをスルーするUIView(TouchTransmissionView)を実装します。


TouchTransmissionView.h

@interface TouchTransmissionView : UIView


@property (nonatomic, readonly) NSUInteger hierarchy;


- (id)initWithFrame:(CGRect)frame hierarchy:(NSUInteger)hierarchy;

- (NSComparisonResult)compareHierarchyAscending:(TouchTransmissionView*)view;


@end


TouchTransmissionView.m

@interface TouchTransmissionView ()

{

NSUInteger _hierarchy;

}


@end


@implementation TouchTransmissionView


@synthesize hierarchy = _hierarchy;


- (id)initWithFrame:(CGRect)frame hierarchy:(NSUInteger)hierarchy

{

    self = [super initWithFrame:frame];

    if (self) {

self->_hierarchy = hierarchy;

    }

    return self;

}


- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event

{

UIView* eventView = nil;

UIView *hitView = [super hitTest:point withEvent:event];

if (hitView != self) eventView = hitView;

return eventView;

}


- (NSComparisonResult)compareHierarchyAscending:(TouchTransmissionView*)view

{

if (self.hierarchy > view.hierarchy) return NSOrderedDescending;

else if (self.hierarchy < view.hierarchy) return NSOrderedAscending;

else return NSOrderedSame;

}


@end


subviewを階層構造で管理するためのUIViewController(HierarchyViewController)を実装します。


HierarchyViewController.h

@interface HierarchyViewController : UIViewController


- (void)addSubview:(UIView*)view hierarchy:(NSUInteger)hierarchy;


- (void)bringSubviewToFront:(UIView*)view;


- (NSArray*)subviews;

- (NSArray*)subviewsOfHierarchy:(NSUInteger)hierarchy;


@end


HierarchyViewController.m

#import "TouchTransmissionView.h"


@interface HierarchyViewController ()


- (TouchTransmissionView*)addLayerViewWithHierarchy:(NSUInteger)hierarchy;


@property (nonatomic, retain) NSMutableArray* layerViewList;


@end


@implementation HierarchyViewController


@synthesize layerViewList;


- (TouchTransmissionView*)addLayerViewWithHierarchy:(NSUInteger)hierarchy

{

TouchTransmissionView* newLayerView = [[[TouchTransmissionView alloc] initWithFrame:self.view.bounds hierarchy:hierarchy] autorelease];

newLayerView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin |

UIViewAutoresizingFlexibleLeftMargin |

UIViewAutoresizingFlexibleRightMargin |

UIViewAutoresizingFlexibleTopMargin |

UIViewAutoresizingFlexibleHeight |

UIViewAutoresizingFlexibleWidth ;

[self.view addSubview:newLayerView];

[self.layerViewList addObject:newLayerView];

NSArray* sortedLayerViewList = [self.layerViewList sortedArrayUsingSelector:@selector(compareHierarchyAscending:)];

for (UIView* view in sortedLayerViewList) {

[self.view bringSubviewToFront:view];

}

self.layerViewList = [[sortedLayerViewList mutableCopy] autorelease];

return newLayerView;

}


#pragma mark - public

- (void)addSubview:(UIView*)view hierarchy:(NSUInteger)hierarchy

{

if (self.layerViewList == nil) {

self.layerViewList = [NSMutableArray array];

}

UIView* targetLayerView = nil;

for (TouchTransmissionView* layerView in self.layerViewList) {

if (layerView.hierarchy == hierarchy) {

targetLayerView = layerView;

break;

}

}

if (targetLayerView == nil) {

targetLayerView = [self addLayerViewWithHierarchy:hierarchy];

}

[targetLayerView addSubview:view];

}


- (NSArray*)subviewsOfHierarchy:(NSUInteger)hierarchy

{

TouchTransmissionView* targetLayerView = nil;

for (TouchTransmissionView* layerView in self.layerViewList) {

if (layerView.hierarchy == hierarchy) {

targetLayerView = layerView;

break;

}

}

return targetLayerView.subviews;

}


- (NSArray*)subviews

{

NSMutableArray* subViewList = [NSMutableArray array];

for (UIView* layerView in self.layerViewList) {

[subViewList addObjectsFromArray:layerView.subviews];

}

return subViewList;

}


- (void)bringSubviewToFront:(UIView*)view

{

for (UIView* layerView in self.layerViewList) {

for (UIView* subView in layerView.subviews) {

if (subView == view) {

[layerView bringSubviewToFront:subView];

break;

}

}

}

}


@end



最後にサンプルとしてHierarchyViewControllerを継承したSampleViewControllerを実装し、動作を確認してみましょう。


SampleViewController.h

@interface SampleViewController : HierarchyViewController

@end


SampleViewController.m

typedef enum {

ViewHierarchy_BackGround = 0,

ViewHierarchy_Back,

ViewHierarchy_Middle,

ViewHierarchy_Front,

}eViewHierarchy;


@interface SampleViewController ()


- (void)handleBackgroundView_Tap:(UITapGestureRecognizer*)sender;


@end


@implementation SampleViewController


- (void)viewDidLoad {

    [super viewDidLoad];

UIView* view2 = [[[UIView alloc] initWithFrame:CGRectMake(30, 30, 200, 200)] autorelease];

view2.backgroundColor = [UIColor blueColor];

[self addSubview:view2 hierarchy:ViewHierarchy_Front];

UIView* view1 = [[[UIView alloc] initWithFrame:CGRectMake(90, 90, 200, 200)] autorelease];

view1.backgroundColor = [UIColor redColor];

[self addSubview:view1 hierarchy:ViewHierarchy_Back];

UIView* backgroundView = [[[UIView alloc] initWithFrame:self.view.bounds] autorelease];

backgroundView.backgroundColor = [UIColor greenColor];

[self addSubview:backgroundView hierarchy:ViewHierarchy_BackGround];

UIView* middleView1 = [[[UIView alloc] initWithFrame:CGRectMake(60, 130, 250, 40)] autorelease];

middleView1.backgroundColor = [UIColor yellowColor];

[self addSubview:middleView1 hierarchy:ViewHierarchy_Middle];

UIView* middleView2 = [[[UIView alloc] initWithFrame:CGRectMake(70, 150, 250, 40)] autorelease];

middleView2.backgroundColor = [UIColor grayColor];

[self addSubview:middleView2 hierarchy:ViewHierarchy_Middle];

[backgroundView addGestureRecognizer:[[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBackgroundView_Tap:)] autorelease]];

}


- (void)handleBackgroundView_Tap:(UITapGestureRecognizer*)sender

{

NSArray* subViews = [self subviewsOfHierarchy:ViewHierarchy_Middle];

UIView* backView = [subViews firstObject];

[self bringSubviewToFront:backView];

}


@end


2014-12-02 04.57.22.png

各subviewを配置するための階層はeViewHierarchyで管理しています。

subviewは最背面から順にaddSubviewをしているわけではなく、まったくのチグハグです。

しかし階層により表示順が管理されているため、正しい表示順が保持されます。

背景view(緑色のview)をタップすると、Middle階層に存在するviewの表示順が入れ替わります。

表示順の入れ替えにbringSubviewToFrontを用いていますが、階層内の最前面に移動するだけで決してFront階層を上書きすることはありません。


あまり多くの階層を作ってしまうとパフォーマンスの悪影響が考えられますので、その点には注意してください。

UIViewやUIViewを継承するクラス(UIButtonやUIImageViewなど)は座標の設定にframeプロパティを用います。

例えばユーザが自由に配置した画像の下にボタンが追従するようにしたいなど、UIViewの大きさは変えずに座標のみをいじりたいという事はよくあります。
CGRectOffsetという座標を変えるためのメソッドもありますが、相対的な移動方法である為、絶対値として設定したい場合には不向きです。

絶対値として座標を設定したい場合frame.orizinを変えれば良いのですが、frameはプロパティでありoriginはCGRect構造体のメンバという差があるため
 xxxView.frame.origin = CGPointMake(10, 100);
といった設定はできません。

一度frameプロパティをCGRect構造体として取り出し、originを書き換えたのちにframeプロパティに設定するという面倒な手順が必要となります。
 CGRect newFrame = xxxView.frame;
 newFrame.origin = CGPointMake(10, 100);
 xxxView.frame = newFrame;

ただ座標を変えるだけのためにこのコードを毎回書くのは面倒ですので、座標に直接アクセスできるプロパティを追加します。
以下のコードはUIViewを拡張してpositionというプロパティを追加しています。

【UIView+Position.h】

// positionプロパティを追加したUIView拡張クラス

@interface UIView (Position)


@property (nonatomic) CGPoint position;


@end


【UIView+Position.m】

@implementation UIView (Position)


@dynamic position;

- (CGPoint)position

{

return self.frame.origin;

}

- (void)setPosition:(CGPoint)position

{

self.frame = CGRectMake(position.x, position.y, self.frame.size.width, self.frame.size.height);

}


@end


内部の処理は至極単純で、自身のframeプロパティを書き換えているのみです。
UIView+Position.hをインポートするだけでUIView及びUIViewを継承しているクラスでpositionプロパティが使用できます。

xxxView.position = CGPointMake(10, 100);
アプリを開発するにあたり、いきなり各機能が複雑に干渉しあう本番機能を実装する事は非常にリスキーです。
まずは各機能が仕様を満たせるか、技術的に問題は無いかを確認する目的で単機能のViewControllerを実装する方も多いのではないでしょうか。
例えば録音機能と取得した音量に合わせてマスコットがアニメーションする機能を実装したい場合、録音機能用のViewControllerとアニメーション用のViewControllerを実装するといった具合になります。

確認用のViewControllerが少ないうちはAppDelegateのviewControllerプロパティに見たい画面を毎度設定し直せば済みますが、画面が多くなってくるとそれも煩雑になってきます。
また確認したい画面が変わるだけで毎回ビルドし直すというのも面倒です。できれば各画面に遷移する事ができる初期画面が欲しい所です。

今回は簡単に実装でき、且つ新しいViewControllerも簡単に追加できる初期画面の実装を紹介します。

各ViewControllerはplistで管理します。
SampleViewControllers.plistを追加し、次のようにデータを入力します。
SampleViewControllers.plist
Rootの属性をArrayに変更します。
Rootの下にDictionary型のアイテムを追加し、Dictionary型アイテムの下に「title」と「class」というキーを持つString型のアイテムを追加します。
titleアイテムの値にはViewControllerのタイトルとなる文字列を設定します。
classアイテムの値には表示したいViewControllerのクラス名を設定します。
上図では6つのViewControllerを追加しています。

UITableViewControllerを継承したクラス「SampleManagerViewController」を追加します。

@interface SampleManagerViewController : UITableViewController


@end


上で作成したplistから読み込んだデータソースを管理するプロパティを追加します。

@interface SampleManagerViewController ()


@property (nonatomic, retain) NSArray* dataSource;


@end


viewDidLoadでSampleViewControllers.plistから情報を読み込みます。

- (void)viewDidLoad {

    [super viewDidLoad];

    

NSString* path = [[NSBundle mainBundle] pathForResource:@"SampleViewControllers" ofType:@"plist"];

self.dataSource = [NSArray arrayWithContentsOfFile:path];

}


tableViewのセクション数は1を返します。

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

    return 1;

}


行数はSampleViewControllers.plistから読み込んだViewControllerの数を返します。

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    return self.dataSource.count;

}


各Cellにはtitleを表示します。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];

}

NSDictionary* dic = [self.dataSource objectAtIndex:indexPath.row];

cell.textLabel.text = [dic objectForKey:@"title"];

return cell;

}


Cellタップ時の動作として、NSClassFromString()を用いclassに設定された文字列からClass型オブジェクトを求め、そのViewControllerへ遷移させます。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

{

NSDictionary* dic = [self.dataSource objectAtIndex:indexPath.row];

NSString* className = [dic objectForKey:@"class"];

Class sampleClass = NSClassFromString(className);

UIViewController* sampleView = [[[sampleClass alloc] init] autorelease];

[self.navigationController pushViewController:sampleView animated:YES];

}


これで初期画面の実装は終了です。
後は初期画面をAppDelegateのviewControllerに設定すれば完了です。
画面遷移はpushViewControllerで行っているため、UINavigationControllerのrootViewControllerに初期画面を追加します。

#import "SampleManagerViewController.h"

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

UIViewController* rootView = [[[SampleManagerViewController alloc] init] autorelease];

self.viewController = [[[UINavigationController alloc] initWithRootViewController:rootView] autorelease];

    self.window.rootViewController = self.viewController;

    [self.window makeKeyAndVisible];

    return YES;

}


@end


新しく画面を追加したい場合は、SampleViewControllers.plistに項目を追加してください。
ExtAudioFileを使って録音のファイル書き込みを行う際に
 1. 録音終了したファイルAを閉じる
 2. すぐ新規録音を開始し、録音データをファイルBに書き込む(ExtAudioFileWriteAsync)
このようなケースでAudioRingBuffer::GetTimeBoundsからEXC_BAD_ACCESSが発生する事があります。

ファイルへの書き込みはExtAudioFileWriteAsyncによって非同期に書き込まれる為、古いファイル(A)への処理が完結する前に新しいファイル(B)への書き込みが行われる事で発生するものと考えられます。

解決策として、排他制御(mutex)を用います。
 ※mutexを用いる為にpthread.hをimportする必用があります。
pthread_mutex_init() mutexの初期化
pthread_mutex_lock() mutexのロック
pthread_mutex_trylock() ブロックしないで行うmutexのロック
pthread_mutex_unlock() mutexのロック解除
pthread_mutex_destroy() mutexの削除

排他制御のキーとなるmutexはpthread_mutex_t型で宣言します。
static pthread_mutex_t fileMutex;

録音を始めるより前にmutexを初期化します。
pthread_mutex_init(&fileMutex, nil);

後は排他制御が必用な箇所をロックとアンロック処理で挟み込みます。
ファイルへの書き込みとファイルを閉じる箇所について挟み込むと問題が解決しました。
例)
// ファイルへの書き込み
if (pthread_mutex_trylock(&fileMutex)) {
    ExtAudioFileWriteAsync(...);
}
pthread_mutex_unlock(&fileMutex);

// ファイルを閉じる
pthread_mutex_lock(&fileMutex);
ExtAudioFileDispose(...);
pthread_mutex_unlock(&fileMutex);

最後に必用の無くなったmutexを削除します。
pthread_mutex_destroy(&fileMutex);
iOS8で新しくAVAudioEngineというクラスが追加されました。
AVAudioEngineはAVFoundationに属するクラスです。
AVFoundationは手軽に再生・録音ができた反面、音声データを直接加工したい場合には下位レイヤーにあるAudioToolboxに手を出す必用がありました。
AudioToolboxを用いる場合C言語による記述が必用になる事もありなかなか敷居の高いものでしたが、AVAudioEngineの登場により大抵の処理がAVFoundationで完結できるようになりました。

AVAudioEngineのイメージですが、コアとしてメインミキサー(各種音声データを1つに纏める機能)が存在し、そのメインミキサーに対してブロックを連結するようにインプット(音楽ファイルやマイクからの入力)や各種エフェクトを接続します。
メインミキサーを含めてブロックの事をノード(node)と呼び、ノードとして扱えるクラスはAVAudioMixerNode、AVAudioInputNodeといった具合にAVAudio〜Nodeという名称で用意されています。

各ノードから出力される音声データを直接操作する方法も用意されています。
例えばAVAudioInputNodeからの出力を操作したい場合、AVAudioInputNodeに対してタップ(tap)を接続します。
tapの接続メソッドはblock構文となっており、block内の処理で生データを操作する事ができます。

今回はこのノード群を組み合わせてマイクから音声を拾い、最終的にファイルへ出力する録音機能を紹介します。
録音機能を図式化すると、下図のようになります。
録音機能
コードは下記リンクを開いてください。
 ※climbi.com様のサービスを利用させて頂いております。

今回は単純に録音のみ行っていますが、Tap内で生データに触れる事ができる為例えば入力レベルをゲインしてファイルに書き込むなどの応用ができます。

Blocksで受け取った処理をNSTimerのscheduledTimerWithTimeIntervalで遅延実行させる方法です。
例えば同期読み込み時は即Blocksを実行し、非同期読み込み時にはNSTimerで読み込みを監視した後にBlocksを実行したいといったケースで役立つかもしれません。

typedef void (^CallbackHandler)();


@interface ViewController ()

{

NSTimer* _blockTimer;

}


- (void)blockTest:(CallbackHandler)handler;


@end


@implementation ViewController


- (void)viewDidLoad

{

    [super viewDidLoad];

[self blockTest:^(void) {

NSLog(@"Block処理実行");

}];

}


- (void)blockTest:(CallbackHandler)handler

{

NSLog(@"blockTest 開始");

_blockTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:[NSBlockOperation blockOperationWithBlock:^(void) {

static NSInteger count = 0;

count++;

NSLog(@"%d", count);

if (count > 10) {

[_blockTimer invalidate];

_blockTimer = nil;

if (handler) handler();

}

}] selector:@selector(main) userInfo:nil repeats:YES];

}


@end


Twitterボタン
Twitterブログパーツ