2017-04-19 13 views
1

私のアプリケーションでは、Music PlayerのNow Playingアイテムのすべての一時変数を保持するために別のクラスを実装したいと考えています。 さまざまな種類のプロパティが多数ありますが、同じ方法で処理する必要があります。彼らは、クラスメソッド「にupdateData」で扱われるべき(コードの末尾を参照)クラスプロパティのコード複製を減らす方法(Swift 3)

これは私のコードです:

struct DataDefaults { 
    //MARK: Default properties 
    let albumTitle: String? = "Unknown Album" 
    let albumArtist: String? = "Unknown Artist" 
    let title: String? = "Unknown Title" 
    let artist: String? = "Unknown Artist" 
    let artwork: UIImage? = UIImage(named: "noartwork")! 
    let genre: String? = "" 
    let lyrics: String? = "No Lyrics" 
    let releaseDate: Date? = nil 
    let playbackDuration: TimeInterval? = 0 
    let rating: Int? = 0 
    let assetURL: URL? = nil 
    let isExplicitItem: Bool? = false 
    let isCloudItem: Bool? = false 
    let hasProtectedAsset: Bool? = false 
} 

class SongInfo: NSObject { 

    static let sharedData = SongInfo() 

    let defaults = DataDefaults() 

    //MARK: Properties 
    var albumTitle: String 
    var albumArtist: String 
    var title: String 
    var artist: String 
    var artwork: UIImage 
    var genre: String 
    var lyrics: String 
    var releaseDate: Date? 
    var playbackDuration: TimeInterval 
    var rating: Int 
    var assetURL: URL? 
    var isExplicitItem: Bool 
    var isCloudItem: Bool 
    var hasProtectedAsset: Bool 


    //MARK: Init 
    private override init() { 
     self.albumTitle = defaults.albumTitle! 
     self.albumArtist = defaults.albumArtist! 
     self.title = defaults.title! 
     self.artist = defaults.artist! 
     self.artwork = defaults.artwork! 
     self.genre = defaults.genre! 
     self.lyrics = defaults.lyrics! 
     self.releaseDate = defaults.releaseDate 
     self.playbackDuration = defaults.playbackDuration! 
     self.rating = defaults.rating! 
     self.assetURL = defaults.assetURL 
     self.isExplicitItem = defaults.isExplicitItem! 
     self.isCloudItem = defaults.isCloudItem! 
     self.hasProtectedAsset = defaults.hasProtectedAsset! 
    } 

    //MARK: Set properties 
    func updateData(allData: DataDefaults) { 
     var wasUpdated: Bool = false 

     if allData.albumTitle == self.albumTitle { 
      //pass 
     } else if allData.albumTitle == nil || allData.albumTitle == "" { 
      self.albumTitle = defaults.albumTitle! 
      wasUpdated = true 
     } else { 
      self.albumTitle = allData.albumTitle! 
      wasUpdated = true 
     } 

     //Need to repeat same IF for all properties 
    } 
} 

私は同じのいくつかの再使用をするために、プロパティ名を使用することができる方法はありますコードを複製する代わりに?

+1

**オプションの目的は何**非'DataDefaults'構造体の-nil定数は、決して値を変更しないためですか? – vadian

答えて

2

奇妙なデザインの解決策を見つけるのではなく、達成しようとしていることのために再設計されました

struct SongData: Equatable { 

    static let defaultData = SongData(albumTitle: "Unknown Album", 
             albumArtist: "Unknown Artist", 
             title: "Unknown Title", 
             artist: "Unknown Artist", 
             artwork: UIImage(named: "noartwork"), 
             genre:"", 
             lyrics: "No Lyrics", 
             releaseDate: nil, 
             playbackDuration: 0, 
             rating: 0, 
             assetURL: nil, 
             isExplicitItem: false, 
             isCloudItem: false, 
             hasProtectedAsset: false) 

    //MARK: Default properties 
    var albumTitle: String? 
    var albumArtist: String? 
    var title: String? 
    var artist: String? 
    var artwork: UIImage? 
    var genre: String? 
    var lyrics: String? 
    var releaseDate: Date? 
    var playbackDuration: TimeInterval? 
    var rating: Int? 
    var assetURL: URL? 
    var isExplicitItem: Bool? 
    var isCloudItem: Bool? 
    var hasProtectedAsset: Bool? 

