2016年9月26日月曜日

UIImagePickerControllerは自分で閉じる

フォトライブラリから画像を取得するコードをSwift 3.0で書いてるんだけど、うまくいかなかった。

Delegateの設定

if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
     let imagePicker = UIImagePickerController()
     imagePicker.delegate = self
     imagePicker.sourceType = .photoLibrary
     present(imagePicker, animated: true, completion: nil//イメージピッカー表示
else {
     print("フォトライブラリが使えません")
}

上記で
imagePicker.delegate = self
と宣言するために、頭のところでUIImagePickerControllerDelegateと設定するだけじゃダメで、以下のようにUINavigationControllerDelegateも設定してやらないとエラーが出てしまう。

class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate { ~ }

UIImagePickerControllerのviewは自分で閉じよう

イメージピッカーで画像を選択したら、以下のdelegateメソッドが呼ばれるわけだが、
[Generic] Creating an image format with an unknown type is an error
というメッセージが出るばかりでイメージピッカーが閉じて画像をimageViewに表示してくれない。
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    let image = info[UIImagePickerControllerOriginalImage] as! UIImage?
    imageView.image = image
}

と思ったら、
picker.dismiss(animated: true, completion: nil)
ってやって自分でイメージピッカーを閉じてやらにゃいかんのね。
今までObjective-Cでやってた時は自分で閉じてくれてたのに。
なんだろ、複数選択とかに対応したとかかね?
なおピッカーが閉じたところ、隠れてたimageViewには選択した画像がちゃんと表示されてた。
それでも[Generic] Creating an image format with an unknown type is an errorのメッセージは出るんだけど、これはXcode8になってから余計なメッセージが出る現象の一つかもしれないので、実用上問題なければ無視すればいいと思う。

2016年9月21日水曜日

Swift3を使わない方法

Xcode8からデフォルトの言語がSwift3.0になったけど、いろいろ大きく変わるようで資料も少ないし、コンバーターかけても動かないコードが続出したり。

いずれ3.0を覚えなければいけないにしても、とりあえず今までのSwiftで書くやり方があった。

プロジェクトのTARGETS、Build Settings、Swift Compiler - Version、Use Legacy Swift Compiler VersionをYESにしてやるだけ。
これでSwift3を回避できる。
Xcode8以前は2.2だったけど、2.3に上がってはいると思う。

Swift 3 DEATHよ

Xcode8でSwift 3になったので、そっちへの気づいた変更点を書いていくですよ。
コードのトランスレーターダとSwift 2.4だかも選べるけど、3にしない理由はないと思うので、3のだけ書きますよ。

ざっと見たけど、いろいろ変わりやがったな。
全てをチェックしてらんない。
関数の引数にラベルを付ける方向で、NSのサフィックスを取る方向で、関数名を短くしたり、関数名と引数のラベルをセットで意味を持たせたり…
書くのが楽になったり、わかりやすくなったりしてるのかどうかわかんないけど、ちょこちょこ変えないでほしいよね…

今までのアップデートだと、トランスレーターでちょっとした変更点は直してくれたけど、今回トランスレーターかけてもエラーになるところが山のように出たよ!!
何のためのトランスレーターだよ、この野郎!!
みなさん覚悟しましょう(≧∇≦)

indexPathのプロパティをいじる時は as NSIndexPathとしないといけない

let row = indexPath.row - alienPicsKeysArray.count

let row = (indexPath as NSIndexPath).row - alienPicsKeysArray.count

関数の第1引数のラベルがない場合、_ を付ける?

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {


func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

UIColorの指定がcolor()がいらなくなった

vc.bgSkyImageView.backgroundColor = UIColor.clearColor()

vc.bgSkyImageView.backgroundColor = UIColor.clear

列挙型の指定が先頭小文字になった

UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)

UIView.setAnimationCurve(UIViewAnimationCurve.easeInOut)

Booleanのプロパティがis〜になった

vc.talkView.hidden = true
vc.cameraBtn.enabled = true

