Pod install時にAdSupportなどの不要なframeworkを入れない方法

最近はCocoa開発時に外部ライブラリを利用するために、CocoaPodsを利用することが多い。CocoaPodsはpodspecに従い自動的に依存関係などを解決してインストールしてくれるが、そのために不要なframeworkなどのリンクが行われてしまうことがある。

自分が経験した一番の問題は、Google Analyticsなどのアクセス解析系のSDKの多くにはAdSupport Frameworkと連携し、広告のコンバージョン追跡機能などが付いている場合。 AdSupportは広告を利用していないのに、バイナリに含めるとAppleにリジェクトされてしまう。しかしCocoaPodsは自動的にAdSupportも入れてしまうのである。

その解決するためにPodfileにhook機能を利用してpod install時に自動的にAdSupportを取り除くようにした。 そのコードは以下のとおり。

Podfile
1
2
3
4
5
6
7
8
9
10
11
12
13
pre_install do |installer_representation|
  installer_representation.pods.each do |pod|
    pod.specs.each do |spec|
      case spec.attributes_hash['weak_frameworks']
      when Array
        spec.attributes_hash['weak_frameworks'] -= ['AdSupport']
      when String
        frameworks = spec.attributes_hash['weak_frameworks'].gsub(/AdSupport/, '')
        spec.attributes_hash['weak_frameworks'] = frameworks.empty? ? nil : frameworks
      end
    end
  end
end

Blocks非対応なクラスを簡単にCategoryでBlocks対応する方法

CocoaにはBlocksには対応しておらず、Protocolが用意されておりDelegateを使ってcallbackやeventを受け取るようになってるものが多数ある。 いちいちProtocolを実装しないといけなくて煩わしさを感じたり、コードが煩雑になってしまう場合がある。

そこで簡単にBlocks対応にしてしまう方法をご紹介。

StoreKitを例に

StoreKitのProductsRequestもDelegateが用意されており、Blocksに非対応なクラス。 In-App-Purchase用のAppStoreにある商品情報を取得するためのクラスだが、初回の取得は遅く2回目以降は早いという特徴が知られていて、AppDelegateなどで、先に一度取得しておくのが通例になっているらしい。

ところがAppDelegateでわざわざこのクラスのDelegateを実装するのは面倒。 そこで今回Blocks化したので、これを例に紹介。

SKProductsRequest+blocks.h
1
2
3
4
5
6
7
#import <StoreKit/StoreKit.h>

@interface SKProductsRequest (blocks)

  + (id)requestWithProductIdentifiers:(NSSet *)productIdentifiers withBlocks:(void (^)(SKProductsResponse *response,  NSError *error))block;
  - (id)initWithProductIdentifiers:(NSSet *)productIdentifiers withBlocks:(void (^)(SKProductsResponse *response,  NSError *error))block;
@end
SKProductsRequest+blocks.m
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
#import "SKProductsRequest+blocks.h"
#import <objc/runtime.h>


@interface SKProductsRequestBlocksDelegate : NSObject <SKProductsRequestDelegate>
@property (strong, nonatomic) void (^block)(SKProductsResponse *response, NSError *error);
@end


@implementation SKProductsRequestBlocksDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    if(self.block){
        self.block(response, nil);
    }
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
    if(self.block){
        self.block(nil, error);
    }
}
@end

static char SKProductsRequestBlocksDelegateKey;

@implementation SKProductsRequest (blocks)

+ (id)requestWithProductIdentifiers:(NSSet *)productIdentifiers withBlocks:(void (^)(SKProductsResponse *response, NSError *error))block {
    return [[[self class] alloc] initWithProductIdentifiers:productIdentifiers withBlocks:block];
}

- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers withBlocks:(void (^)(SKProductsResponse *response, NSError *error))block {
    NSParameterAssert(block != nil);

    if((self = [self initWithProductIdentifiers:productIdentifiers])){
        SKProductsRequestBlocksDelegate *productsRequestDelegate = [[SKProductsRequestBlocksDelegate alloc] init];

        objc_setAssociatedObject(self, &SKProductsRequestBlocksDelegateKey, productsRequestDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        self.delegate = productsRequestDelegate;
        productsRequestDelegate.block = block;

        [self start];
    }

    return self;
}

@end

解説

  • SKProductsRequestに対して、blocksというCategoryを作り、blocks対応のメソッドを追加している
  • Delegateを受け取るためにProtocolを実装したSKProductsRequestBlocksDelegateというクラスを作る
  • SKProductsRequestBlocksDelegateにはblockをpropertyとして持つようにしている
  • delegateが返って来たら、propertyのblockを呼んでいる

注意点

なんとなく普通に見えるが、1点気をつけななければならない点がある。 Categoryではインスタンス変数を追加できないためSKProductsRequestBlocksDelegateのインスタンスをCategoryの中で作っても消えてしまう。

解決策

実装側のファイルで、

1
#import <objc/runtime.h>

のようなファイルをインポートしているが、これはObjCのランタイムAPIのメソッドを使うため。

ランタイムAPIを使うことでCategoryに簡単にインスタンス変数を持たせることができる。

1
objc_setAssociatedObject(self, &SKProductsRequestBlocksDelegateKey, productsRequestDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

これがその実際の呼び出し部分。selfにdelegateのインスタンスを入れた productsRequestDelegateを紐付けてselfと同時に開放するようにしてくれる。

本来は第2引数で入れた名前で、紐付けた変数を取り出したりできるようにするための機能だが、今回は保持さえしてくれればいいのでcharをとりあえず入れている。

まとめ

Categoryを使いBlocksを拡張する際には、Delegateを保持し続ける必要があるが、インスタンス変数を普通は持てないのでランタイムAPIを用いて無理やり持たせると簡単に実装ができる。 他にもBlocks化する方法を色々あると思うので追って紹介して行きたい。

Octopress始めました

ブログ再開!Octopressっていうのが最近流行りらしいから書いてみる。

2006年ぐらいから、同じドメインでMovableTypeでやってたけど2年ぐらいでやめてしまった。 今回はどれぐらい続くかな。気が向いたら古い記事とかもマージしてみようと思う。

Test Post

最初のポスト

  • これはテスト
  • これもテスト
    • つまりそういうため
  • テスト・テスト
    • なるほどね

なるほどテスト

  1. テストその1
  2. テストその2
  3. テストその3

そうなんだテスト

one line command
1
$ ls test
コードテスト
1
2
3
def test?
  true
end

引用文の取り扱い

$ cat test.txt

$ cat test2.txt

リンク