2016-04-08 4 views
0

TLをからの.plistファイルをロードするためにNSBundleを指定します。特定の場所をから.plistを読み取ることができ(フレームワークの一部である)シングルトンを(作成する方法DRフレームワークのアーキテクチャ:シングルトンのinit中に

フレームワークのバンドルからではありません)。

解決策は、以下に掲載され、受け入れられた回答に基づいています。

セットアップ説明

私のiOSアプリケーションはすべての共通のコードが独自に行くUsefulKit.frameworkを利用しています。

フレームワークは、(RAII)初期化中.plistから(..例えばなどベースURL、APIキー、)いくつかの設定をロードするための責任ConfigurationManager(シングルトン)を、持っているとアプリケーションを読んで興味を持って他のコンポーネントに+ (id)valueForKey:(NSString *)key; APIを提供全般的な設定。

ConfigurationManagerは、EnvironmentConfiguration-Default.plistの初期化中にロードすると予想されるデフォルトの名前(以下の質問#3を参照)を格納します。

マネージャは[NSBundle bundleForClass:[self class]]から.plistをロードし、それは管理者がUsefulKit.frameworkの一部となっている前に、正常に動作するために使用しました。メインアプリの一部だったときは、同じバンドルにそれぞれ.plistがあり、名前で見つけることができました。以下のConfigurationManager.mのコードを参照してください。それは一部UsefulKit.frameworkとき

NSString * const kDefaultEnvironmentConfigurationFileName = @"EnvironmentConfiguration-Default"; 

@interface ConfigurationManager() 

@property (nonatomic, strong) NSMutableDictionary *environmentInfo; 

@end 

@implementation ConfigurationManager 

+ (instancetype)sharedInstance { 
    static ConfigurationManager *sharedEnvironment; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     if (!sharedEnvironment) { 
      sharedEnvironment = [self new]; 
     } 
    }); 
    return sharedEnvironment; 
} 

- (instancetype)init { 
    self = [super init]; 
    if (self) { 
     self.environmentInfo = [NSMutableDictionary new]; 
     [self loadEnvironment]; 
    } 
    return self; 
} 

- (void)loadEnvironment { 
    [self.environmentInfo removeAllObjects]; 
    [self loadDefaultEnvironmentConfiguration]; 
} 

- (void)loadDefaultEnvironmentConfiguration { 
    NSBundle* bundle = [NSBundle bundleForClass:[self class]]; 
    NSString *defaultPlistPath = [bundle pathForResource:kDefaultEnvironmentConfigurationFileName ofType:@"plist"]; 

    assert(defaultPlistPath != nil); // <=== code crashes here 

    // 
    // processing the plist file here... 
    // 
} 

// ... 
// some code omitted 
// ... 

@end 

問題

、アプローチは動作しません。 EnvironmentConfiguration-Default.plistがフレームワークと一緒にバンドルされて出荷されている場合にのみ動作します。これは、フレームワークを利用するアプリケーションによって構成が異なるためです。アプリケーションにはそれぞれ.plistが必要で、フレームワークのConfigurationManagerを使用して設定にアクセスします。

このコードは、フレームワークのXcodeプロジェクト内の単体テストターゲットでも機能しません。 (上記参照)-loadDefaultEnvironmentConfigurationでコードがクラッシュ...

- (void)testConfigurationManagerInstantiation { 
    [ConfigurationManager sharedInstance]; 
} 

:私は、テスト対象のバンドルにEnvironmentConfiguration-Default.plistファイルを入れて、このユニットテストを書きました。

私はこれを参照してください上記の方法でデバッグ:バンドルは間違いなく私の.plistがで見つけることができるものではありません