vc.talkView.isHidden = true
vc.cameraBtn.isEnabled = true

NSTimerがただのTimerに

どうやら全体的にNSのprefixがいらなくなったようだ。
var urouroTimer = NSTimer()

var urouroTimer = Timer()

関数名が変わった

finishTimer = NSTimer.scheduledTimerWithTimeInterval(rnd,
                    target: self,
                    selector: #selector(Cennina.finish),
                    userInfo: nil,
                    repeats: false)
            }


finishTimer = Timer.scheduledTimer(timeInterval: rnd,
                    target: self,
                    selector: #selector(Cennina.finish),
                    userInfo: nil,
                    repeats: false)
            }


CGRectMakeとかが CGRectとかに

CGPointMakeなんかもそう。
ラベルも必ずつくようになったのね。
vc.ufoImage.frame = CGRectMake(0, 0, 200 * vc.倍率, 200 * vc.倍率)

vc.ufoImage.frame = CGRect(x: 0, y: 0, width: 200 * vc.倍率, height: 200 * vc.倍率)

レイヤーのaddAnimationがaddに

vc.view.layer.addAnimation(transition, forKey: nil)

vc.view.layer.add(transition, forKey: nil)


Switch分の書き方

optionalな値を判断するcaseの値の前に?が。めんどくせえ!
switch month {
        case 1: //1
            msgJanuary(day)
        case 2:
            msgFebruary(day)
        default:
            print()
        }

switch month {
        case ?1: //1
            msgJanuary(day!)
        case ?2:
            msgFebruary(day!)
        default:
            print()
        }

UserDefaultsの書き方

setする値が何でもsetだけで良くなったのだね。
読む時は型によって.object とか .boolとか指定しないといけない。
let ud = NSUserDefaults.standardUserDefaults()
if let _ = ud.objectForKey("currentStage")
ud.setBool(soundFlag, forKey: "soundFlag")
ud.setObject(currentStage.rawValue, forKey: "currentStage")

let test = ud.objectForKey("currentStage")

let ud = UserDefaults.standard
if let _ = ud.object(forKey: "currentStage")

ud.set(soundFlag, forKey: "soundFlag")
ud.set(currentStage.rawValue, forKey: "currentStage")

let test = ud.object(forKey: "currentStage")

Info.plistをローカライズする

注意:結論から言うとうまくいきませんでした。

iOS10、Xcode8からプライバシー関連の設定が増え、例えば画像ライブラリにアクセスするコードを書く場合に、あらかじめInfo.plistの必要な項目にユーザーに伝える使用目的を明示するメッセージを書かなければいけなくなった。
(書いたメッセージがアクセス時にアラートで表示される)

ま、そりゃいいんだけど、そこに日本語用、英語用なんて項目はないんで、Info.plist自体をローカライズして日本語用、英語用を作ることになる。
やり方としてはInfo.plistを選んで、右側のインスペクタからLocalize...ボタンを押して云々なのでまあわかるだろう。

でもそれやってビルドしようとしたら、
error: could not read data from 'パス/アプリ名-Info.plist': The file "アプリ名-Info.plist" couldn't be opened because there is no such file.
というエラーが出てビルドできなかった。
メッセージの内容としてはInfo.plistが見つかんないから開けねぇぞってことだけど、ちゃんとplistファイルはある。

調べたところ、Xcodeの以前からのバグで、パスがおかしいんだそうな。
プロジェクトのパッケージ内容を開いてproject.pbxprojを開き、中の該当部分を絶対パスから相対パスに書き直すといいとあったんで見たけど、どうも最初から相対パスになってるっぽい。(参考リンクもう一つ)
一度Localizeのチェックを外し、再度チェックすると直るとか書いてあったんだけど、試してもダメ。

それ以外の情報もないようなので、メッセージは英語と日本語両方入れておく。ダサダサ〜。
やむを得ずこうなった
後で解決方法がわかったら直します。
こんなとこで止まってリリースを遅らしちゃなんねぇ!!

2016年9月20日火曜日

ローカライズをテストする