    /// This initializer will set the properties to the defaultData properties if a passed value is nil 
    init(albumTitle: String?, albumArtist: String?, title: String?, artist: String?, artwork: UIImage?, genre: String?, lyrics: String?, releaseDate: Date?, playbackDuration: TimeInterval?, rating: Int?, assetURL: URL?, isExplicitItem: Bool?, isCloudItem: Bool?, hasProtectedAsset: Bool?) { 

     // initialize properties where the default is nil 
     self.releaseDate = releaseDate 
     self.assetURL = assetURL 

     //initialize other properties with the passed values, or use the default value if nil 
     self.albumTitle = SongData.valueOrDefault(albumTitle, SongData.defaultData.albumTitle) 
     self.albumArtist = SongData.valueOrDefault(albumArtist, SongData.defaultData.albumArtist) 
     self.title = SongData.valueOrDefault(title, SongData.defaultData.title) 
     self.artist = SongData.valueOrDefault(artist, SongData.defaultData.artist) 
     self.artwork = artwork ?? SongData.defaultData.artwork 
     self.genre = SongData.valueOrDefault(genre, SongData.defaultData.genre) 
     self.lyrics = SongData.valueOrDefault(lyrics, SongData.defaultData.lyrics) 
     self.playbackDuration = playbackDuration ?? SongData.defaultData.playbackDuration 
     self.rating = rating ?? SongData.defaultData.rating 
     self.isExplicitItem = isExplicitItem ?? SongData.defaultData.isExplicitItem 
     self.isCloudItem = isCloudItem ?? SongData.defaultData.isCloudItem 
     self.hasProtectedAsset = hasProtectedAsset ?? SongData.defaultData.hasProtectedAsset 
    } 

    static func ==(leftItem: SongData, rightItem: SongData) -> Bool { 
     return (leftItem.albumTitle == rightItem.albumTitle) && 
       (leftItem.albumArtist == rightItem.albumArtist) && 
       (leftItem.title == rightItem.title) && 

       // Comparing a reference type here. may need to be handled differently if that's a problem 
       (leftItem.artwork === rightItem.artwork) && 
       (leftItem.genre == rightItem.genre) && 
       (leftItem.lyrics == rightItem.lyrics) && 
       (leftItem.releaseDate == rightItem.releaseDate) && 
       (leftItem.playbackDuration == rightItem.playbackDuration) && 
       (leftItem.rating == rightItem.rating) && 
       (leftItem.assetURL == rightItem.assetURL) && 
       (leftItem.isExplicitItem == rightItem.isExplicitItem) && 
       (leftItem.isCloudItem == rightItem.isCloudItem) && 
       (leftItem.hasProtectedAsset == rightItem.hasProtectedAsset) 
    } 

    //simple helper function to avoid long turneries in the init 
    static func valueOrDefault(_ value: String?, _ defaultValue: String?) -> String? { 
     guard let value = value, !value.isEmpty else { 
      return defaultValue 
     } 
     return value 
    } 
} 

class SongInfo { 

    static let sharedData = SongInfo() 

    var data: SongData 

    //MARK: Init 
    private init() 
    { 
     self.data = SongData.defaultData 
    } 

    //MARK: Set properties 
    func updateData(newData: SongData) { 
     if(newData != self.data) { 
      self.data = newData 
     } 
    } 
} 

私は構造体を使用したいと思われるように変更しました。構造体のinitは、初期値がnilの場合はデフォルト値に戻ります。私のデザインには、ほとんど常に悪いフォールアンラップも含まれていません。

+0

すばらしい答え!ありがとう、これは間違いなく私が達成しようとしていたものです。 – dandepeched

+0

このコードでは 'defaultData'の初期化に問題があります。 'valueOrDefault'メソッドを呼び出して無限ループに落ちると、自身を参照しようとします。回避策として、私は 'return defaultData'の代わりにswitchを使用しました:switch defaultValue { case" albumTitle ": return" Unknown Album " ...} – dandepeched

+0

あなたは正しいと思います。私は2つのイニシャライザを避けようとしていましたが、単に値を設定するデフォルト値のための別のイニシャライザが必要になるでしょう。 – PeejWeej

1

別の構造体を使用せずにクラス定義に直接デフォルトを設定し、既定値のスタティックな変更されていないインスタンスを持つことができます。例えば