- (void)loadDefaultEnvironmentConfiguration { 
    NSBundle* bundle = [NSBundle bundleForClass:[self class]]; 

    // Printing description of bundle: 
    // NSBundle </Users/admin/Library/Developer/Xcode/DerivedData/MyWorkspace-asazpgalibrpubbrimxpbrebqdww/Build/Products/Debug-iphonesimulator/UsefulKit.framework> (loaded) 

    NSString *defaultPlistPath = [[NSBundle bundleForClass:[self class]] pathForResource:kDefaultEnvironmentConfigurationFileName ofType:@"plist"]; 

    // Printing description of defaultPlistPath: 
    // <nil> 

を。だから、私は構造的に何か間違っていると疑い始めました。 Singletonパターンで構築されたConfigurationManagerとして

質問

  1. 私はConstructor Injectionを経由して、バンドルを注入することはできません。実際、私はどんな種類の依存症注射も「うまく」演奏することはできません。私は何かが恋しいですか?たぶんstatic varクライアントのアプリはパスを割り当てますか?

  2. フレームワークは内部的に他のバンドルを検索できますか?

  3. EnvironmentConfiguration-Default.plistの名前はGoogleAnalytics(B/C、他の開発者がそれを知っているし、セットアップを行う、しかし、私は多くのサードパーティ製のフレームワークのためにこの種のものを見なければならない、私に匂いConfigurationManagerの内部、にハードコードされ、UrbanAirhip、Fabric)、ここでフレームワークは特定の場所(多くの場合、フレームワークバージョン間で異なる)で.plistを見つけることを期待しています。したがって、開発者はフレームワーク統合の一環としてドキュメントを読み、環境を準備する必要があります。

アーキテクチャの変更に関するご意見は歓迎します。以下は、非常に@NSGodにより投稿の提案に基づいています

解決策は、それを感謝します!私はアプローチをある種の(静的な)依存性注入と呼んでいます。

ConfigurationManager.m

static NSBundle * defaultConfigurationBundle = nil; 

@implementation ConfigurationManager 

+ (void)initialize { 
    if (self == [ConfigurationManager class]) { 
     /// Defaults to main bundle 
     [[ConfigurationManager class] setDefaultConfigurationBundle:[NSBundle mainBundle]]; 
    } 
} 

+ (instancetype)sharedInstance { 
    static ConfigurationManager *sharedEnvironment; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     if (!sharedEnvironment) { 
      sharedEnvironment = [self new]; 
     } 
    }); 
    return sharedEnvironment; 
} 

+ (void)setDefaultConfigurationBundle:(NSBundle *)bundle { 
    @synchronized(self) { 
     defaultConfigurationBundle = bundle; 
    } 
} 
// ... 
@end 

ConfigurationManager.h:呼び出し場所で

@interface ConfigurationManager : NSObject 

// ... 

/** 
@brief Specify default NSBundle, other than [NSBundle mainBundle] (which is used, otherwise) where .plist configuration file is expected to be found during initialization. 
@discussion For some purpose (e.g. unit-testing) there might be cases, where forcing other NSBundle usage is required. The value, assigned in this method might be [NSBundle bundleForClass:[self class]], to get the bundle for caller. 
@attention This method must be called before any other method in this class for assignment to take effect, because default bundle setup happens during class instantiation. 
@param An NSBundle to read Default .plist from. 
*/ 
+ (void)setDefaultConfigurationBundle:(NSBundle *)bundle; 

// ... 
@end 

@implementation ConfigurationManagerTests 

- (void)setUp { 
    [super setUp]; 

    /// Prepare test case with correct bundle 
    [ConfigurationManager setDefaultConfigurationBundle:[NSBundle bundleForClass:[self class]]]; 
} 

- (void)testConfigurationManagerInstantiation { 
    // call sequence: 
    // 1. +initialize 
    // 2. +setDefaultConfigurationBundle 
    // 3. +sharedInstance 
    XCTAssertNoThrow([ConfigurationManager sharedInstance]); 
} 
// ... 
@end 

アプローチは、フレームワークを効率化できアプリケーションターゲットからの使用(mainBundleには.plistが存在します)、+setDefaultConfigurationBundleはこれまでの単体テストにのみ必要です。あなたがアプリケーションと通信フレームワークを持つようにしたい場合は

+0