日本語のほか、世界各国の言語に対応できるわけだが、シミュレータ及び実機でテストする際、いちいちホームの設定画面から言語とリージョンを選んで切り替えてた。

でもそんなことしなくてもXcodeの設定で一時的に変更することができたのな。知らんかったわ。
メニューの Product / Scheme / Edi Scheme...(⌘+<)で開くウィンドウの、RunのOptionsの

  • Application Language
  • Application Region

を変更してからビルドしてやればいい。

2016年9月19日月曜日

変なメッセージ:Xcode8でまだ出るまだ出る…

余計なメッセージを出さないような設定にしても出ちゃうメッセージを載せてく。

Painter Z index: (でかい数値) is too large (max 255)

iOS10シミューレタ、iOS9シミュレータ、XcodeからビルドしたiPad実機で確認
Painter Z index: 4294967169 is too large (max 255)Painter Z index: 4294967199 is too large (max 255)Painter Z index: 4294967170 is too large (max 255)Painter Z index: 4294967170 is too large (max 255)Painter Z index: 4294967169 is too large (max 255)Painter Z index: 4294967169 is too large (max 255)Painter Z index: 4294967170 is too large (max 255)Painter Z index: 4294967199 is too large (max 255)Painter Z index: 4294967199 is too large (max 255)Painter Z index: 4294967170 is too large (max 255)
MKMapViewを表示してると出る。
特にPinを刺し、ある一定以上地図をズームしてる時に、以下のようにずらずらと出る。
Paintっていうからには画面描画の塗りのことかね?
んで、Z indexっていうんだから、X軸、Y軸じゃなくてZ軸の値で。
つまりは地図を表示する過程で妙な値をMKMapViewに渡してしまい、MKMapView側が「デカすぎんぞ!」って警告してるんだと思う。
つまりは俺は悪くない! 悪いのはきっとXcode!

きっとXcode8のバグの一種で、普段なら問題なく、表示されないような些細なメッセージが出ちゃってるんだろう。
追々出ないようになるんじゃないスか?

warning: section "__textcoal_nt" is deprecated

warning: section "__const_coal" is deprecated

それまで出なかったのに、審査に出そうと思ってアーカイブしたら出たWarning。
以下のようなメッセージがいっぱい出てる。
warning: section "__textcoal_nt" is deprecated
        .section __TEXT, __textcoal_nt, coalesced, pure_instructions
                 ^      ~~~~~~~~~~~~~~
note: change section name to "__text"
        .section __TEXT, __textcoal_nt, coalesced, pure_instructions
                 ^      ~~~~~~~~~~~~~~
warning: section "__const_coal" is deprecated
        .section __TEXT, __const_coal, coalesced
                 ^      ~~~~~~~~~~~~~
note: change section name to "__const"
        .section __TEXT, __const_coal, coalesced
                 ^      ~~~~~~~~~~~~~
deprecatedってことは、バージョンが上がって非推奨のやり方になったよってことだけど、どこの何が非推奨なのかてんでわかんないっス。
まあWarningだから無視しとく。(^◇^)ハハハハ。

プライバシー設定をplistにしとかないと落ちる

シミュレータで落ちる!

iOS10、Xcode8になってから、プライバシーに関する機能を使う際に、Info.plistにあらかじめ宣言(みたいなこと)しとかないと、シミュレータで落ちてしまうようになった。

たとえばフォトライブラリにアクセスするようなAPIを使ったら、こんなん出て落ちた。
[access] This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data.
アプリがクラッシュしたのは、プライバシーに敏感なデータにアクセスしようとしたためです。アプリのInfo.plistは、ユーザーにアプリがどのようにこのデータを使うかについて説明している文字列値で、NSPhotoLibraryUsageDescriptionキーを含まなければなりません。
だそうです。

 落ちないようにしましょう

アプリ名-Info.plistにPrivacy - から始まる項目を追加する(探すとメニューで出てくる)。
そこに、フォトライブラリにアクセスする際に表示させるAlertのメッセージを書き込んでおしまい。

