【Swift 3】UIImage(named:)の代わりにUIImage(contentsOfFile:)を使ったらメモリに優しくなれた

重いアプリをどうにかしようと絶賛奮闘中のReoです。

色々とメモリリークする原因は探り中なのですが、今回1つわかったことがあるのでメモメモしていきます。UIImageに関する件です。

 

今までずっと画像ファイルを読み出すときは、UIImageのイニシャライザはinit(named:)の方を用いていました。

メモリリーク関連を調べている時にこちらの質問にたどり着いてちょっと衝撃でした。

UIImage named: のメモリ解放について -teratail

こちらによると、init(named:)の方にはキャッシュ問題があるらしいです。

どうやら昔から言われている話らしい。全然知らなかった。

 

私のアプリでは、チュートリアルにUIImage(named: )の方を使って画像を読み込んで4ページくらいを表示させているんですが、それだけでも@2xとか@3xのを表示させると結構重たいんですね。

それが一度使われたら画面遷移した後でもそのまま解放されずにずーっと残っていました。

チュートリアルなんてそれこそ一回終わっちゃえばいらないのにいつまでたっても残ってて、本当にきつい。

 

それをinit(contentsOfFile:)の方を用いてみたら、ナントちゃんと解放された∩(〃・ω・〃)∩ ばんじゃーい!!!!

 

以下のような書き換えをしました。

//解放されてくれない
let image = UIImage(named:"image.png")

//スバラシイ!
let imagePath = Bundle.main.path(forResource: "image", ofType: "png")
let image = UIImage(contentsOfFile:imagePath!)

 

これで解放されてくれるので素晴らしいのですが、さてさてここでまた問題が発生しました。

 

 

contentsOfFile:の方は@2xや@3xに自動対応しない

named:を用いた場合の1番の魅力は、用意しておけば自動的に@2xや@3xに対応してくれるってことだと思います。

image@2x.pngとimage@3x.pngをちゃんと使い分けてくれるし、書くときは

UIImage(named:"image.png")

//UIImage(named:"image@2x.png") とは書かない...

って書けばいいんですからすごく楽です。

 

一方で、contentsOfFile:を用いた場合は自動的に対応してくれません

//image@3x.pngを用いたい場合は
let imagePath = Bundle.main.path(forResource: "image@3x", ofType: "png")
let image = UIImage(contentsOfFile:imagePath!)

こういう風に@3xの部分までちゃんと書かないと取得できません。

 

@2xと@3xちゃんと用意してあるのに〜〜〜この場合分けどうしたらいいんだ〜〜〜〜

そこでまず考えたのは画面サイズで判断とか、端末で判断とか・・・

 

でもちゃんとうってつけのがありました。

//iPhone7なら2.0 iPhone7 plus なら3.0
UIScreen.main.scale

これで判断ができます。

 

とりあえず定数で@2x@3xの部分を作っちゃいました。

let RESOLUTION:String = "@" + String(Int(UIScreen.main.scale)) + "x"

CGFloatなのでIntにしてStringにしてって忙しいですが、

でもこれで一応両対応できます。

let RESOLUTION:String = "@" + String(Int(UIScreen.main.scale)) + "x"

let imagePath = Bundle.main.path(forResource: "image\(RESOLUTION)", ofType: "png")
let image = UIImage(contentsOfFile:imagePath!)

 

ワ─+。:.゚ヽ(*´∀`)ノ゚.:。+゚─イ♪

 

どこのディレクトリに入ってるかによってまた変わってくると思います。

let imagePath = Bundle.main.path(forResource: "images/image\(RESOLUTION)", ofType: "png")
let image = UIImage(contentsOfFile:imagePath!)

その辺も自分のプロジェクトに合わせてやればおkです。

なかなか上手くいかなくて、下記の記事を参考にさせていただきました。

[Swift3] Bundle.main.path が nil を返すときに確認すること

 

ヌゥン。

一応nil判定もしておいた方が良いのかしら・・・

if let imagePath = Bundle.main.path(forResource: "images/image\(RESOLUTION)", ofType: "png"){
    let image = UIImage(contentsOfFile:imagePath)
}

 

結局私の場合はimagesディレクトリに入っているはずでimages/imageにしていたのですが、何故かうまくいかず、imageだけにするとできました。あとは@3xとかちゃんとつけてやるとできました。

 

これだけでも結構メモリに優しくできる気がします。

このUIImage解放されてなくない?と思ったらぜひ試してみてください。

 

 

2018/04/13追記
Swift4で動作チェック済みです〜。
Swift
コメントは認証制です。詳しくは下記の注意をお読みください。お気軽にコメントお願いします!

Write a Comment

コメント時の注意

「Twitter」「Facebook」「Google+」「WordPress」のいずれかのアカウントをお持ちの方は各アカウントと連携することでコメントできます。 コメントしたことはSNSに流れませんので、アカウントをお持ちの方はこちらの方法でコメントを投稿して下さると嬉しいです。 アカウントをお持ちでない方はメールアドレスで投稿することができます。 初回コメント時は承認後に表示されます。

Related Memo...

記事を書くほどでもないけれどメモっておきたいこと

テスト投稿。

例えばiphone7 の画面サイズ

750 × 1334
半分375 × 667

iOS

UINavigationController + UIScrollView の組み合わせで使っている時に謎の余白ができる時

UINavigationController + UIScrollView の組み合わせで使っていて、UIScrollView 上に AutoLayout で上下左右0で View を設置しているのに、30px程度上にずれてしまうとき。

`navigationController.navigationBar.isTranslucent = false` にすると直るかもしれない。

ScrollView上のコンテンツとNavigationBarの重なっているところが透過していたら多分これで直せるはず。

通常のターゲットではちゃんと動いているのに、iOSSnapshotTestCase を用いたテストでだけこの対応が必要なのよくわからないけれど。。。

iOS

UITableView.RowAnimation の .none はアニメーションするよ

UITableView.RowAnimation の .none はアニメーションがnoneなわけじゃなく、デフォルトの設定を使うよという意味らしい。

The inserted or deleted rows use the default animations.

なのでアニメーションしちゃう。今更の気づき。

 

iOS
more