Objective-CからSwiftにしたら処理が劇的に重くなった話


⇒ Arrayの扱いが変わってきたので一旦この記事は保留で全体を見直すことにしました。


絶賛まだ修正中ですが、「Assetsを表示するプログラムをSwiftにしてみる」でいろいろと作業しながらSwiftの勉強をしていたのですが、自分のおもいつくままの方法でObjective-CからSwiftにしたら処理が耐えられないほど重くなってしまいました・・・・。
いろいろと原因追及をしながら考えてみたいなと思います。
・・・考えている途中のメモです。
解決したら解決方法を追記する予定ですが現在解決しておりませぬ。
コメント募集中?

そもそも未熟すぎるのが悪いというのは大目に見てください・・・orz

劇重になった問題の処理

問題のコード部分はAssetLibraryからデータを取り出して日付でソートするという部分です。
日付順に格納したかったのでソート処理をしてデータを格納しなおしてごにょごにょしてました。
そもそもがサンプルで作ったソースなので元々が良くないのかもしれませんが、Objective-Cでは数秒程度の処理がSwiftにすることで数分の処理へと変貌してしまいました。

  • Objective-Cで。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
ALAssetsGroupType assetsGroupType = ALAssetsGroupAlbum | ALAssetsGroupEvent | ALAssetsGroupFaces | ALAssetsGroupSavedPhotos;

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy/MM/dd"];

// アセットライブラリから取得したグループ内のアセットごとの処理
ALAssetsGroupEnumerationResultsBlock groupResultBlock = ^(ALAsset *asset, NSUInteger index, BOOL *stop) {
  if (asset) {
    NSDate   *assetDate    = [asset valueForProperty:ALAssetPropertyDate];
    NSString *assetDateStr = [formatter stringFromDate:assetDate];
    [allAssets addObject:@{ ASSET: asset, DATE: assetDate, DATE_STR: assetDateStr }];
  }
};

NSComparator comparetor = ^NSComparisonResult (id obj1, id obj2) {
  NSDate *datea = obj1[DATE];
  NSDate *dateb = obj2[DATE];

  return [dateb compare:datea];
};


// ライブラリからアセットグループを取得する処理
ALAssetsLibraryGroupsEnumerationResultsBlock resultBlock = ^(ALAssetsGroup *assetsGroup, BOOL *stop) {
  //        NSLog(@"AssetsGroup : %@", assetsGroup);
  ALAssetsFilter *onlyPhotosFilter = [ALAssetsFilter allPhotos];
  [assetsGroup setAssetsFilter:onlyPhotosFilter];
  if (assetsGroup) {
    if ([assetsGroup numberOfAssets] > 0) {
      [assetsGroup enumerateAssetsUsingBlock:groupResultBlock];
    }
  } else {
    // 全体sort
    [allAssets sortWithOptions:NSSortConcurrent usingComparator:comparetor];
    for (NSDictionary *asset in allAssets) {
      NSString *dateStr = asset[DATE_STR];
      if (self.assetsData[dateStr]) {   // 存在していたら
        [self.assetsData[dateStr] addObject:asset[ASSET]];
      } else {
        NSMutableArray *sectionArray = [[NSMutableArray alloc] init];
        [sectionArray addObject:asset[ASSET]];
        [self.assetsData setObject:sectionArray forKey:dateStr];
        [sectionArray release];
        [self.sectionList addObject:dateStr];
      }
    }
    [formatter release];
    [allAssets release];
    [self.collectionView reloadData];
  }
};
  • Swiftで
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
let assetsGroupType :ALAssetsGroupType! = ALAssetsGroupType(0xFFFFFFFF)

var fomatter = NSDateFormatter()
fomatter.dateFormat = "yyyy/MM/dd"

var groupResultBlock : ALAssetsGroupEnumerationResultsBlock = { (asset: ALAsset!, index: Int, stop:CMutablePointer<ObjCBool>) -> Void in
  if asset {
    let assetDate: NSDate = asset.valueForProperty(ALAssetPropertyDate) as NSDate
    let assetDateStr: String = fomatter.stringFromDate(assetDate) as String
    allAssets.append([self.ASSET:asset, self.DATE:assetDate, self.DATE_STR:assetDateStr])
  }
}