'ConfigurationManager'クラスあなた' .framework'の名前は何:それを行うのに最適な場所は、呼び出される最初のいずれかの方法でアプリデリゲートの+initialize方法、になります住んでいる?それは 'UsefulKit.framework'ですか?もしそうなら、 'UsefulKit.framework'プロジェクトに' EnvironmentConfiguration-Default.plist'ファイルを追加し、それを 'UsefulKit.framework'ターゲットのコピーリソースビルド段階に追加しましたか?言い換えれば、 'EnvironmentConfiguration-Default.plist'ファイルが' UsefulKit.framework/Resources/EnvironmentConfiguration-Default.plist'に存在することを確認してください。 – NSGod

+0

'ConfigurationManager'の' NSBundle bundleForClass:[self class]];コードは 'ConfigurationManager'クラスがあるフレームワーク(' UsefulKit.framework'?)の 'NSBundle'を返します。なぜEnvironmentConfiguration-Default.plistファイルをテストターゲットバンドルに入れて、このユニットテストを書いたのか、私は混乱しています。 "これは、設定ファイルを必要とするフレームワークです... – NSGod

+0

@NSGod 'UsefulKit.framework'はフレームワーク名です。 'ConfigurationManager'はフレームワークの一部です。要点は、私は 'EnvironmentConfiguration-Default.plist'をフレームワークの一部にしたくないということです。それはクライアントアプリケーションの一部でなければなりません。管理者は設定を「フィード」し、便利なAPIとともに使用する必要があります。私はこのようにする方法を知らない。 –

答えて

1

実際、考えてみると、あなただけの1行のコードを変更する必要が:

- (void)loadDefaultEnvironmentConfiguration { 
    // NSBundle* bundle = [NSBundle bundleForClass:[self class]]; 
    NSBundle* bundle = [NSBundle mainBundle]; 

ConfigurationManagerクラスは、メインアプリケーションの一部でした[NSBundle bundleForClass:[self class]]は、メインのアプリケーションバンドル(すなわち、[NSBundle mainBundle]によって返された同じバンドルを返しました。ConfigurationManagerクラスをフレームワーク(バンドルとも見なすことができます)に移動したとき、[NSBundle bundleForClass:[self class]]はフレームワークの代わりにNSBundleを返すようになりました。メインアプリケーションバンドル

フレームワーク内から[NSBundle mainBundle]を呼び出すと、フレームワークを使用しているアプリケーションがあれば返されます。

また、クラスメソッドを使用して、初期化時に使用されるデフォルト値を設定することもできます。ConfigurationManager.m

@interface ConfigurationManager : NSObject 

+ (void)setDefaultConfigurationPath:(NSString *)aPath; 

@end 

:静的defaultConfigurationPathを宣言し

static NSString *defaultConfigurationPath = nil; 

@implementation ConfigurationManager 

+ (void)setDefaultConfigurationPath:(NSString *)aPath { 
    @synchronized(self) { 
     defaultConfigurationPath = aPath; 
    } 
} 
// additional methods 
- (void)loadDefaultEnvironmentConfiguration { 
    NSDictionary *dic = [NSDictionary 
       dictionaryWithContentsOfFile:defaultConfigurationPath]; 


    // 
    // processing the plist file here... 
    // 
} 

@end 

、あなたはソートのそれを変数 "クラス" 作る作るのではなくたとえば、ConfigurationManagerクラスのパブリックインターフェイスで

インスタンス変数したがって、クラスのインスタンスが作成される前にクラスメソッドを使用してその値を変更します。私はコードがARCと同じように動作するはずですが、私は肯定的ではありませんが(マニュアル参照にはまだまだ慣れています)

を呼び出す前に、メインアプリで正しいパスで[ConfigurationManager setDefaultConfigurationPath:]が呼び出されていることを確認してください。

+ (void)initialize { 
    NSString *path; // get path for plist 
    [ConfigurationManager setDefaultConfigurationPath:path]; 
} 
+0

静的なvarは私が考えた解決策の1つでした。パブリックセッターを持つことは、アイデアの良い拡張です。私はフレームワーククラスが何らかの形でアプリケーションバンドルにアクセスする可能性もまだ探しています。ありがとうございました。 –

+0

ああ、大丈夫、単純な解決策で更新しました... – NSGod

+0

ありがとうございました。私はちょうどあなたのアドバイスに基づいていた私が行った解決策を投稿しました。ありがとうございました! –

関連する問題