ぴーさんログ

だいたいXamarin.Formsのブログ

Xamarin.Forms Material Visualのバグ修正しました

お仕事で踏んだXamarin.Formsのバグ修正をしました。

この記事を書いている時点でPR承認済みなのでそのうち取り込まれてリリースされると思います。

[Bug] iOS Material Renderers can cause NRE. · Issue #15669 · xamarin/Xamarin.Forms

この問題はMaterial Visual(いわゆるマテリアルデザイン化するやつ)を使っていると、iOSでまれにNullReferenceExceptionが発生するというものです。

原因個所はレイアウト更新時のnullチェックで、タイミングが悪いとMaterialXxxxRendererがElement(Xamarin.Formsコントロール)付け替え中にそのプロパティを参照してNREになるようです。

今この問題を踏んで困っている人向けの回避策

MaterialXxxxRendererのカスタムRendererでApplyThemeIfNeededにnullチェックを追加して差し替えましょう。 (サンプルはFrameコントロールを差し替える例)

using System;
using Xamarin.Forms;
using Xamarin.Forms.Material.iOS;

[assembly: ExportRenderer(
    typeof(Xamarin.Forms.Frame),
    typeof(XFApp.iOS.MyMaterialFrameRenderer),
    new[] { typeof(VisualMarker.MaterialVisual) })]

namespace XFApp.iOS
{
    public class MyMaterialFrameRenderer : MaterialFrameRenderer
    {
        protected override void ApplyThemeIfNeeded()
        {
            // まれにElement差し替えタイミングで走ることがあるのでnullチェックする。
            if (Element == null) return;
            base.ApplyThemeIfNeeded();
        }
    }
}

艦これ二期のスクリーンショットを撮るChrome拡張(ver.1.5)

やったのもう結構前なんですが、艦これのスクショを撮るChrome拡張がストアのポリシー変更に対応してなくて取り下げられちゃってたので対応しました。

chrome.google.com

ポリシー違反関係の対応自体は申請ページの作業なんですが、バックグラウンド処理のやり方が変わってservice worker必須になっていたのでその対応もしてます。

github.com

Xamarin.iOSのメモリ管理

2つのメモリ管理

Xamarin.iOS(.NET 6も同様)では、C#(.NET)側とiOS側(Objective-C, Swift)でそれぞれ独立したメモリ管理をしています。

C#(.NET)では、ガベージコレクタ(GC)を採用しています。 コード上ではメモリ管理を行わず、GC不定期にどこからも参照されていないインスタンスを検査してメモリを解放します。

一方、iOS(Objective-C, Swift)では参照カウンタを採用しています。 歴史的にMRC(Manual Reference Counting)とARC(Automatic Reference Counting)があり、ARCの方が新しい方式です。MRCではコードでメモリ管理を行い、ARCではコンパイラがメモリ管理のコードを自動生成してくれるというイメージです。

Xamarin.iOSでは内部的にMCRを利用してiOS側オブジェクトの参照カウンタ操作を行います。

インスタンス生成~解放

NSObjectなどのiOSの世界のクラスをC#から生成して操作することが出来ますが、この時C#側のインスタンスiOS側のインスタンスがペアで生成されています。

NSObjectクラスを例にして単純なケースを考えると次のような感じです。

C#側からのインスタンス生成

  • C#new NSObject() 実行したとき、このコンストラクタ内部でObjective-C[[NSObject alloc] init] がP/Invoke経由で実行されます。
    • (参照カウンタ 0 → 1)
  • C#側のNSObjectは、生成されたiOS側NSObjectのポインタをHandleプロパティに格納します。

C#側からのインスタンス解放

  • C#側NSObjectが何処からも参照されなくなるとGCに回収され、後始末としてデストラクタが呼ばれる。
  • デストラクタから呼ばれたDisposeがP/Invoke経由で [NSObject release] を実行する。
    • (参照カウンタ N → N-1、この時1 → 0ならiOS側でもメモリ解放)

まとめ

実際にはもちろん、先に作られたiOSインスタンスを元にC#インスタンスが作られるケースもありますし、monoランタイムに絡む制御も複雑で実際に起こる事をトレースしていくのは至難の技です。 (私はそこまで出来てません)

そこまで行かなくとも、C#側とiOS側のメモリ管理とその連携がイメージできると、メモリリーク対策やバグフィックスに役立つと思います。

オマケ

実際の処理が気になる人はxamarin-maciosリポジトリNSObject2.cs を起点に色々見てみましょう。

個人的には、iOS側から(強制的に?)ネイティブオブジェクトを解放されてしまうと検知出来ないのを、Associated Objectを使ってフックして解決してる所なんか面白かったです。 (アタッチ先と一緒に死ぬのでdeinitに処理を仕込めば引っかけられる)