:あなたはまた、クラス全体の機能せず、基本的なデータを操作する必要がある場合は

class SongInfo: NSObject { 

    static let sharedData = SongInfo() 

    static let defaults = SongInfo() 

    //MARK: Properties 
    var albumTitle: String?  = "Unknown Album" 
    var albumArtist: String?  = "Unknown Artist" 
    var title: String?   = "Unknown Title" 
    var artist: String?   = "Unknown Artist" 
    var artwork: UIImage?  = UIImage(named: "noartwork")! 
    var genre: String?   = "" 
    var lyrics: String?   = "No Lyrics" 
    var releaseDate: Date?  = nil 
    var playbackDuration: TimeInterval? = 0 
    var rating: Int?    = 0 
    var assetURL: URL?   = nil 
    var isExplicitItem: Bool? = false 
    var isCloudItem: Bool?  = false 
    var hasProtectedAsset: Bool? = false 


    //MARK: Init 
    private override init() 
    { 
     // nothing to do here 
    } 

    //MARK: Set properties 
    func updateData(allData: DataDefaults) { 
     var wasUpdated: Bool = false 

     if allData.albumTitle == self.albumTitle { 
      //pass 
     } else if allData.albumTitle == nil || allData.albumTitle == "" { 
      self.albumTitle = SongInfo.defaults.albumTitle! 
      wasUpdated = true 
     } else { 
      self.albumTitle = allData.albumTitle! 
      wasUpdated = true 
     } 

     //Need to repeat same IF for all properties 
    } 
} 

、あなただけのプロパティを持つSongInfoDataクラスを定義し、SingInfoは、そのクラスから継承させることもできます。デフォルトの静的変数はSongInfoDataクラスにあり、SingInfoサブクラスはプロパティ宣言を必要としません。

[EDIT]更新機能のコードの繰り返しを避ける...

あなたのクラスに汎用的な機能を追加することにより、プロパティの更新プロセスを一般化することができます

例えば:

func assign<T:Equatable>(_ variable:inout T?, _ getValue:(SongInfo)->T?) -> Int 
{ 
    let newValue = getValue(self) 

    if variable == newValue 
    { return 0 } 

    var valueIsEmpty = false 
    if let stringValue = newValue as? String, stringValue == "" 
    { valueIsEmpty = true } 

    if newValue == nil || valueIsEmpty 
    { 
     variable = getValue(SongInfo.defaults) 
     return 1 
    } 

    variable = newValue 
    return 1    
} 

func update(with newInfo:SongInfo) 
{ 
    let updates = newInfo.assign(&albumTitle) {$0.albumTitle} 
       + newInfo.assign(&albumArtist) {$0.albumArtist} 
       + newInfo.assign(&title)  {$0.title} 
       + newInfo.assign(&artist)  {$0.artist} 
       + newInfo.assign(&artwork)  {$0.artwork} 
       + newInfo.assign(&genre)  {$0.genre} 
       + newInfo.assign(&lyrics)  {$0.lyrics} 
       // ... 

    if updates > 0 
    { 
    // react to update 
    } 
} 
+0

ありがとう、それは興味深い点です。私はそれを検討します。しかし、私の質問は 'updateData'メソッドに関するものでした。まず第一に、構造体で使用しているのと同じプロパティを受け取ることができます。なぜそれを別々に定義するのでしょうか。実際の問題は、私は何らかの形で、各プロパティの 'updateData'メソッドにIFステートメントをコピーして貼り付けることができないのですか? – dandepeched

0

MPMediaアイテムを使用しているようです。 これらのプロパティをすべて保存する必要はありません。 あなただけ(UInt64型から文字列に変換する)アイテムの永続的なIDを格納する必要があり、以降、述語でこのような何かをMPMediaQueryを使用してMPMediaItemをフェッチ:

func findSong(persistentIDString: String) -> MPMediaItem? { 
    let predicate = MPMediaPropertyPredicate(value: persistentIDString, forProperty: MPMediaItemPropertyPersistentID) 
    let songQuery = MPMediaQuery() 
    songQuery.addFilterPredicate(predicate) 

    return songQuery.items.first 
} 
+0

はい、私は 'MPMediaItem'を使用しますが、私はDataStorageからMediaPlayerインタレーションを分離したいと思います。別のMediaPlayerクラスからDataStorageを書き直したい – dandepeched

関連する問題