そうすると最初のアクセス時に以下のように出て、無事クラッシュせずにアクセスできるようになった。
かあんたん(≧∇≦)
でも、もともとフォトライブラリにアクセスするような場合って、iOS側が確認のアラートを表示してくれてたよね。
メッセージ部分を入力しなければ、デフォルトでいつものメッセージを表示してくれるんだけど、メッセージがないと審査の段階で自動的にアップロードしたビルドが拒否されちゃう
一応英語で書いたけど、他言語対応はどうやるのかね?
しかしめんどくさくなったねぇ。

Info.plistに設定が必要になったらしい機能の一覧はこちらのサイト

Info.plistの候補からまとめてみた(iOS10、Xcode8時点)
  • Privacy - Bluetooth Peripheral Usage Description
    • Bluetoothを使うとき
  • Privacy - Calendars Usage Description
    • カレンダーにアクセスするとき
  • Privacy - Camera Usage Description
    • カメラを使うとき
  • Privacy - Contacts Usage Description
    • 住所録を使うとき
  • Privacy - Health Share Usage Description
    • ヘルスの機能を使うとき
  • Privacy - Health Update Usage Description
    • ヘルスのアップデートを使うとき
  • Privacy - HomeKit Usage Description
    • ホームキットを使うとき
  • Privacy - Location Always Usage Description
    • 位置情報をアプリ裏でも常に使うとき
  • Privacy - Location Usage Description
    • 位置情報を使うとき
  • Privacy - Location When In Use Usage Description
    • 場所に着いた時点(?)の位置情報を使うとき
  • Privacy - Media Library Usage Description
    • メディアライブラリを使うとき
  • Privacy - Microphone Usage Description
    • マイクを使うとき
  • Privacy - Motion Usage Description
    • モーション機能を使うとき
  • Privacy - Music Usage Description
    • ミュージックにアクセスするとき
  • Privacy - Photo Library Usage Description
    • 画像ライブラリにアクセスするとき
  • Privacy - Reminders Usage Description
    • リマインダーにアクセスするとき
  • Privacy - Siri Usage Description
    • Siriを使うとき
  • Privacy - Speech Recognition Usage Description
    • 音声認識機能を使うとき
  • Privacy - TV Provider Usage Description
    • TVプロバイダ機能(そんなのあるの?)を使うとき
  • Privacy - Video Subscriber Account Usage Description
    • ビデオ購読機能を使うとき

2016年9月18日日曜日

変なメッセージ:Xcode8、iOS10でいっぱい出た

iOS10、Xcode8にしたところ、特にシミュレータを使った時に、これまで出なかったわけのわかんないメッセージが100行以上もずらずら出てしまった。

こんなのや⇩
2016-09-19 00:34:50.662934 SkyReporter[2273:1024925] [DYMTLInitPlatform] platform initialization successful2016-09-19 00:34:50.830564 SkyReporter[2273:1024768] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles2016-09-19 00:34:50.831165 SkyReporter[2273:1024768] [MC] Reading from public effective user settings.
2016-09-19 00:35:12.281089 SkyReporter[2273:1024768] Metal GPU Frame Capture Enabled2016-09-19 00:35:12.281607 SkyReporter[2273:1024768] Metal API Validation Enabled2016-09-19 00:35:12.355170 SkyReporter[2273:1024966] [LogMessageLogging] 6.1 <private> 

こんなの⇩
objc[1207]: Class PLBuildVersion is implemented in both /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/PrivateFrameworks/AssetsLibraryServices.framework/AssetsLibraryServices (0x120acb910) and /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/PrivateFrameworks/PhotoLibraryServices.framework/PhotoLibraryServices (0x1208f5210). One of the two will be used. Which one is undefined.2016-09-18 18:12:50.341636 SkyReporter[1207:83865] bundleid: com.galakuta09.SkyReporter, enable_level: 0, persist_level: 0, propagate_with_activity: 02016-09-18 18:12:50.372739 SkyReporter[1207:83865] subsystem: com.apple.siri, category: Intents, enable_level: 1, persist_level: 1, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 0, privacy_setting: 0, enable_private_data: 0
デバッグ用のNSLogとかが埋もれてしまって非常に困る。

