【问题标题】:Firebase converting snapshot value to objectsFirebase 将快照值转换为对象
【发布时间】:2016-10-28 14:00:57
【问题描述】:

所以我有一个postDict 作为[String: AnyObject],我有一个模型类Post

有没有一种快速的方法可以将postDict 转换为Post 对象的数组,这样在出列单元格时,它将是:

cell.textLabel.text = posts[indexPath.item].author

import UIKit
import Firebase

class ViewController: UIViewController {

var posts = [Post]()

override func viewDidLoad() {
    super.viewDidLoad()

    let ref = FIRDatabase.database().reference().child("posts").queryLimitedToFirst(5)

    ref.observeEventType(FIRDataEventType.ChildAdded, withBlock: { (snapshot) in
        let postDict = snapshot.value as! [String : AnyObject]

        print(postDict)          

        //convert postDict to array of Post objects
    })
  }
}

class Post: NSObject {
    var author: String = ""
    var body: String = ""
    var imageURL: String = ""
    var uid: String = ""
}

这是打印出 postDict 时的输出:

【问题讨论】:

  • 我不确定,但这可能有帮助吗? stackoverflow.com/questions/37106246/…
  • 在这种情况下,snapshot.value 是单个节点(由于 .ChildAdded),因此它将代表单个帖子。我会建议两件事之一。 1) 向 Post 类添加一个名为:func populateFromSnapshot(aSnapshot: FDataShapshot) 或 2) 在块内创建新帖子并从快照本身填充它的函数。 Post.author = snapshot.value["author"] as!细绳。将其包装在一个 let 中以进行错误检查,以防未包装的为 nil。然后将帖子添加到数组 Posts.append(newPost)
  • 啊,更新的问题。对于新案例,您希望使用 for child in snapshot.children 迭代快照。每个孩子都将是一个不同的职位。我们已经在堆栈溢出时发布了很多关于如何在此处执行此操作的答案,您实际上可以通过该短语进行搜索。有关模式,请参阅答案 Adding firebase data to an array in ios swift。您可以使用我上面的注释来填充要添加到数组中的对象。

标签: swift firebase firebase-realtime-database


【解决方案1】:

我发现的最简单的解决方案是将对象转换为 JSON 数据,如下所示:

let jsonData =  try! JSONSerialization.data(withJSONObject: snapshot.value!, options: .prettyPrinted)

然后使用 JSONDecoder 解析对象:

try! JSONDecoder().decode(UserOtherDetails.self, from: jsonData)

完整的解决方案如下所示:

Database.database().reference().child("USER").observe(.value) { (snapshot) in
    let jsonData =  try! JSONSerialization.data(withJSONObject: snapshot.value!, options: .prettyPrinted)
    self.userOtherDetails = try! JSONDecoder().decode(UserOtherDetails.self, from: jsonData)
}

对象的一个​​例子是:

struct UserOtherDetails: Codable {
   let addressBlock, addressCity: String
   let addressIDS: [Int]
   let addressUnit, displayName, phone: String
   let rememberMe: Int

   enum CodingKeys: String, CodingKey {
      case addressBlock = "address_block"
      case addressCity = "address_city"
      case addressIDS = "address_ids"
      case addressUnit = "address_unit"
      case displayName = "display_name"
      case phone
      case rememberMe = "remember_me"
   }
}

