24/7 twenty-four seven

iOS/OS X application programing topics.

Objective-C でサブクラスのインスタンスから任意のスーパークラスのメソッドを呼ぶ

サブクラスのインスタンスからポリモーフィズムを無視して任意のスーパークラスのメソッドを呼びます。


↓ 下のように Shape クラスと Shape クラスを継承した Path クラス、および Path クラス を継承した Circle があります。
それぞれのクラスで draw メソッドをオーバーライドしています。

////////////////////////////////////////////////////////////////////////
#pragma mark - Shape
////////////////////////////////////////////////////////////////////////
@interface Shape : NSObject
@end

@implementation Shape

- (void)draw {
    NSLog(@"%@", @"Shape.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Path
////////////////////////////////////////////////////////////////////////
@interface Path : Shape
@end

@implementation Path

- (void)draw {
    NSLog(@"%@", @"Path.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Circle
////////////////////////////////////////////////////////////////////////
@interface Circle : Path
@end

@implementation Circle

- (void)draw {
    NSLog(@"%@", @"Circle.");
}

@end


↓ Circle クラスのインスタンスから draw メソッドを呼び出すと Circle クラスの draw メソッドが実行されて "Circle." と出力されます。
たまに多態性を無視してスーパークラスや、スーパークラスのさらにスーパークラスのメソッドを実行したいということってありますよね。
そういうときは 対象メソッドの IMP (メソッドを参照する関数へのポインタ) を使います。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    Shape *shape = [[Circle alloc] init];
    [shape draw]; // => Circle.
    
    SEL selector = @selector(draw);
    
    void(*pathFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Path instanceMethodForSelector:selector];
    pathFunction(shape, selector); // => Path.
    
    void(*shapeFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Shape instanceMethodForSelector:selector];
    shapeFunction(shape, selector); // => Shape.
}

@end

↑ IMP の定義のままだと ARC が戻り値を retain しようとするので、戻り値が void の関数ポインタにキャストしています。


↓ 上記のコードの出力は下記になります。

2012-10-23 13:15:20.632 Monomorphism[50033:c07] Circle.
2012-10-23 13:15:20.633 Monomorphism[50033:c07] Path.
2012-10-23 13:15:20.633 Monomorphism[50033:c07] Shape.


サブクラスのインスタンスからポリモーフィズムを無視して任意のスーパークラスのメソッドが呼べました。
↓ 試したコードの全体を載せておきます。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Shape
////////////////////////////////////////////////////////////////////////
@interface Shape : NSObject
@end

@implementation Shape

- (void)draw {
    NSLog(@"%@", @"Shape.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Path
////////////////////////////////////////////////////////////////////////
@interface Path : Shape
@end

@implementation Path

- (void)draw {
    NSLog(@"%@", @"Path.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Circle
////////////////////////////////////////////////////////////////////////
@interface Circle : Path
@end

@implementation Circle

- (void)draw {
    NSLog(@"%@", @"Circle.");
}

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    Shape *shape = [[Circle alloc] init];
    [shape draw]; // => Circle.
    
    SEL selector = @selector(draw);
    
    void(*pathFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Path instanceMethodForSelector:selector];
    pathFunction(shape, selector); // => Path.
    
    void(*shapeFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Shape instanceMethodForSelector:selector];
    shapeFunction(shape, selector); // => Shape.
}

@end

iOS 6.0 と iOS 5.x の両方で動作するアプリケーションをビルドする設定

iOS 4.0 と iPhone OS 3.x の両方で動作するアプリケーションをビルドする設定 - 24/7 twenty-four seven

↑ こちらも参考に
iOS 4.0 が登場したくらいのときに上の記事を書いて、仕組みは変わってないのですけど Xcode 4.x 系だと UI が変わってるので現在のやり方をまとめます。

ベース SDK と Deployment Target を設定する

プロジェクトの "Build Settings" で "Base SDK" を "Latest iOS" にします。
前にも書きましたが、ベース SDK は最新を指定したほうがいいです。


プロジェクトの "Info" で "Deployment Target" をサポートする OS の最も低いバージョンにします。
(下の場合は iOS 5.0 以降で動作する。)


今なら、Base SDK 6.0 でビルドして、Deployment Target を 5.0 または 5.1 にするのが効果的でしょうか。2世代サポートということで。

新しい Framework を Weak Link (Optional) に設定する

古い環境には含まれていない Framework をリンクしていると、Dynamic Linker がシンボルのロードに失敗してアプリケーションが起動しません。
その場合は、新しい OS にのみ存在する Framework を Weak Link に設定します(今回は Social.framework)。

↑ リンクの設定はプロジェクトではなくターゲットに対してしか行えないので、ターゲットを選択して "Build Phases" > "Link Binary With Libraries" から設定します。
リンクするフレームワークを設定して、 Required > Optional に変更します。

OS のバージョンごとに処理を分岐する

古い API には存在しないセレクタを呼び出したり、クラスを参照したりするとクラッシュしますので必要に応じて処理を分岐します。

- (void)tweet:(id)sender
{
    Class clazz = NSClassFromString(@"SLComposeViewController");
    if (clazz) {
        SLComposeViewController *controller = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
        [self presentViewController:controller animated:YES completion:nil];
    } else {
        TWTweetComposeViewController *controller = [[TWLandscapeTweetComposeViewController alloc] init];
        [self presentViewController:controller animated:YES completion:nil];
    }
}

Auto Layout に注意

Auto Layout は iOS 6 以降しか使えません。Auto Layout を使用していると iOS 5.x などで起動時に関連のクラスが無くてクラッシュします。StoryBoard を新しく追加したときなどにうっかり Auto Layout のチェックをつけたままにしてしまったりするので注意しましょう。

iOS 6 では Supported interface orientations の順番に注意!

最近の Xcode ではアプリケーションが対応しているデバイスの向きをターゲットの Summary 画面から GUI を用いて設定できるようになりましたが、ここから設定する場合はボタンを押す順番に注意する必要があります。

というのも、この画面で設定した内容は、Info.plist の Supported interface orientations (UISupportedInterfaceOrientations) に反映されるのですが、この項目は Array の値で順番が起動時の状態に影響するからなのです。


上記の画面の状態になるように、ボタンを左から順に押していった場合、Info.plist の UISupportedInterfaceOrientations は下記のようになります。これは新規プロジェクトを作成した場合のデフォルト値です。


今度は同じ状態になるように、ボタンを「右から」順に押していきます。すると Info.plist は下記のようになります。
値の順番が変わっています。


実はこの順番は起動時の画面の向きに関係していて、iOS 6 では一番先頭に指定されている画面の向きで起動することになります。
つまり、前の例では縦画面で起動するのですが、後の例では横向きで起動することになります。


別のキーに Initial interface orientation (UIInterfaceOrientation) というものがあって、こちらを指定すると初期状態を指定できそうですが、試したところ iOS 6 ではどうもこの値は無視されるようです。

iOS 6 ではグループスタイルのテーブルビューの背景色がこっそり非推奨になっている。


iOS 6 では上記のカラーを生成するメソッドがヘッダのコメントでひそかに deprecated になっています。

UIInterface.h

// Group style table view backgrounds can no longer be represented by a simple color.
// If you want to have a background in your own view that looks like the table view background,
// then you should create an empty table view and place it behind your content.
+ (UIColor *)groupTableViewBackgroundColor; // This method will be deprecated during the 6.0 seed program


ドキュメントの記載は変わってないのですが、実際に使ってみると今までは下記のコードでピンストライプのカラーが設定されていましたが、iOS 6 だと真っ黒になってしまいます。

self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];


この背景色を使いたい場合は空のテーブルビューを設定しろということなので、iOS 6 から代替のコードは下記のようになります。

UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
[self.view addSubview:tableView];

はやりのシンボルフォントを iOS で画像として扱える SymbolFontKit を公開しました。

kishikawakatsumi/SymbolFontKit · GitHub

ScreenShot1

↑ シミュレータに表示されている画像やツールバーのボタン、タブバーのアイコンは全てフォントです。

シンボルフォントとは要するにアイコン画像などをフォント形式にしたものです。
Webだと最近のブラウザだとWebフォントが使えるので、利用者の環境にフォントがインストールされていなくても使えるので、最近は解像度非依存ということもあっていろいろな Github や Twitter などいろいろなサイトで利用されています。

シンボルフォントについて詳しくは下記のリンク先などを見てください。
Ligature Symbols 〜ほんとにべんりなフォントのはなし〜
【完全版】Ligature Symbols フォントセットの自作方法 - くらげだらけ
Ligature Symbols

で、フォントなのでベクターデータのためどんな解像度でもキレイに表示されることや、1つのアイコンで色違いや別のサイズを表示することが簡単だったり、はやっているので様々なデザインのシンボルフォントが使いやすいライセンスで入手できるなど、iOS でも利用できたら便利だと思って作りました。

使い方

  1. SFKImage.h/m をプロジェクトにコピーします。
  2. 利用したいフォントファイルをプロジェクトにコピーします。
  3. 上記でコピーしたフォントファイルのファイル名を UIAppFonts をキーにして Info.plist に追加します。

API は UIImage 互換なので UIImage と同様の使い方ができます。(実は現在は imageNamed: 以外のインスタンス化はできません)

SFKImage *image = [SFKImage imageNamed:@"print"];


インスタンス化した SFKImage オブジェクトは UIImage のインスタンスと同様に UIButton や UIImageView、UITabBarItem などに直接設定することができます。

self.imageView1.image = [SFKImage imageNamed:@"share"];

UITabBarItem *calendarTabBarItem = [[UITabBarItem alloc] initWithTitle:@"calendar" image:[SFKImage imageNamed:@"calender"] tag:1];
UITabBarItem *globeTabBarItem = [[UITabBarItem alloc] initWithTitle:@"globe" image:[SFKImage imageNamed:@"globe"] tag:2];
_tabBar.items = @[calendarTabBarItem, globeTabBarItem];


フォントのレンダリングは実際に画面に描画されるときまで遅延されるので、UIImage と違って、1つのインスタンスを途中で色や大きさを変えたりできます。

SFKImage *image = [SFKImage imageNamed:@"compass"];
image.size = CGSizeMake(20, 20);
image.color = [UIColor redColor];
self.imageView6.image = image;
    
image.size = CGSizeMake(40, 40);
image.color = [UIColor yellowColor];
self.imageView7.image = image;
    
image.size = CGSizeMake(80, 80);
image.color = [UIColor blueColor];
self.imageView8.image = image;


UIImage として振る舞うためにちょっと無茶をしているのでそのまま AppStore の審査に通るかどうかは「?」ですが近いうちに適当なアプリを提出して調査したいと思います。


SymbolFontKit は第1回iphone_dev_jp東京 iPhone/Mac Hackathon 〜みんなが幸せになるハッカソン〜の成果物です。

Objective-C や Xcode の新しい機能のほとんどは iOS4 以前でも動く

Objective-C Feature Availability Index に一覧表が載ってるのですが、ARCのWeak Reference 以外は iOS 4 以上、ほとんどはすべての iOS バージョンで動くので、古い環境を気にせずにジャンジャン使ってしまってOKです。

新しいリテラルや Subscripting は断然コードが書きやすく読みやすくなるのでこれはうれしいですね。


Objective-C Feature Availability Index

FeatureTools versionsOS X deploymentiOS deployment
Automatic Reference Counting (ARC)Xcode 4.2
(LLVM Compiler 3.0)
Requires modern runtime
Deploys back to OS X v10.7
Deploys back to iOS 5
Automatic Reference Counting without zeroing weak reference (“ARCLite”)Xcode 4.2
(LLVM Compiler 3.0)
Requires modern runtime
Deploys back to OS X v10.6
Deploys back to iOS 4
Default synthesis of @property instance variables and accessor methodsXcode 4.4 (LLVM Compiler 4.0)Requires modern runtime
Deploys back to OS X v10.6
Deploys back to iOS 4
Instance variables in class extensionsXcode 4.2
(LLVM Compiler 3.0)
Requires modern runtimeAll iOS releases
Instance variables in @implementation blockXcode 4.2
(LLVM Compiler 3.0)
Requires modern runtimeAll iOS releases
No forward method prototypes needed in @implementation blockXcode 4.3
(LLVM Compiler 3.1)
All releasesAll iOS releases
NSNumber, NSDictionary and NSArray literalsXcode 4.4
(LLVM Compiler 4.0)
All releasesAll iOS releases
@YES and @NO literalsXcode 4.4 and OS X 10.8 or later SDK
Xcode 4.5 and iOS 6 or later SDK
(LLVM Compiler 4.0)
All releasesAll iOS releases
NSDictionary and NSArray subscriptingXcode 4.4 and OS X 10.8 or later SDK
Xcode 4.5 and iOS 6 or later SDK
(LLVM Compiler 4.0)
Requires modern runtime
Deploys back to OS X v10.6
Deploys back to iOS 4

はてなブックマークの ShareKit 拡張を書きました

ShareKit/Classes/ShareKit/Sharers/Services/Hatena at hatena · kishikawakatsumi/ShareKit · GitHub


ShareKit といういろいろな外部サービスとの連携機能を提供するライブラリがあるのですが、それのプラグイン (ShareKit では Sharer と呼びます) として「はてなブックマーク」とリンクを共有するものを書きました。
オリジナルを fork して hatena ブランチにコミットしています。利用するには clone して hatena ブランチに切り替えます。


本家にPull Request を送ったので、もしかしたらマージされるかもしれません。
(既存の Sharer に日本のサービスはなかったのでイマイチ勝手がわかりませんでした。)


↓ 発端は fladdict さんとの下記のやりとりです。


はてなブックマークへの投稿は昔書いたことがあったので、認証を OAuth にしたらあとはカンタンそうだなと思って安請け合いしました。いやまあ、特に難しいことはなかったんですけど。




↑ ちょっとだけハマったのは、付属している OAuth ライブラリ (OAuthConsumer) が不具合のあるリビジョンのもので、シグネチャを組み立てるときに追加の OAuth パラメータの部分を2重に URL エンコードしていました。
そのため、はてなの OAuth は必ずコールバック URL を oauth_callback パラメータで指定する必要があるのですが、シグネチャが invalid になって OAuth が成功しないということがありました。


↓ こちらの不具合は別の Pull Request でパッチを送ったので現在は修正されています。
Fix double URI encode for extra OAuth parameters. by kishikawakatsumi · Pull Request #515 · ShareKit/ShareKit · GitHub



↑ そんなこんなでとりあえず書いてみた割りにはちゃんと使えてるようでよかったです。
OAuth で許可するスコープに write_private を書いたら失敗する点とか気になるところはあるけど。。。



↑ Pull Request しました。↓
Add Hatena Bookmark Sharer. by kishikawakatsumi · Pull Request #521 · ShareKit/ShareKit · GitHub

第1回iphone_dev_jp東京 iPhone/Mac Hackathon 〜みんなが幸せになるハッカソン〜 を開催しました

iphone_dev_jp東京 iPhone/Mac Hackathon : ATND
iPhone_dev_jpで、みんなが幸せになるハッカソンを開催します | fladdict


前回の勉強会で深津さんが「一発もののアプリじゃなくてきちんと使われるライブラリをドキュメント込みで作るハッカソンやったらいいんじゃない?」って話をしていたので、それはすばらしいと思ったのでやってみました。


ハッカソンってやったことなくて、勝手がわからずにかなりギリギリの告知になってしまったのですが、約30名の猛者が集まってくださいました。

とはいえ、私は1日でそんなに書けるものだろうかと不安だったので実はそれまでの1週間である程度メドを立てておこうとか思ってたのですが、意外と時間がなくてぶっつけになってしまいました。
でもなんとかなるもので、やっぱり集中して書いたほうがダンゼン効率がいいんだなあとか今さらながら思いました。


こういう周辺ライブラリは必要だなあと思っててもけっこう普段の時間には書こうと思ってもなかなか筆が進まなかったりするもので、こういう集まりは小規模でもいいので定期的にやっていこうというところを確認できた一日でした。
かなりメリットのあることがわかったので近いうちにまたやると思いますのでまたみなさん集まってくれたらうれしいです。

第3回iphone_dev_jp 東京iPhone/Mac勉強会を開催しました

第3回iphone_dev_jp 東京iPhone/Mac勉強会 : ATND

発表内容と資料のまとめ

http://mtl.recruit.co.jp/blog/2012/07/721_iphone_dev_jp_iphonemacmtl.html
会場を提供してくださったMTLフナミさんのすばらしいまとめです。
すべての資料と発表ごとに分けていただいたUstreamの動画を見ることができます。


↓ Slidrsで共有した資料
第3回 iphone_dev_jp 東京iPhone/Mac勉強会 - Slidrs

ネット配信の録画

↓ ニコニコ動画(プレミアム会員のひとは1週間はタイムシフト視聴できます)
7/21 iPhoneDev-jp 勉強会放送 - 2012/07/21 13:00開始 - ニコニコ生放送

↓ Youtube
http://www.youtube.com/playlist?list=PL286E9699885DAEA8&feature=mh_lolz

CoreData の 属性に現在時刻など固定値以外のデフォルト値を設定する

CoreData のオブジェクトに作成日や更新時刻が自動的に入ったらいいなと思うことってありますよね。固定の値ならばモデルエディタの Defaut の欄に設定しておけば初期値として自動的に設定されますが実行時の現在時刻のように変わる値はモデルエディタでは設定することができません。

このような値をデフォルト値として設定するには NSManagedObject の awakeFromInsert メソッドを使用します。

#import "RecentSearch+CoreData.h"

@implementation RecentSearch(CoreData)

- (void)awakeFromInsert {
    [super awakeFromInsert];
    self.createdAt = [NSDate date];
    self.updatedAt = self.createdAt;
}

↑ このように awakeFromInsert メソッドをオーバライドしておくと、このオブジェクトが新しく作成されたときに自動的に現在時刻が設定されます。


↓ ちなみに、これを書いている途中に発見したのですが、モデルエディタの Default のところに "now" や "today" と入力すると現在時刻が「固定値として」設定されます。


あくまでここで設定できる初期値は固定値なのでイマイチ何の役にたつのか分かりませんが、もしかしたら便利なのかもしれません。

今年のチケット争奪戦が終わったので WWDC チェッカーのソースコードを公開します


kishikawakatsumi/WWDCChecker-Mac · GitHub
kishikawakatsumi/WWDCChecker-iPhone · GitHub


WWDC 2012 がようやく発表されました。
チケットは2時間ほどで売り切れてしまいましたが、なんとか買うことができました。

これまでの傾向から激しい争奪戦になることは分かっていたので WWDC のサイト (WWDC - Apple Developer) を監視して、更新があったら手元の iPhone にプッシュ通知で知らせてくれるアプリケーションを作りました。
今回、無事に役目を果たすことができたので少々の解説をしつつ、来年のためにソースコードを公開します。


↓ ぞくぞくと寄せられる喜びの声



構成は、通知を受けるための iPhone アプリケーションと、常時起動していてサイトを監視し変更があったら通知する Mac アプリケーション の2つのソフトウェアからなります。
↓ iPhone アプリはこんな感じです。

といってもこの画像ではわかりにくいですが、WWDC のサイトに自動的に接続するようにしてあるブラウザです。
別にプッシュ通知を受けるために必要なだけなので特に機能は必要ないのですが、プッシュ通知から起動したあと、すぐに購入することができるようにこうしてみました。

↓ 通知が届いたときの様子


プッシュ通知には Parse.com を使いました。
1,000,000 通知/月までは無料で使えるので今回のような Ad Hoc で限られた人にだけ配布するような用途にはピッタリです。
また Parse は iOS 用には専用の SDK が提供されているので決まったコードを少し書くだけでプッシュ通知が受けられるようになります。


↓ これだけのコードで通知を受けられるようになります。キモはプッシュ通知なのでインストールして通知が有効になってさえいれば、アプリケーションは起動しててもしてなくてもどうでもいいので、これだけでほぼ完成といえます。

@implementation WWDCAppDelegate

@synthesize window;
@synthesize viewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#if !(TARGET_IPHONE_SIMULATOR)
    TESTFLIGHT_TAKEOFF;
#endif
    
    [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert];
    
    [Parse setApplicationId:@"SCRdFC3GKlr9WD5ElC9IGpBZewLxDhrl2zSQOKlu" 
                  clientKey:@"5cRNScoCLbMlnPbgwznv40TxNRJKrVQGmXWwbCH5"];
    
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    
    self.viewController = [[WWDCViewController alloc] initWithNibName:@"WWDCViewController" bundle:nil];
    window.rootViewController = viewController;
    [window makeKeyAndVisible];
    
    return YES;
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)newDeviceToken 
{
    [PFPush storeDeviceToken:newDeviceToken];
    [PFPush subscribeToChannelInBackground:@""];
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error 
{
    if ([error code] == 3010) {
        NSLog(@"%@", @"Push notifications don't work in the simulator!");
    }
    
    NSString *message = error.localizedDescription;
    
    NSBundle *bundle = [NSBundle mainBundle];
    NSDictionary *infoDictionary = [bundle localizedInfoDictionary];
    NSString *appName = [[infoDictionary count] ? infoDictionary : [bundle infoDictionary] objectForKey:@"CFBundleName"];
    
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:appName message:message delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
    [alert show];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 
{
    [PFPush handlePush:userInfo];
}

@end


Mac アプリケーションはいろいろ試しましたが、結局 WebView を使って一定の間隔でサイトにアクセスしてタイトルに変更がないかを比較する、という方式で監視することにしました。
HTTP リクエストを使ってダウンロードしたデータを比較したり、もっと厳密にやろうかと思ったのですがいろいろあってこの方法に落ち着きました。
これを家でつけっぱなしにしている Mac にインストールして準備完了です。


↓ Mac アプリのコードの抜粋です。Mac の WebView は自由にデータにアクセスできるから楽チンですね。Parse は SDK によるアクセスの他に REST API も用意されていて、プッシュ通知も REST API を使って iOS 以外から送信することができます。

- (void)onTimer:(NSTimer *)t
{
    if (!timer) {
        self.timer = [NSTimer timerWithTimeInterval:120.0 target:self selector:@selector(onTimer:) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    }
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://developer.apple.com/wwdc/"]];
    [request setHTTPShouldHandleCookies:NO];
    [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
    
    [[webView mainFrame] loadRequest:request];
}

- (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame 
{
    if ([title isEqualToString:@"Apple Worldwide Developers Conference 2011"]) {
        [self log:@"WWDC Checked... no changes\n"];
    } else {
        [self notify];
    }
}

- (void)notify {
    [self log:@"WWDC 2012 may have been announced!\n"];
    
    NSString *JSON = @"{\"channel\":\"\", \"data\":{\"alert\":\"WWDC 2012 may have been announced!\", \"sound\":\"notify.wav\"}}";
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://api.parse.com/1/push"]];
    [request setHTTPMethod:@"POST"];
    [request addValue:@"ICFdOE4GOlu8WN5KlC0ILpASiqIxShrl6zWKQKlu" forHTTPHeaderField:@"X-Parse-Application-Id"];
    [request addValue:@"ork5uoWbcoYxV8wXz34WLhNfKzmyAGEKTOG4JDKF" forHTTPHeaderField:@"X-Parse-REST-API-Key"];
    [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPBody:[JSON dataUsingEncoding:NSUTF8StringEncoding]];
    
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
     {
         if (error) {
             [self log:error.localizedDescription];
         }
     }];
}


Mac アプリのほうも、最初はウインドウなど何もなかったのですが、ちゃんと動いているのか分からないと心配で監視アプリの死活を監視するためのアプリが必要になって……とキリがないのでウインドウにログ出力をするようにしました。
これで変更があったり、アプリが動かなくなっていることが一目でわかるようになりました。
↓ 以下は発表があった当時の様子です。2分間隔なので誤差はありますがこの記録によると 21:34:32 に発表があったことになります。

売り切れたのがだいたい 23 時半ですので今年は2時間もたなかったということになりますね。

というわけで来年はもはや発表前に売り切れるんじゃないかという話もある WWDC ですが、少しでも有利に争奪戦に参加するためにぜひ有効に活用してみてください。
(上記のコード、および GitHub のコードは Parse.com や TestFlight のトークンを書き換えてあります。試される際は自分のアカウントのトークンに直してご利用ください。)

CoreData の NSManagedObject のサブクラスを変更する場合はカテゴリを使うと便利

CoreDataのモデルクラスはXcodeのモデルエディタから自動生成しますが、生成されたクラスにメソッドを追加したりしたいことがあると思います。
そのとき、自動生成されたファイルを直接変更してしまうと、モデルに変更がありモデルクラスを再生成したときにその変更が上書きされてしまいます。

そこで、カテゴリを使って追加部分は別のファイルに分けておくと、モデルクラスを再生成しても後から追加した部分は上書きされずに残るのでそのまま使えます。


例えば下記のようなクラス (Event.h) があるとして、条件でフェッチするメソッドや、日付をフォーマットして返すメソッドを Event+CoreData.h/m や Event+Formatter.h/m として別ファイルに定義します。

// Event.h

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

@class Favorites, History, User;

@interface Event : NSManagedObject

@property (nonatomic, retain) NSNumber * accepted;
@property (nonatomic, retain) NSString * address;
@property (nonatomic, retain) NSString * catch;
@property (nonatomic, retain) NSDate * endedAt;
@property (nonatomic, retain) NSString * eventDescription;
@property (nonatomic, retain) NSNumber * eventID;
@property (nonatomic, retain) NSString * eventURL;
@property (nonatomic, retain) NSNumber * favorite;
〜(略)〜
// Event+CoreData.h

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

@interface Event(CoreData)

+ (Event *)eventWithEventID:(NSNumber *)eventID;
+ (NSArray *)eventsWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate;

@end
// Event+CoreData.m

#import "Event+CoreData.h"
#import "EPCoreDataManager.h"

@implementation Event(CoreData)

+ (Event *)eventWithEventID:(NSNumber *)eventID {
    EPCoreDataManager *manager = [EPCoreDataManager sharedManager];
    NSManagedObjectContext *context = manager.managedObjectContext;
    
    NSPredicate *predicate = [self predicateWithEventID:eventID];
    
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    request.predicate = predicate;
    
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:context];
    request.entity = entity;
    
    NSArray *results = [context executeFetchRequest:request error:nil];
    
    Event *event = [results lastObject];
    if (!event) {
        event = [[Event alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
        event.eventID = eventID;
    }
    
    return event;
}

+ (NSArray *)eventsWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate {
    EPCoreDataManager *manager = [EPCoreDataManager sharedManager];
    NSManagedObjectContext *context = manager.managedObjectContext;
    
    NSPredicate *predicate = [Event predicateWithStartDate:startDate endDate:endDate];
    
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    request.predicate = predicate;
    
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"startedAt" ascending:YES];
    request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
    
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:context];
    request.entity = entity;
    
    NSArray *results = [context executeFetchRequest:request error:nil];
    
    return results;
}

@end
// Event+Formatter.h

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

@interface Event(Formatter)

- (NSString *)formattedStartedAt;
- (NSString *)formattedEndedAt;
- (NSString *)formattedUpdatedAt;

@end
// Event+Formatter.m

#import "Event+Formatter.h"

@implementation Event(Formatter)

- (NSString *)formattedStartedAt {
    return [[self dateFormatter] stringFromDate:self.startedAt];
}

- (NSString *)formattedEndedAt {
    return [[self dateFormatter] stringFromDate:self.endedAt];
}

- (NSString *)formattedUpdatedAt {
    return [[self dateFormatter] stringFromDate:self.updatedAt];
}

- (NSDateFormatter *)dateFormatter {
    static NSDateFormatter *dateFormatter;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        dateFormatter = [[NSDateFormatter alloc] init];
        NSLocale *locale = [NSLocale currentLocale];
        NSArray *preferredLanguages = [NSLocale preferredLanguages];
        if ([preferredLanguages count] > 0) {
            locale = [[NSLocale alloc] initWithLocaleIdentifier:[preferredLanguages objectAtIndex:0]];
        }
        dateFormatter.locale = locale;
        dateFormatter.dateStyle = NSDateFormatterLongStyle;
        dateFormatter.timeStyle = NSDateFormatterShortStyle;
    });
    
    return dateFormatter;
}

@end

iphone_dev_jp 東京iPhone/Mac勉強会のあきすてさんの資料を共有しました



あきすてさんの依頼で私が代わりにSpeakerDeckで共有しました。
当日は話さなかった Unity Asset Server や Unity+Jenkins の話が載っていますのでぜひご覧ください。

iphone_dev_jp 東京iPhone/Mac勉強会 (@akisutesama) // Speaker Deck

iphone_dev_jp 東京iPhone/Mac勉強会を開催しました

iphone_dev_jp 東京iPhone/Mac勉強会 : ATND

ネット配信の録画(ニコニコ動画のプレミアム会員のひとは1週間はタイムシフト視聴できます)

4/14 iPhonedev-jp 勉強発表会 - 2012/04/14 13:00開始 - ニコニコ生放送


東京にいるのにもう長いことiOSの勉強会してない!名古屋とか大阪とか札幌のほうが活発にやってるなんておかしい!っていう感じのことをボードゲームの会のあとでごはんを食べているときに言ったら「じゃあ、やるかー」ということになって瀧内さん (@takiuchi) がサイバーエージェント・ベンチャーズのStartup Base Camp会議室というステキな場所をとってくれてその場で何人か発表してねってお願いしてなんだかんだで勉強会を主催することになりました。

そんそんさん(@sonson_twit)も書いているのですけど、僕もその場にいたメンバー+5〜10人くらいで考えていたので、あれよあれよというまに100人を超えて200人も参加希望があったときには正直どうしようかと思いました。

きっとその時点で僕がヘタレそうになっているということはお見通しだったのでしょう、しゃちさん(@shachi)をはじめ多くのひとが手伝ってくださったおかげでなんとか無事に開催することができました。
あらためて、ありがとうございました。

ただ、iOSの話っておもしろいことはひととおりやっちゃったよね、みたいな感覚があったのも確かで、たくさん来ていただいたものの期待に沿うような内容になるのかは正直半信半疑だったのですが、ふたを開けてみればそんな心配はまったく杞憂で、エクストリームiOSデベロップメントみたいな発表ばかり集まって非常に内容の濃い勉強会になったのでした。

本当にどの発表も楽しくて、それだけでこの会を開いてよかったなあと思いました。


また、今回はニコニコ生放送によるインターネット配信とフィードテイラーの大石さん(@oishi)のご好意によりSYNCNEL会議機能で資料を同期して閲覧する、ということが実現でき当日会場に来られなかったかたもリモートで参加することができ好評だったようです。

SYNCNELの会議機能を iPhone dev jp 勉強会で御利用頂きました! | 関西/大阪のiPhone・iPadアプリ開発 feedtailor Inc. 社長ブログ
ku-sukeのブログ
iPhonedev-jp 勉強発表会 – ニコニコ生放送を見たよ | Macで遊んでる
倍返しだ! : 『iphone_dev_jp iPhone勉強会を大阪で見る会』に行って来ました。





運営スタッフとしての反省は、事前に会場の下見に行っていればあと20人は参加人数の上限を増やすことができたかもしれないこと、ネットワークの準備と当日の仕切りをおざなりにしてしまったので最初の1〜2時間は安定してネット配信とスライドの同期が提供できなかったことが挙げられます。
非常にすばらしい発表をしてもらっていたのでキチッと準備していればちゃんとした形で最初からもっと多くのひとに聞いてもらうことができたかなあと思っています。


ということで不手際はありましたが、心からやってよかったと思いますし次回も近いうちに開催したいと思ってます。
なので発表をお願いしたら快く引き受けてくださいね!と誰ともなく言ってみる。


あとバッファとして用意していた私の発表はけっきょくしなかったので、近いうちに補足して記事にします。


http://shachi.hatenablog.com/entry/2012/04/14/220813
sonson.jp
ハイスピードXcodeコーディング – iphone_dev_jp東京勉強会 | DOTAPON Blog
MacでかんたんKinect - よぴったんこ
A-Liaison BLOG: iphone_dev_jp 東京iPhone/Mac勉強会でしゃべってきた & 反省
【参加してみた】iOSDevJp勉強会 | RE:START
SYNCNELの会議機能を iPhone dev jp 勉強会で御利用頂きました! | 関西/大阪のiPhone・iPadアプリ開発 feedtailor Inc. 社長ブログ
ku-sukeのブログ
iPhonedev-jp 勉強発表会 – ニコニコ生放送を見たよ | Macで遊んでる
倍返しだ! : 『iphone_dev_jp iPhone勉強会を大阪で見る会』に行って来ました。
iOS/MacOS 開発勉強会 - 情報 -> 知識 -> 経験 の足跡
【おもいっきり!iOS!】iphone_dev_jp 東京iPhone/Mac勉強会まとめ #idevjp - ぬんびりぶろぐ
☆iPhoneアプリ開発者のみなさまへ☆プロモコードでプロモーション!「PromoBook」#idevjp - ぬんびりぶろぐ
わたしを見つけて!アプリのApp Store内のSEO最適化 #idevjp - ぬんびりぶろぐ

当日のタイムテーブル

時間 発表者 タイトル
13:00 – 13:10 @takiuchiさん 会場についての諸注意・説明

(トイレの場所、無線LANの設定、自販機の使い方など)
13:10 – 13:35 @5mingame2さん C++とOpenGL ES 1.1の話
13:40 – 14:05 @kamiyanさん ibisPaint の OpenGL ES 2.0
14:10 – 14:35 @cocoponさん キーボードで完結!ハイスピードXcodeコーディング
14:40- 15:05 @nakamura001さん Unity恐くないよ!!
休憩 (10分)
15:15 – 15:40 @yopita_さん Mac でかんたん Kinect
15:45 – 16:10 @akisutesamaさん ・Unity Asset Server使ってみた

・Unity Asset Server+Jenkins

・Unity Cache Server

・Core Animation

・Blocks

・NSURLConnection
16:15 – 16:40 @novi_さん CoreText のはなし
16:45 – 17:10 @sonson_twitさん iCloud
休憩 (10分)
Lightening Talks
17:20 – @monsukeさん プロモコード配布サービス PromoBook について
@umekun123さん App Store内のSEO最適化について調べてみた
@k_katsumi WWDC Checkerの作りかた(Parse.com でらくらくサーバーサイド)