メッセージが出る理由

どうやらXcode8の設定が変なために、普段は表示されない各種情報が表示されているようだ。
これを設定すればよし
XcodeのProduct / Scheme / Edit Scheme...メニュー(⌘+<)の、左側のカラムがRun、上の選択肢がArguments、で表示されるEnvironment Variablesに、
Nameを「OS_ACTIVITY_MODE」、Valueを「disable」に設定してやることでほとんど出なくなってくれる。ありがたい。

このありがたい情報はこちらのサイトから。ありがたやありがたや。

でも、今度は逆に実機確認時にNSLogとかの表示が全く出てくれなくなった。
⌘+<ですぐに表示できる設定だから、Xcode8が直ってくれるまでは切り替えて使うしかないか…。アップルのバカ!

シミュレータメニューが変になった

Xcode8にしたら、iOSシミュレータのメニューがおかしくなりましたよ。
なんじゃこりゃ!!

ライブラリのフォルダを消す

~/ライブラリ/Developer/CoreSimulatorフォルダを消して再起動したらいいと聞いたので、消してみた。
中がそのままProfiles/Runtimesになってて、その中がこれ。ここにある白いファイル、iOSシミュレータごとの各種ランタイムで、一個1GB以上ある。HDの整理にもちょうどいいですな。
消したはいいが、完全には戻らなかった。
完全には戻らない

Xcodeから重複シミュレータを消す

XcodeのWindow/Devicesメニューを表示し、重複しているシミュレータを消すのがいいそうです


重複しているのを1個ずつ消す

iOS8とiOS9のシミュレータが消えたようなので、Xcode/Preferences.../Componentsメニューから必要な過去のシミュレータを再ダウンロードして完了。
戻りました
めんどくさいね。

2016年9月15日木曜日

iPhoneとiPadでtableViewの高さを変える

iOS10で、AutoLayoutとSizeClassesで中のLabelとかの大きさはiPhoneとiPadで変えられるけど、tableViewCellの高さを個別に変える方法がわからなかった。

viewDidLoadあたりに以下のコードを書いときゃいい。
デバイスがiPadなら70に、iPhoneなら40にという単純なコード。
せっかくSizeClassesの機能があるんだから、Cellの高さに対しても機能させろよ! と思うのは俺だけじゃあるまい。

if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
    self.tableView.rowHeight = 70;
}
else {
    self.tableView.rowHeight = 40;
}


なお、以下のコードを使うとセルの内容に応じてセルごとに高さを変えてくれるようになったらしいけど、今回のケースではうまくいかなかった。
self.tableView.rowHeight = UITableViewAutomaticDimension;

変なメッセージでた:Assigning to 'AppDelegate *__strong' from incompatible type 'id _Nullable'

iOS10リリースに伴い、Xcode8にしたら、

AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];

って書いたところに以下のWarningが出たよ。

Assigning to 'AppDelegate *__strong' from incompatible type 'id<UIApplicationDelegate> _Nullable'

互換性のないタイプの'id<UIApplicationDelegate> _Nullable'から'AppDelegate *__strong'にアサイン(割り当て)しようとしている
ってことだな。

クラス間で使うグローバル変数みたいなものをAppDelegateクラスに作っといて、それにアクセスするためのおまじないで、iOS9までは問題なかったんだけど。

appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

どうやら型のチェックを厳密にするようになったためらしく、上記のようにキャストしてやったらWarning消えただよ。めでたしめでたし。

iOS10とXcode8でプロビジョニングファイルを求められた

iOS10リリースに伴い、Xcode8をインストールしたら、実機確認で
アプリ名 requires a provisioning profile. Select a provisioning profile for the "Debug" build configuration in the project editor.
Code signing is required for product type 'Application' in SDK 'iOS 10.0'