【讨论】:

    【解决方案2】:

    我找到了一个更简单的方法。

    Swift 对象:

    import Foundation
    
    class FirebaseTransactionData : NSObject{
    
        var customer : FirebaseTransactionDataCustomer!
        var driver : FirebaseTransactionDataCustomer!
        var status : String!
    
        init(fromDictionary dictionary: [String:Any]){
            status = dictionary["status"] as? String
            if let customerData = dictionary["customer"] as? [String:Any]{
                customer = FirebaseTransactionDataCustomer(fromDictionary: customerData)
            }
            if let driverData = dictionary["driver"] as? [String:Any]{
                driver = FirebaseTransactionDataCustomer(fromDictionary: driverData)
            }
        }
    }
    
    class FirebaseTransactionDataCustomer : NSObject{
    
        var lat : Double!
        var longField : Double!
    
        init(fromDictionary dictionary: [String:Any]){
            lat = dictionary["lat"] as? Double
            longField = dictionary["lng"] as? Double
        }
    }
    

    Firebase 方法

    ref.observe(DataEventType.value, with: { (snapshot) in
                let value = snapshot.value as? [String:Any]
                let datt = FirebaseTransactionData(fromDictionary: value!)
                print("snapshot \(datt.status!)")
                print("snapshot \(datt.customer.lat!)")
            })
    

    【讨论】:

      【解决方案3】:

      我编写了一个名为 CodableFirebase 的小框架,它有助于在 swift 4 中将 Firebase 实时数据库与 Codable 结合使用。因此,在您的情况下,您需要使您的 Post 模型符合 Codable

      class Post: NSObject, Codable {
          var author: String = ""
          var body: String = ""
          var imageURL: String = ""
          var uid: String = ""
      }
      

      然后你就可以使用库来解析对象了:

      import CodableFirebase
      
      ref.observeEventType(.сhildAdded, withBlock: { (snapshot) in
          guard let value = snapshot.value else { return }
          do {
              let posts = try FirebaseDecoder().decode([Post].self, from: value)
              print(posts)
          } catch let error {
              print(error)
          }
      })
      

      就是这样 :) 我认为这是最短且最优雅的方式。

      【讨论】:

      • 如何检查某些密钥是否不是每次都来自 firebase 数据库。?
      【解决方案4】:

      此代码在 swift 4 中不再有效,因为默认情况下禁用了 @objc 推理。

      更新适用于 Swift 4

      class FIRDataObject: NSObject {
      
          let snapshot: FIRDataSnapshot
          @objc var key: String { return snapshot.key }
          var ref: FIRDatabaseReference { return snapshot.ref }
      
          required init(snapshot: FIRDataSnapshot) {
      
              self.snapshot = snapshot
      
              super.init()
      
              for child in snapshot.children.allObjects as? [FIRDataSnapshot] ?? [] {
                  if responds(to: Selector(child.key)) {
                      setValue(child.value, forKey: child.key)
                  }
              }
          }
      }
      
      class Post: FIRDataObject {
      
          @objc var author: String = ""
          @objc var body: String = ""
          @objc var imageURL: String = ""
      }
      

      或者您可以通过(警告:性能损失)将@objc 推断为项目的默认值: The use of Swift 3 @objc inference in Swift 4 mode is deprecated?

      【讨论】:

      • 这意味着项目中的所有模型都需要是类。如果我们想将它们保留为结构或枚举怎么办?
      【解决方案5】:

      这是上面 Callam 代码的 Objective-C 版本。

      @import Firebase;
      
      @interface FIRDataObject : NSObject
      
      @property (strong, nonatomic) FIRDataSnapshot *snapshot;
      @property (strong, nonatomic, readonly) NSString *key;
      @property (strong, nonatomic, readonly) FIRDatabaseReference *ref;
      
      -(instancetype)initWithSnapshot:(FIRDataSnapshot *)snapshot;
      
      @end
      
      @implementation FIRDataObject
      
      -(NSString *)key
      {
          return _snapshot.key;
      }
      
      -(FIRDatabaseReference *)ref
      {
          return _snapshot.ref;
      }
      
      -(instancetype)initWithSnapshot:(FIRDataSnapshot *)snapshot
      {
       if (self = [super init])
       {
           _snapshot = snapshot;
           for (FIRDataSnapshot *child in snapshot.children.allObjects)
           {
               if ([self respondsToSelector:NSSelectorFromString(child.key)])
               {
                   [self setValue:child.value forKey:child.key];
               }
           }
       }
          return self;
      }
      

      现在我们需要的只是模型级联和属性类型强制。

      【讨论】:

        【解决方案6】:

        我正在创建一个助手来轻松将快照转换为对象,反之亦然。我还没有完成我的项目,但到目前为止它正在工作,我会随时更新。

        类所做的是自动为key赋值,但是如果key代表一个字典,那么它会再次映射到另一个对象(可能是另一个类对象)

        getMap 方法非常简单,将每个属性转换为字典或对象。当属性是另一个对象时。您不能分配 nil 值,因此必须对 [NSNull] 进行转换。

        我找不到自动检测 BOOL / Double / int 等的方法,因此它们应该在 getMap 方法上正确映射,或者只是在属性模型中使用 NSNumbers。

        界面

        #import <Foundation/Foundation.h>
        @import FirebaseDatabase;
        
        #ifndef FIRModel_m
        
        #define FIRModel_m
        
        #define IS_OBJECT(T) _Generic( (T), id: YES, default: NO)
        
        #endif
        
        
        
        /** Firebase model that helps converting Firebase Snapshot to object, and converting the object
         * to a dictionary mapping for updates */
        @interface FIRModel : NSObject
        
        /** Parses the snapshot data into the object */
        - (void) parseFromSnapshot: (FIRDataSnapshot*) snapshot;
        
        /** Returns a new model for the given key */
        - (FIRModel*) modelForKey: (NSString*) key;
        
        /** Returns the dictionary representation of this object */
        - (NSMutableDictionary*) getMap;
        
        /** Returns an object value for the given preference
         * If the property is null, then NSNUll is returned
         */
        - (NSObject*) objFor: (id) value;
        
        @end
        

        实施

        #import "FIRModel.h"
        
        @implementation FIRModel
        /** Parses the snapshot data into the object */
        - (void) parseFromSnapshot: (FIRDataSnapshot*) snapshot {
        
            [self setValuesFromDictionary: snapshot.value];
        }
        
        /** Custom implementation for setValuesForKeysWithDictionary 
         *  Whenever it finds a Dictionary, it is transformed to the corresponding model object
         */
        - (void)setValuesFromDictionary:(NSDictionary*)dict
        {
        
            NSLog(@"Parsing in %@ the following received info: %@", [self class], dict);
        
        
            for (NSString* key in dict) {
        
                NSObject* value = [dict objectForKey:key];
        
                if(!value || [value isKindOfClass: [NSNull class]]) {
                    //do nothing, value stays null
                }
        
        
                //TODO: Do the same for arrays
                else if(value && [value isKindOfClass: [NSDictionary class]]) {
        
                    FIRModel* submodel = [self modelForKey: key];
        
                    if(submodel) {
                        [submodel setValuesFromDictionary: (NSDictionary*)value];
                        [self setValue: submodel forKey: key];
                    } else {
                        NSLog(@"ERROR - *** Nil model returned from modelForKey for key: %@ ***", key );
                    }
                }
                else {
                    [self setValue: value forKey:key];
                }
        
            }
        }
        
        /** Override for added firebase properties**/
        - (void)setValue:(id)value forUndefinedKey:(NSString *)key {
            NSLog(@"Unknown key: %@ on object: %@", key, [self class] );
        }
        
        /** Returns a new model for the given key */
        - (FIRModel*) modelForKey: (NSString*) key {
            return nil; //to be implemented by subclasses
        }
        
        /** Returns the dictionary representation of this object */
        - (NSMutableDictionary*) getMap {
            [NSException raise:@"getMap not implmented" format:@"ERROR - Not implementing getMap for %@", self.class];
            return [NSMutableDictionary dictionary];
        }
        
        /** Returns an object value for the given preference
         * If the property is null, then NSNUll is returned
         */
        - (NSObject*) objFor: (id) value {
        
            if(!value || !IS_OBJECT(value)) {
                return [NSNull null];
            }
        
            return value;
        
        
        }
        
        
        @end
        

        使用示例:

        #import <Foundation/Foundation.h>
        #import "FIRModel.h"
        
        
        /** The user object */
        @class PublicInfo;
        
        
        @interface User : FIRModel
        
        @property (nonatomic, strong) NSString* email;
        
        @property (nonatomic, strong) NSString* phone;
        
        @property (nonatomic, strong) PublicInfo* publicInfo;
        
        @property (nonatomic, assign) double aDoubleValue;  
        
        @property (nonatomic, assign) BOOL aBoolValue;  
        
        @property (nonatomic, strong) id timestampJoined;   //Map or NSNumber
        
        @property (nonatomic, strong) id timestampLastLogin;  //Map or NSNumber
        
        
        @end
        
        
        
        @interface PublicInfo : FIRModel
        
        @property (nonatomic, strong) NSString* key;
        
        @property (nonatomic, strong) NSString* name;
        
        @property (nonatomic, strong) NSString* pic;
        
        @end
        

        实施

        #import "User.h"
        
        
        @implementation User
        
        /** Returns a new model for the given key */
        - (FIRModel*) modelForKey: (NSString*) key {
            if ([key isEqualToString: @"publicInfo"]) {
                return [[PublicInfo alloc] init];
            }
            return nil;
        }
        
        - (NSMutableDictionary *)getMap {
        
            NSMutableDictionary* map = [NSMutableDictionary dictionary];
            map[@"email"] =  [self objFor: self.email];
            map[@"phone"] = [self objFor: self.phone];
            map[@"aDoubleValue"] = @(self.aDoubleValue);
            map[@"aBoolValue"] = @(self.aBoolValue);
            map[@"publicInfo"] = self.publicInfo ? [self.publicInfo getMap] : [NSNull null];
            map[@"timestampJoined"] =  [self objFor: self.timestampJoined];
            map[@"timestampLastLogin"] = [self objFor: self.timestampLastLogin];
        
            return map;
        }
        
        @end
        
        
        #pragma mark -
        
        @implementation PublicInfo
        
        - (NSMutableDictionary *)getMap {
        
            NSMutableDictionary* map = [NSMutableDictionary dictionary];
            map[@"name"] =  [self objFor: self.name];
            map[@"pic"] =  [self objFor: self.pic];
            map[@"key"] = [self objFor: self.key];
        
            return map;
        }
        
        @end
        

        用法

        //Parsing model
        User *user = [[User alloc] init];
        [user parseFromSnapshot: snapshot];
        
        //Getting map for updateChildValues method
        [user getMap]
        

        【讨论】:

        • 您是否为此创建了任何 git 存储库?还是更新了?
        【解决方案7】:

        尝试使用我在下面创建的类、协议和扩展,它将为您节省大量尝试将快照映射到对象的时间。

        //
        //  FIRDataObject.swift
        //
        //  Created by Callam Poynter on 24/06/2016.
        //
        
        import Firebase
        
        class FIRDataObject: NSObject {
        
            let snapshot: FIRDataSnapshot
            var key: String { return snapshot.key }
            var ref: FIRDatabaseReference { return snapshot.ref }
        
            required init(snapshot: FIRDataSnapshot) {
        
                self.snapshot = snapshot
        
                super.init()
        
                for child in in snapshot.children.allObjects as? [FIRDataSnapshot] ?? [] {
                    if respondsToSelector(Selector(child.key)) {
                        setValue(child.value, forKey: child.key)
                    }
                }
            }
        }
        
        protocol FIRDatabaseReferenceable {
            var ref: FIRDatabaseReference { get }
        }
        
        extension FIRDatabaseReferenceable {
            var ref: FIRDatabaseReference {
                return FIRDatabase.database().reference()
            }
        }
        

        现在您可以创建一个继承 FIRDataObject 类并可以使用 FIRDataSnapshot 进行初始化的模型。然后将 FIRDatabaseReferenceable 协议添加到您的 ViewController 以访问您的基本引用。

        import Firebase
        import UIKit
        
        class ViewController: UIViewController, FIRDatabaseReferenceable {
        
            var posts: [Post] = []
        
            override func viewDidLoad() {
        
                super.viewDidLoad()
        
                ref.child("posts").observeEventType(.ChildAdded, withBlock: {
                    self.posts.append(Post(snapshot: $0))
                })
            }
        }
        
        class Post: FIRDataObject {
        
            var author: String = ""
            var body: String = ""
            var imageURL: String = ""
        }
        

        更新 适用于 Swift 3

        class FIRDataObject: NSObject {
        
            let snapshot: FIRDataSnapshot
            var key: String { return snapshot.key }
            var ref: FIRDatabaseReference { return snapshot.ref }
        
            required init(snapshot: FIRDataSnapshot) {
        
                self.snapshot = snapshot
        
                super.init()
        
                for child in snapshot.children.allObjects as? [FIRDataSnapshot] ?? [] {
                    if responds(to: Selector(child.key)) {
                        setValue(child.value, forKey: child.key)
                    }
                }
            }
        }
        

        【讨论】:

        • 不错!您将如何处理嵌套对象?
        • 确认对象响应子键作为选择器后,我检查子键作为选择器的类是否为FIRDataObject类型。然后您可以从子快照中初始化 FIRDataObject 并在父对象上设置子键的值。
        • @callam,您应该将此代码作为要点放在 GitHub 中,以便跟踪更新。我在网上找到了您的代码的多个版本,并试图保持它们的正确性
        • @otusweb 好的,我会尽快调查的
        • 如何将“发布”对象写回 Firebase?有没有快速的方法?
        【解决方案8】:

        感谢以上所有 cmets 和提示。他们当然有帮助。所以我使用 setValuesForKeysWithDictionary 的方法。它让他们进入一系列帖子。

        import UIKit
        import Firebase
        class ViewController: UIViewController {
        
        var posts = [Post]()
        
        override func viewDidLoad() {
            super.viewDidLoad()
        
            let ref = FIRDatabase.database().reference().child("posts").queryLimitedToFirst(3)
        
            ref.observeEventType(.Value, withBlock: { snapshot in
                print(snapshot.value)
                self.posts = []
                if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
                    for snap in snapshots {
                        if let postDict = snap.value as? Dictionary<String, AnyObject> {
                            let post = Post()
                            post.setValuesForKeysWithDictionary(postDict)
                            self.posts.append(post)
                        }
                    }
                }
                print("post 0: \(self.posts[0].body)")
                print("post 1: \(self.posts[1].body)")
                print("post 2: \(self.posts[2].body)")
              })
           }
        }
        
        class Post: NSObject {
            var author: String = ""
            var body: String = ""
            var imageURL: String = ""
            var uid: String = ""
        }
        

        【讨论】:

        • 将“Post”对象写回firebase怎么样?
        猜你喜欢
        • 2021-03-25
        • 1970-01-01
        • 1970-01-01
        • 2020-08-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多