読んでいたソース

デスクトップPC更新

自宅のデスクトップPCを更新しました。

いつも構成を忘れるので備忘録としてここに残します。

買ったもの

旧PCはCore i7 3770K(2012年発売)だったので6年ぶりの更新。ストレージとグラボは旧PCから移植。

写真

f:id:ticktack623:20181208142141j:plain

f:id:ticktack623:20181208142011j:plain 作業開始。


f:id:ticktack623:20181208164413j:plain クソデカCPUクーラー君。


f:id:ticktack623:20181209134422j:plain 組み立て完了、マザーボード側。


f:id:ticktack623:20181209134259j:plain 組み立て完了、裏配線。

艦これ二期のスクリーンショットを撮るChrome拡張(ver.1.4)

chrome.google.com

前回の問題点を改善して、高DPIの環境だったり、スクロールやズームをしていても原寸大のスクリーンショットが撮れるようになりました。

ついでにオプションで保存形式を選択できるようにして

申し訳程度のアイコンを設定しました。

f:id:ticktack623:20180824202442j:plain

艦これ第二期がスタートしたのでスクリーンショットを撮るChrome拡張を作った

8月17日(金)に艦これ第二期がスタートして、FlashからHTML5に移行しました。リニューアルオープンです。

ゲーム画面が 800x480 から 1200x720 になりました。大きい!

それに合わせて艦娘の立ち絵などの画像素材も高解像度化されました。う、美しすぎる!

綺麗になったら艦娘たちのスクリーンショットたくさん撮りたいですね。

という訳で艦これ二期のスクリーンショットを撮るChrome拡張を作ってみました。ボタンひと押しでスクショが撮れます。 (初Chrome拡張たのしかったです)

せっかく作ったので公開します。

chrome.google.com

ソースコード

制限事項

スクロールしたりウィンドウを縮小していると正しく取れない

Chrome APIでタブの見えている範囲をキャプチャしてゲーム画面部分を切り抜いています。 なのでスクロールするとずれちゃいます。(スクロール量を取得すれば対応可能?)

高DPI環境で使うと正しく撮れない

例えばMacBook Proのような高DPIで画面が拡大表示されている環境。

切り抜く範囲をDPIに応じて調整すれば対応できるけど、拡大された画像を縮小し直すことになるので画質劣化しそう。

スクショを撮るたびにいちいち保存場所を選ばされる

作り始める前はFileSystem APIでローカルに保存できると思っていたのですがChrome拡張からは使えないようなので、諦めてファイルとしてダウンロード形に落ち着きました。

保存場所を選ぶダイアログが出ている間も次のスクショが撮れるので連写は可能。

アイコンが無い

せっかく公開したので何か作ります。

試したけどダメだったアプローチ

ゲーム画面を描画しているcanvas要素から直接描画内容を画像化

canvas.toDataURL()しても真っ黒な画像しか取得できませんでした。

ゲーム側で使ってるPixiJS(?)の設定でレンダラーのバッファを保持するように使っていないと取得できない模様。

Xamarin.Formsのトロフィーをもらいました

Xamarin.FormsチームがGitHubリポジトリのコントリビューター宛にトロフィーを贈ってくれました。やったぜ。

(P3PPPは私のGitHubアカウント名)

(ちなみにトロフィーの形はXamarin Universityの認定トロフィーと同じだったりする)

届くまでの紆余曲折

2018年 5月下旬

David(※)「Xamarin.Formsリポジトリにコントリビュートありがとう!贈り物をしたいのでmailing address(住所)を教えてもらえますか?」
ワイ「(細かいやり取りをするための連絡先をGitHub経由で聞いてるのかな?)、これです(e-mailアドレス)」
David「physical mailing addressを教えて?」
ワイ「アッハイ、ここ宛てでお願いします。」

※Xamarin.Formsのプログラムマネージャー

2018年 7月頭

David「アメリカ国外に発送するには荷受人の電話番号が必要みたい、教えてください。こういうことをするのは初めてで、学びがあります。」
ワイ「この番号でー。」

2018年 7月中旬

UPS(配送業者)「アメリカからの荷物が来てるんですが心当たりあります?住所が市区町村までしか入ってなくてどこに送れば?宛先がMicrosoftアワードセンター(?)になってますが会社受取にします?」
ワイ「その宛先は発送作業した部署か何かですかねぇ、普通に個人宅宛でお願いしますー。」
UPS「あー、Microsoftの従業員ではないんですね、承りましたー。」

数日後着弾

こうして見ると、ほぼ電話番号だけで届いてますね。無事に届いて本当に良かった。