var resultBlock : ALAssetsLibraryGroupsEnumerationResultsBlock = { (assetsGroup:ALAssetsGroup!, stop: CMutablePointer<ObjCBool>) -> Void in
  let onlyPhotosFilter: ALAssetsFilter = ALAssetsFilter.allPhotos()
  if assetsGroup {
    assetsGroup.setAssetsFilter( onlyPhotosFilter )
    if assetsGroup.numberOfAssets() > 0 {
      assetsGroup.enumerateAssetsUsingBlock(groupResultBlock)
    }
  } else {
    sort( allAssets ){ $0[self.DATE_STR] as String > $1[self.DATE_STR] as String }

    for anAsset: Dictionary<Int,AnyObject> in allAssets {
      let assetDict: NSDictionary = anAsset
      let dateStr: String! = assetDict[self.DATE_STR] as String
      if self.assetsData[dateStr] {
        var asstsArr:Array<ALAsset> = self.assetsData[dateStr]!
        asstsArr.append(anAsset[self.ASSET] as ALAsset)
        self.assetsData[dateStr] = asstsArr
      } else {
        var sectionArray:Array<ALAsset> = []
        sectionArray.append(anAsset[self.ASSET] as ALAsset)
        self.assetsData[dateStr] = sectionArray
        self.sectionList.append(dateStr)
      }

    }
    self.collectionView!.reloadData()
  }
}

Swiftにうまく変換できなかった部分は思いつくままに無理矢理やっている部分があります。
処理としては下記のものになります。

  • allAssetsという配列にまずはAssetsたちを全部入れていく
  • allAssetsを日付でソートする
  • ソートした結果をCollectionViewで使いやすいようにself.assetsDataへ日付をkeyにしたDictionaryに格納していく

以上の作業をしています。

細かくみていきたいと思います。

第1の問題点 : ALAssetsGroupType

AssetsLibraryからデータを取り出す時にALAssetsGroupTypeを指定することで必要なデータのみ表示されるように調整しています。

1
ALAssetsGroupType assetsGroupType = ALAssetsGroupAlbum | ALAssetsGroupEvent | ALAssetsGroupFaces | ALAssetsGroupSavedPhotos;
1
let assetsGroupType :ALAssetsGroupType! = ALAssetsGroupType(0xFFFFFFFF)

実はここで問題が起きてしまいました。
AssetsLibraryを利用するので

1
import AssetsLibrary

を記載しています。が、ALAssetsGroupAlbumが呼び出せないのです。
ヘッダファイルに飛んでみると、Objective-Cではenumで記述されていた部分が、変数の宣言に切り替わっています。
しかも、ALAssetsGroupTypeInt型ですが、ObjCで宣言されていたALAssetsGroupAlbumなどはCUnsignedInt型です。
そしてALAssetsGroupTypetypealiasで宣言されています。

1
2
3
4
5
6
7
8
9
10
11
var ALAssetsGroupLibrary: CUnsignedInt { get } // The Library group that includes all assets.
var ALAssetsGroupAlbum: CUnsignedInt { get } // All the albums synced from iTunes or created on the device.
var ALAssetsGroupEvent: CUnsignedInt { get } // All the events synced from iTunes.
var ALAssetsGroupFaces: CUnsignedInt { get } // All the faces albums synced from iTunes.
var ALAssetsGroupSavedPhotos: CUnsignedInt { get } // The Saved Photos album.

var ALAssetsGroupPhotoStream: CUnsignedInt { get } // The PhotoStream album.

var ALAssetsGroupAll: CUnsignedInt { get } // The same as ORing together all the available group types,

typealias ALAssetsGroupType = Int

この子たちの扱いがわかりません・・・。
いろいろ試行錯誤して下記の書き方にしてみました。

1
let assetsGroupType :ALAssetsGroupType!  = ALAssetsGroupType(ALAssetsGroupAll)

こうするとこの行でXcodeが落ちてしまいますorz
うーん。まだ勉強不足故このあたりをどう書けばいいのかわからず、結局0xFFFFFFFFと直に書いて進むことにしてしまいました。
絶賛解決方法探し中。
ちなみにplaygroundでは

1
Playground execution failed: error: <REPL>:60:1: error: use of unresolved identifier 'ALAssetsGroupAll'

と、表示されています・・・。importが間違えてるのかなぁ・・・。

第2の問題点 : sort

ObjCではNSComparatorを使ってた部分をSwiftではsortがあるのでそれを使いました。
allAssetsには

1
2
3
4
5
6
7
NSComparator comparetor = ^NSComparisonResult (id obj1, id obj2) {
  NSDate *datea = obj1[DATE];
  NSDate *dateb = obj2[DATE];

  return [dateb compare:datea];
};
[allAssets sortWithOptions:NSSortConcurrent usingComparator:comparetor];
1
sort( allAssets ){ $0[self.DATE_STR] as String > $1[self.DATE_STR] as String }

参考

Comments