コメントの書き込みをONにしました。
反映にお時間がかかる場合があります。
技術的なお話や、アプリに対するご感想やご要望等もお願いします。
また、書き込みされたコメントに対してのご返事は必ずしもお約束出来ませんし、著しく送れる事がございますのでよろしくお願い致します。
「小学生の漢字 − その時は習った?」が新しく人名漢字として認められた「巫」に対応しました。
先日の書き込みでXcodeから自動的にExtensionにアタッチ出来ないと書きましたが、出来るようです。
1. まずXcodeからデバッグするExtensionを選び実行します。
2. 次にXcodeのDebugメニューから”Attach to Process”→”By Process Identifier (PID) or Name”を選びます。
3. ここで前回はProcess IDを入力すると書きましたが、Extension名を入力します。
4. XcodeでExtensionのソースコード中にブレークポイントを指定すれば、実機でExtensionを起動した時に適切にプレークポイントで止まります。
新しいiPhoneやiOS8のリリースも正式にアナウンスされドタバタしています。
iOS8の新機能、Extension。
例えば「写真」アプリで写真を選択し、アクションボタンをタップすると
このようにTwitterやFacebookに投稿したり、印刷したりする機能が現れます。
iOS8ではアプリケーションが提供するExtensionを追加出来るようになりました。
例えばiOSが提供している以外のSNSに投稿するShare Extensionを追加したり、画像にフィルターを掛けるAction Extensionを提供したり出来るようになりました。
さて、このExtension、Appleのアナウンスではホスト・アプリを選んでXcodeで起動させればソースコードレベルでデバッグ出来る事になっているのですが、私がドキュメントをちゃんと読みこなせていないのか、なかなかうまくデバッグする事が出来ません。
そこで強引にデバッグする手法です。
まず、Extensionが稼働しているプロセスを特定するため、PIDを取得します。
viewDidLoadの最初にでも下記のようなコードを埋め込みます。
- (void)viewDidLoad { [super viewDidLoad]; #ifdef DEBUG pid_t pid; pid = getpid(); NSLog(@"%s : pid = %ld", __func__, (long)pid); #endif
次にXcodeからExtensionを実行します。
するとホストアプリをどれにするか聞いてきます。
UIActivityViewControllerを使ってAction Extensionを起動出来るアプリを選んでやります。
次にコンソールのメッセージを見るためにXcodeのWindowメニューからDeviceを選びます。
先ほどでバッグ用に起動したデバイスで目的のExtensionをタップし、Active Extensionを起動します。
コンソールには先ほどviewDidLoadに仕込んだメッセージが表示され、Prosess IDが 4924 だと判明しました。
“Debugメニュー” -> “Attache To Process” -> “By Process ID(Identifier) or Name..”
で先ほどのPIDを指定して目的のプロセスにAttacheします。
以上でブレークポイントを掛けてデバッグ出来るようになります。
ただ、この方法ではExtensionが起動した直後からデバッグを行う事は出来ません。
今回の事で分かったのは、Action Extensionはホスト・アプリの中で動いているのではなく、別プロセスとして立ち上げられていると言う事です。
これによるメリットはホスト・アプリが非常にメモリーを使う物であっても、それに影響されずメモリーなどの資源を使う事が出来ると言う事です。またAction Extensionにバグがあり、強制終了させられた場合でもホスト・アプリの動作には影響が無いのでしょう。
Swiftには#defineがありません。
#ifdef 〜 #else 〜 #endifも#if 〜 #else 〜 #endif に置き換わっています。
何が問題かと言うと、例えば Precompiled Header で下記のように記述する事が出来ません。
#ifdef DEBUG #else #endif
まぁSwiftではヘッダファイルという概念が無いのですからしょうがないと言えばしょうがないのですが。
またこの DEBUG も今まではBuild Configuration をDebugにすればObjective-Cのコンパイルオプションとして -DDEBUG=1 が付くのですが、Swiftのコンパイルではこれは意味が無いようです。
しかしこれはBuild Settingで下記のように”Swift Compiler – Custom Flags”に “Other Swift Flags” に追加すれば解決します(リリース時にはデフォルトで設定しておいてもらいたいですね)。
では例えばデバッグ時にメッセージを出力して出荷時にはそれを自動的に消すには?
Swiftではクラスに属さない「関数」を記述する事も出来るようです。
例えば “GlobalFunction.swift” というようなソースにデバッグ用の関数を定義しておけば、Swiftはヘッダファイルを必要としていないため、自動的に全てのコードの中で参照出来るようになります。
#ifdefの中で#defineとしてマクロを組むのとは違って、リリース版にも何もしないLOG()が呼び出されてしまいますが、無いよりは良いですね。
もしかしたらもっと良い方法があるかもしれませんが…
import Foundation #if DEBUG func LOG(msgs:Any) { println("LOG: \(msgs)") } #else func LOG(msgs:Any) { } #endif
CoreFoundationを含めてiOSやOSXのAPIのほとんどはNSObjectのサブクラスとして提供されています。そのため新しい画面をSwiftで追加する場合にはUIViewControllerのサブクラスを作るしかありません。UIViewも同じですね。
ですのでどうしてもNSObjectとは切っても切れません。言い換えればObjective-Cのクラスとは切り離せません。
とはいえ前回のコラムでも書きましたがSwiftは親クラスを持たないクラスを作成する事が出来ますので、プログラミング言語と実行環境(Framework)が分離出来たと言えると思います。その事はiOSやOSX以外の環境でももしかしたらSwiftが主流の開発言語になる可能性も否定出来ないって事だと思います。
さて、Objective-CのクラスをSwiftで使う方法です。
標準のFrameworkを使うにはimport文でフレームワークを指定してやれば良いだけです。
import UIKit import CoreData
しかし自前のFrameworkやクラスをSwiftで使うためには最初のコラムで紹介しましたように<#ProductModuleName#>-Bridging-Header.h に #import 文を追加してやる必要があります。
このファイルはObjective-CのプロジェクトにSwiftのソースを追加してやる時に自動的に作成されます。
Objective-CのクラスをSwiftで使うための前準備はこれだけです。
インスタンス化
しかしSwiftとObjective-Cではクラスのインスタンス化の方法が違うので注意する必要があります。
Swiftではalloc やinit メソッドを呼び出す必要はありません。
var obj:ObjectiveC-Class! obj = ObjectiveC-Class()
これだけです。
これだけでObjective-Cのクラスのallocやinitを自動的に呼び出してくれます。
しかしクラスによってはinitメソッドに引数を持たせているものもあります。標準Frameworkではそのようなinitメソッドは下記のようなフォーマットになっています。
-(id)initWithParam:(classname)parameter;
WithParam が付いています。
Objective-CのこのようなinitメソッドをSwiftから呼び出す場合には、withのあとの文字列をパラメータ名としたクラス・メソッドに置き換えてくれます。
var obj:ObjectiveC-Class! obj = ObjectiveC-Class(param:parameter)
iOSでViewを作るような場合で比較してみます。
CGRect viewRect = CGRectMake(10, 10, 100, 100); UIView* myView = [[UIView alloc] initWithFrame:viewRect];
let viewRect = CGRect(x: 10, y: 10, width: 100, height: 100) let myView = UIView(frame: viewRect)
これは自分で作ったクラスがWith〜が付いたinitメソッドを持っていても、自動的に変換されます。
いきなり出てきたSwiftですので、今はやはり取っ付きにくい部分も多くあります。ですがかなり柔軟な開発言語だと思います。使いこなせるようになれば便利じゃないかと感じています。swiftは現在のところXcode 6.0 betaでしか使えません。beta版のXcodeでは製品をAppStoreに上げる事は出来ませんが、古いiOSに対応したアプリも作成できるようですので、是非習得しておきたいと思います。
前回はNSObjectから派生したSwiftのクラスをObjective-Cで使うための方法を紹介しました。
Swiftは親クラスを持たないクラスを作る事も出来ます。その場合には気をつけなければいけない事があります。
@objc(<#name#>)
まず、クラスの宣言の前に@objc(<#name#>)を付けて、Objective-Cから<#name#>というクラス名で使用出来るようにしなければなりません。マニュアルによると@objc(<#name#>)はSwiftで宣言したのとは違うクラス名でアクセス出来るようにも出来ると書かれているのですが実際に試してみると上手くいきません。とりあえず問題はありませんのでクラス名にします。
alloc, init
Objective-Cでクラスを扱う際には全てのクラスはNSObjectのサブクラスになります。従ってインスタンス化する時には
以下のようにしていました。
OneClass* obj; obj = [OneClass alloc]; obj = [obj init];
しかしこのような方法でNSObjectを親クラスに持たないクラスをインスタンス化する事は出来ません。なぜならallocとinitはどちらもNSObjectクラスのメソッドだからです。
ではどうするか?
Swiftのクラスにクラス・メソッドを作ってやり、その中でクラスのインスタンスを返してやります。
import Foundation @objc(SwiftClass02) class SwiftClass02 { class func create() -> SwiftClass02 { return SwiftClass02() } }
#import "ObjectiveC-Class.h" #import "Swift03-Swift.h" @implementation ObjectiveC_Class -(void)method { SwiftClass02* swiftClass02; swiftClass02 = [SwiftClass02 create]; } @end
次はSwiftの中でObjective-Cのクラスを使う方法を考えてみます。
WWDC 2014で現れた新しいプログラミング言語のSwift。正直なところ、こんなのが出てくるとは思いませんでした。
特徴については様々ところで書かれていますので紹介しません。
とりあえずどんなモンか試していきます。
全く新しいアプリを書くなら全てSwiftで書くという事も可能でしょうが、バージョンアップの場合に全てを一からSwiftで書き直すと言うのはシンドイ話です。
徐々にSwiftにシフトしていく事を前提に、新しいSwiftのクラスをObjective-Cで使う事を試してみます。
まず新しくSwiftで書かれたクラスを追加します。
Swiftのクラスを作成するにはLanguageをSwiftにしてやるだけで作成されます。
最初にSwiftのクラスを作成するとこのようにBridge-Headerを作成して設定を変更するかどうかを聞いてきますので迷わず「Yes」をクリックします。
すると”ProductModuleName-Bridging-Header.h”と言うファイルが作成され、プロジェクトに追加されます。またBuild Settingの中の”Objective-C Bridging Header”に追加されます。これはSwiftの中でObjective-Cのクラスを使用する場合に必要になるもので、今回は説明を省きます。
必要なのは”ProductModuleName-Swift.h” というファイルです。マニュアルにはこのファイルをObjective-Cのコードの先頭で#importしろと書かれているのですが、面倒なのはこのファイルはプロジェクトの中には現れません。ビルドを行わなければ(クリアなどをしても)ファイル自体が存在しないようです(多分)。
ですので構わずObjective-Cのコードの先頭に追加してしまいます。”ProductModuleName”は各プロジェクトの名前ですので必要に応じて置き換えてください。
この方法で、NSObjectをルートクラスとするようなクラスは使えるようになります。
新しくSwiftのクラスを追加したりメソッドやプロパティを追加削除したり、クラスを削除した場合にはXcodeがこのヘッダファイルを自動的に編集してくれるようです。勝手に編集されないようにファイルを見えないようにしているのでしょう。またSwiftのクラスを編集するとアップデートするまで若干時間がかかるようで、すぐにObjective-Cの方で新しいメソッドを参照しようとするとエラーが出ますが、ビルドすればすぐに解決するようです。
import Foundation class SwiftClass01: NSObject { func method01() { println("method01 has benn called"); } }
#import "ObjectiveC-Class.h" #import "Swift03-Swift.h" @implementation ObjectiveC_Class -(void)method { SwiftClass01* swiftClass01; swiftClass01 = [SwiftClass01 alloc]; swiftClass01 = [swiftClass01 init]; [swiftClass01 method01]; } @end
しかしSwiftは親クラスを持たないクラスを作成する事も出来ます。その場合にはちょっと変わってきます。
それはまた次に。
iOS7.1からEnterprise Distributionを行うにはHTTPSを使う必要が出てきました。 開発中のアプリケーションを実機でテストする場合にもとても便利な機能なので残念です。今までは手元にあるWindows XPのIISを手軽に使っていたのですが、私自身IISの深い知識は無いのでどのようにHTTPSを稼働させるのか良くわかりません。そのため、Mervericsに標準でインストールされているapatchでHTTPSサーバを立てる事にしました。
以下はその手順です。
まずはスーパーユーザーになり以下の手順で公開鍵などを作成してきます。
1. 秘密鍵の作成
パスフレーズの入力を求められますが、これは忘れないようにして下さい。その後の処理にも必要になります。
root# openssl genrsa -out server.key -aes128 1024 Generating RSA private key, 1024 bit long modulus ......................++++++ ......++++++ e is 65537 (0x10001) Enter pass phrase for server.key: Verifying - Enter pass phrase for server.key:
2. 秘密鍵でCSRを作成
Common Nameは本来はドメイン名を使いますが、ここではDNSで管理していないマシンを使うため、IPアドレスをそのまま指定しています。
root# openssl req -new -key server.key -out server.csr Enter pass phrase for server.key: You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:JP State or Province Name (full name) [Some-State]:Saitama Locality Name (eg, city) []:Saitama Organization Name (eg, company) [Internet Widgits Pty Ltd]:FuturesVision Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:192.168.11.3 Email Address []:xxx@futuresvision.net Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []:
3. 証明書の認証要求の作成
毎年更新するのも面倒ですので、この例では有効期限を約100年にしました。
root# openssl x509 -in server.cer -days 36500 -req -signkey server.key -out server.crt Signature ok subject=/C=JP/ST=Saitama/L=Saitama/O=FuturesVision/CN=192.168.11.3/emailAddress=xxx@futuresvision.net Getting Private key Enter pass phrase for server.key:
4. apatchの設定
/private/etc/apache2/httpd.conf を編集し、HTTPSを有効にします。490行目のコメントを外します。
# Various default settings #Include /private/etc/apache2/extra/httpd-default.conf # Secure (SSL/TLS) connections Include /private/etc/apache2/extra/httpd-ssl.conf # # Note: The following must must be present to support
/private/etc/apache2/extra/httpd-ssl.confを編集し、先ほど作った証明書を読み込むなのどの設定をします。設定が必要なのは以下の項目です。
- ServerName
- SSLCertificateFile
- SSLCertificateKeyFile
ServerNameはCSRの作成時に指定したCommon Nameと同じにします。
# General setup for the virtual host DocumentRoot "/Library/WebServer/Documents" ServerName <span style="color: #ff0000;">192.168.11.3:443</span> ServerAdmin <span style="color: #ff0000;">xxx</span> ErrorLog "/private/var/log/apache2/error_log" TransferLog "/private/var/log/apache2/access_log"
下記の設定でもわかりますように、先ほど作成したファイルは /private/etc/apache2/config/ の下に置いてあります。
# Server Certificate: # Point SSLCertificateFile at a PEM encoded certificate. If # the certificate is encrypted, then you will be prompted for a # pass phrase. Note that a kill -HUP will prompt again. Keep # in mind that if you have both an RSA and a DSA certificate you # can configure both in parallel (to also allow the use of DSA # ciphers, etc.) <span style="color: #ff0000;">SSLCertificateFile "/private/etc/apache2/config/server.crt"</span> #SSLCertificateFile "/private/etc/apache2/server-dsa.crt" # Server Private Key: # If the key is not combined with the certificate, use this # directive to point at the key file. Keep in mind that if # you've both a RSA and a DSA private key you can configure # both in parallel (to also allow the use of DSA ciphers, etc.) <span style="color: #ff0000;">SSLCertificateKeyFile "/private/etc/apache2/config/server.key"</span> #SSLCertificateKeyFile "/private/etc/apache2/server-dsa.key"
5. apatchを起動します
起動時にはパスフェーズの入力が必要です。
oot# /usr/sbin/httpd -k restart httpd: Could not reliably determine the server's fully qualified domain name, using MacBook-Air.local for ServerName httpd not running, trying to start Apache/2.2.26 mod_ssl/2.2.26 (Pass Phrase Dialog) Some of your private key files are encrypted for security reasons. In order to read them you have to provide the pass phrases. Server 192.168.11.3:443 (RSA) Enter pass phrase:
6. 証明書をiOSデバイスにインストール
iPhone構成ユーティリティを起動します。
「構成プロファイル」を選び、ファイルメニューから新規作成します。
「一般」で名前などを入力したら 「資格情報」から先ほど作成したCSRを読み込みます。
デバイスをUSB接続していれば「デバイス」に表示されますので、 インストールしたいデ
バイスを選んでインストールします。
以上で設定は完了です。
apacheの標準の設定ではDocumentRootは “/Library/WebServer/Documents” になっていま
すので、適当なHTMLを置いて、アプリケーションをダルンロード出来るようにします。
1. ibtoolをカスタムで起動させる。
まず、 “Build Phases” -> “Copy Bundle Resource” から拡張子が “.storyboard” となっているファイルを外します。
次に ”Build Phases” -> “Run Script” に追加して以下のように書き込みます。
ibtool --errors --warnings --notices --minimum-deployment-target 5.0 --output-format human-readable-text --compile 'Sample/Storyboard.storyboardc' 'Sample/Storyboard.storyboard'
このコマンドの出力ファイルは Storyboard.storyboardc です。最後に “c” が追加されています。
シュミレータでアプリがインストールされるディレクトリを参照して頂ければ、Storyboardのファイルはこのように最後に “c” の付いたファイルになっている事が確認出来ると思います。
Storyboard.storyboardc を追加します。
また念のために “Build Phases” -> “Copy Bundle Resource” でこのファイルがコピー対象になっている事も確認します。
2. AppMainAppDelegate.mを書き換えます。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSString *reqSysVer = @"7.0"; NSString *currSysVer = [[UIDevice currentDevice] systemVersion]; if ([currSysVer compare:reqSysVer options:NSNumericSearch] != NSOrderedAscending) { // iOS 7.0 or later UIStoryboard* storyboard; NSString* storyboardFile; storyboardFile = @"Storyboard"; storyboard = [UIStoryboard storyboardWithName:storyboardFile bundle:nil]; self.tabBarController = [storyboard instantiateInitialViewController]; } else { // Override point for customization after application launch. UIViewController *viewController1 = [[[AppMainFirstViewController alloc] initWithNibName:@"AppMainFirstViewController" bundle:nil] autorelease]; UIViewController *viewController2 = [[[AppMainSecondViewController alloc] initWithNibName:@"AppMainSecondViewController" bundle:nil] autorelease]; self.tabBarController = [[[UITabBarController alloc] init] autorelease]; self.tabBarController.viewControllers = @[viewController1, viewController2]; } _window = [UIWindow alloc]; _window = [_window initWithFrame:[UIScreen mainScreen].bounds]; self.window.rootViewController = self.tabBarController; [self.window makeKeyAndVisible]; return YES; }
この例ではxcodeのabBarのテンプレートから生成されるコードを元に追加変更しています。
3. iOS7への移行を考える
これを書いている時点ではまだWWDCも終わっていません。ですのでまだどんなサプライズがあるかはわかりません。しかし今の時点で出てきている部分だけを見てもUIに関しては大きな変更が加わっています。ある意味、総取っ替えの感もあります。もちろんUIButtonなどの今までのクラスは引き続き使う事は出来ます。ですが外観が大きく変わり、サイズも変わっています。全体の整合性を取るためにはiOS7以前と後ではUIを別の物として考えた方が良いのではないかとさえ思います。
XcodeもDeveloper Previewの段階ですのでまだ使い勝手が良くなるのかもしれません。
しかし既に幾つかのアプリをリリースしてしまっている側としてはマイグレーションの方法を早めに見つけておいた方が良いかと思います。
この例ではNIBを使った今までのコードにStoryboardを加えたわけですが、すでにStoryboardを使って開発しているアプリも、iOS7以前と以後のStoryboardを分けて実装する事も可能となります。