web-dev-qa-db-fra.com

Firebase convertissant la valeur de l'instantané en objets

J'ai donc une postDict en tant que [String: AnyObject] et une classe de modèle Post

Existe-t-il un moyen rapide de convertir postDict en un tableau d'objets Post afin que, lors de la mise en file d'attente de la cellule, ce soit:

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 = ""
}

Voici le résultat lors de l’impression postDict:

 enter image description here

13
Daryl Wong

Essayez d'utiliser la classe, le protocole et l'extension que j'ai créés ci-dessous, cela vous fera gagner beaucoup de temps en essayant de mapper les instantanés aux objets.

//
//  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()
    }
}

Vous pouvez maintenant créer un modèle qui hérite de la classe FIRDataObject et peut être initialisé avec un FIRDataSnapshot. Ajoutez ensuite le protocole FIRDatabaseReferenceable à votre ViewController pour avoir accès à votre référence de base.

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 = ""
}

UPDATEpour 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)
            }
        }
    }
}
21
Callam

Merci pour tous les commentaires et astuces ci-dessus. Ils ont certainement aidé. J'utilise donc la méthode avec setValuesForKeysWithDictionary. Cela les met dans un tableau de messages. 

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 = ""
}
8
Daryl Wong

J'ai écrit un petit cadre appelé CodableFirebase qui facilite l'utilisation de Firebase Realtime Database avec Codable dans Swift 4. Ainsi, dans votre cas, vous devez conformer votre modèle Post à Codable:

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

Et ensuite, vous pouvez utiliser la bibliothèque pour analyser l'objet:

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)
    }
})

Et c'est tout :) Je pense que c'est le moyen le plus court et le plus élégant.

8
Noobass

Ce code ne fonctionne plus dans Swift 4 car l'inférence @objc est désactivée par défaut.

UPDATE pour 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 = ""
}

Vous pouvez également faire de l’inférence @objc une valeur par défaut de votre projet en procédant comme suit (AVERTISSEMENT: perte de performances): L’utilisation de l’inférence Swift 3 @objc en mode Swift 4 est déconseillée?

2
Rodrigo Guimarães

Je crée un assistant pour faciliter la transformation des instantanés en objets et vice-versa. Je n'ai pas fini mon projet mais ça marche jusqu'à présent, je le mettrai à jour chaque fois que je changerai.

La classe attribue automatiquement la valeur pour la clé, mais si la clé représente un dictionnaire, elle est à nouveau mappée sur un autre objet (qui peut être un autre objet de classe).

La méthode getMap est assez simple, convertissant chaque propriété en dictionnaire ou en objet. Lorsque la propriété est un autre objet. Vous ne pouvez pas affecter de valeur nulle, la transformation doit donc être effectuée vers [NSNull].

Je ne pouvais pas trouver un moyen de détecter automatiquement BOOL/Double/int, etc., ils devraient donc être mappés correctement sur la méthode getMap ou tout simplement utiliser NSNumbers dans le modèle des propriétés.

Interface

#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

La mise en oeuvre

#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

Exemple d'utilisation:

#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

La mise en oeuvre

#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

Utilisation

//Parsing model
User *user = [[User alloc] init];
[user parseFromSnapshot: snapshot];

//Getting map for updateChildValues method
[user getMap]
0
htafoya

Voici une version Objective-C du code de Callam ci-dessus.

@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;
}

Tout ce dont nous avons besoin maintenant est une cascade de modèles et une application de type de propriété.

0
psobko