って2つのエラーが出て起動しなかったよ。
要はiOS10.0に合ったプロビジョニングファイルを使えってことだと思う。

ざっとネットで調べたところ、General、TARGETS、アプリ名のSigningってところにエラーマークが出てた。


Automatically manage signingにチェックを入れて、Enable Automaticボタン押したら二つともエラーが消えてくれた。
アプリが2年以上前の製作だったので、もう切れた古いプロビジョニングファイルに結びついてたのかしら。
Xcode7では問題なく動いてたんだけどな?
んで、手動で管理すべきところを自動にしたということで。

なんにせよ新OS、新Xcodeリリース時は何かと面倒くさい。
リリース直前だったアプリもXcode8でいろいろ誤動作してるし(ノД`)

2016年9月14日水曜日

iOS9シミュレータからメール送れないらしいですわよ、奥さん!

iOS9のシミュレータからMailCompositionServiceを使ってメールを送ろうとしても、メール送信画面が閉じてしまい、
MailCompositionService が予期しない理由で終了しました
ってクラッシュメッセージが出ちゃう。(アプリはクラッシュしない)

調べてみると、どうやら既知の問題のようだ。
実機確認すると大丈夫。
もうiOS10になるっつーのに、直せや、アッポー!

OSを新しくしたら実機確認できなくなった

iOS10が出たってんで、さっそくiPadをアップデートしたですよ。
iTunes経由でやらないと途中で文鎮化するなんて聞いたんで、その通りにしてうまくいきましたよ。

それはともかく、XcodeもXcode8にせんといかんよねーと思ってたんだけど、MacAppStoreでアップデートを確認しても候補に出てこない。
でもやっぱり実機確認しようとすると
Could not find Developer Disk Image
と出てビルドできない。

Xcode8じゃないとiOS10はサポートされてないから、実機確認もできないんだよね。これはOSのアップデートがあるといつものことだ。

あらためてMacAppStoreでXcodeを直接検索してみたら、こっちは新しいXcode8が用意されてる。
なんか釈然としないけど手動でアップデートしてますよ。

Swift3になるのね。
またいろいろ変更点が出てきそうだわなあ。

2016年9月13日火曜日

TextView内のURLリンクを有効にする

TextViewの中にURLとかが書かれてた場合、それをタップしてSafariとかで表示する方法。


  1. TextViewでURLを検知するチェックをオンにする。(Storyboardもしくはコードで)
  2. TextViewを編集不可能にしておく。(Storyboardもしくはコードで)
  3. TextViewのタップジェスチャーを追加し、タップされたら編集可能にし、キーボードを表示するように設定。
  4. Delegateで編集終了を検知し、編集不可能にし、キーボードを隠す。
  5. URLを押すとSafariでリンクが開き(YouTube動画の場合はYouTubeアプリが)、URL以外を押すと編集が可能になる。


あらかじめdelegateを設定
@interface DetailTableViewController : UITableViewController <UITextViewDelegate>

viewDidLoadあたりで
_detailTextView.delegate = self;
//TextViewのLinkを有効にする
//StoryboardのDetectionのLinksをオンにしてもいい
_detailTextView.dataDetectorTypes = UIDataDetectorTypeLink;
//TextViewにタップジェスチャー追加
//通常は編集不可でリンクを有効にし、タップしたら編集可能にするため
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(textViewTap)];

[_detailTextView addGestureRecognizer:tap];


//textViewがタップされたらエディタブルにしてキーボード表示
-(void)textViewTap {
    [_detailTextView setEditable:YES]; //編集可能に
    [_detailTextView becomeFirstResponder]; //キーボード表示
}


//textView編集終了時、キーボードを隠し、元データを更新
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
    [textView setEditable:NO]; //編集不可に(linkを有効にするため)
    [textView endEditing:YES]; //キーボード隠す
    
    return YES;
}


編集不可能な状態じゃないと、Linkを有効に設定していても有効にならない。しかし編集可能になってくれないとTextViewの意味がない。
というわけで、タップジェスチャーとDelegateを使って編集可能、不可能の設定をON/OFFしてやるわけですな。めんどくせぇ。
これくらいOSサイドで一括処理してくんないかしらね?

URLの他にも住所、イベント(日付かな?)、電話番号の認識もあるようだ。いろいろやってみるよろし。

2016年9月5日月曜日

変なメッセージ:This application is modifying the autolayout engine〜

 OpenWeatherMapを使って気象情報をもらう処理が時間がかかることがあるので、
 [NSThread detachNewThreadSelector:@selector(getCurrentWeatherData) toTarget:self withObject:nil];
というように別スレッドで処理するようにしていた。
iOS7、8の頃は良かったが、9になったら以下のメッセージとともにずらずらとオブジェクトの中身みたいなメッセージが表示されるようになった。
This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes.  This will cause an exception in a future release.
バックグラウンドのスレッドでオートレイアウトをいじってると、将来痛い目にあうぞという意味だと思う。
気象情報をラベルなんかに入れる時に、オートレイアウトの設定で文字数に応じてサイズを変更してくれたりするようなので、その関連だろう。

以下のようにdispatch_async()で処理してやったらエラー出なくなった。

Objective-Cのコード
dispatch_async(dispatch_get_main_queue(), ^{
        ここにコードを書く
    });

そもそも、別スレッドの並列処理にしなくても大丈夫そうなので、普通にgetCurrentWeatherDataメソッド呼ぶようにしちゃったけどねw

2016年9月2日金曜日

非同期処理を同期処理に(dispatch_semaphore)

時として不便な非同期処理

画像ライブラリから1枚ずつ画像を読み込んで処理するコードを書いていたのだが、読み込み処理がblock構文を使った非同期処理なので、forループばかりどんどん空回りしてblock内の処理が後回しになり、期待通りの結果になってくれなかった。

for (int i = 0; i < imageURL.count; i++) {
      ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
      [library assetForURL:imageURL[i] resultBlock:^(ALAsset *asset) {
            //ここに読み込み成功時の処理を書く
      } failureBlock:^(NSError *error) {
            //ここにエラー処理を書く
      }];
}

非同期処理を同期処理にする技

これを、処理が一件終わるまで待たせるようにするのが同期処理だ。
dispatch_semaphore(ディスパッチ・セマフォ)ってのを使う。

dispatch = 使者や手紙などを送る
semaphore = 手旗信号

ということなので、それこそ処理の終了待ちを意味するフラグみたいなものをセットして並列処理を制限し、終わった時点でそれをクリアしてやるのだな。

for (int i = 0; i < imageURL.count; i++) {
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
      [library assetForURL:tempURLArray[i] resultBlock:^(ALAsset *asset) {
          //ここに読み込み成功時の処理を書く
          dispatch_semaphore_signal(semaphore);
      } failureBlock:^(NSError *error) {
          //ここにエラー処理を書く
          dispatch_semaphore_signal(semaphore);
      }];
   });
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

}

dispatch_semaphore_create(0)でセマフォを作り、
dispatch_get_global_queue()内のblock構文の中に同期処理させたいコードを書き、
処理が済んだ時点でdispatch_semaphore_signal(semaphore)を実行して次の処理に移っている。
dispatch_semaphore_wait()はどれくらい処理を待つかだ。ここでは処理が終わるまでずっと待たせるようにしている。
古い参考サイトでは最後に変数semaphoreを解放してやる必要があるってなってたけど、ARC使ってる分には勝手に解放してくれるからいらない。

dispatch_async()ってのはasyncってくらいだから本来非同期処理にするための命令らしい(だから並列処理の優先順位をDISPATCH_QUEUE_PRIORITY_BACKGROUNDって指定してる)。
でもこう書くみたいだからいいの! 動いたからいいの! 他の参考サイトでもそう書いてるからいいの!

これを応用すれば同期処理も非同期処理に変更できるようだけど、それはまたいつか。

Swift3から(だと思う)は、書き方がObjective-Cの書き方とだいぶ変わってしまったので、別にまとめる。