Firebase конвертирует значение моментального снимка в объекты

Итак, у меня есть 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:

введите описание изображения здесь

Ответ 1

Попробуйте использовать класс, протокол и расширение, которые я создал ниже, это сэкономит вам много времени, пытаясь сопоставить моментальные снимки с объектами.

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

UPDATE для 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)
            }
        }
    }
}

Ответ 2

Спасибо за все комментарии и подсказки выше. Они, безусловно, помогли. Поэтому я использую метод с 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 = ""
}

Ответ 3

Я написал небольшую структуру под названием CodableFirebase, которая помогает использовать базу данных реального времени Firebase с помощью Codable в swift 4. Итак, в вашем случае, вам необходимо сопоставить модель 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)
    }
})

И что это:) Я думаю, что это самый короткий и самый элегантный способ.

Ответ 4

Этот код больше не работает в swift 4, потому что по умолчанию функция @objc отключена.

UPDATE для 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 выводом по умолчанию для вашего проекта с помощью (ВНИМАНИЕ: потеря производительности): Использование вывода Swift 3 @objc в режиме Swift 4 устарело?

Ответ 5

Я создаю помощник, чтобы упростить преобразование моментальных снимков в объекты и наоборот. Я еще не закончил свой проект, но он работает до сих пор, я буду обновлять всякий раз, когда я вношу изменения.

То, что класс делает, автоматически назначает значение для ключа, но если ключ представляет словарь, то он снова отображается на другой объект (который может быть другим объектом класса)

Метод 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]

Ответ 6

Здесь версия Objective-C кода Callam выше.

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

Теперь все, что нам нужно, это каскадирование модели и принудительное применение типа свойства.