siegesmund / SwiftDDP

A Meteor client, written in Swift
MIT License
145 stars 60 forks source link

Can't subscribe to publication: get a crush -[__NSCFBoolean length]: unrecognized selector sent to instance #31

Closed achirkof closed 7 years ago

achirkof commented 8 years ago

Hello! I can't subscribe to publication like this: Meteor publication

Meteor.publish('workouts', function() {
    return Workouts.find({});
});

Swift class:

class Workouts: MeteorDocument {
    var _id: String?
    var createdBy: String?
    var createdAt: NSDate?
    var workoutName: String?
    var workoutDesc: String?
    var workoutDuration: String?
    var workoutExercises: NSDictionary?
    var isPrivate: String?
    var isDeleted: String?
}

I trying to get document:

let workouts = MeteorCollection<Workouts>(name: "workouts")
                    Meteor.subscribe("workouts") {
                        let workout = workouts.sorted
                        print("WORKOUT: \(workout)")
                    }

But receive a crush just after call Meteor.subscribe("workouts") *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFBoolean length]: unrecognized selector sent to instance 0x10300a528' I thing the reason of crush is in fields isPrivate and isDeleted because there are boolean in Mongo

meteor:PRIMARY> db.workouts.find({_id: "Kdzs7BsYkAnBoE4Bg"}, {workoutExercises: 0})
{
        "_id" : "Kdzs7BsYkAnBoE4Bg",
        "workoutName" : "Test User2",
        "workoutDesc" : "<p>Detail of the programm<br></p>",
        "createdBy" : "QhKCfojmdC3Ep2oSz",
        "createdAt" : ISODate("2016-04-05T10:54:46.193Z"),
        "isPrivate" : true,
        "isDeleted" : false
}

but subscribtion retun's it as string or integer.

[Debug] [DDP] [DDP Background Data Queue :: NSOperation 0x7fee5bbe4c00 (QOS: BACKGROUND)] [260] ddpMessageHandler > Received message: {
    collection = workouts;
    fields =     {
        createdAt =         {
            "$date" = 1457870369448;
        };
        createdBy = BeNTtaupkj5JfpJcH;
        isDeleted = 0;
        isPrivate = 1;
        workoutDesc = "<p><br></p>";

Here the stack trace:

*** First throw call stack:
(
    0   CoreFoundation                      0x0000000102de1d85 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x0000000104b85deb objc_exception_throw + 48
    2   CoreFoundation                      0x0000000102dead3d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
    3   CoreFoundation                      0x0000000102d30cfa ___forwarding___ + 970
    4   CoreFoundation                      0x0000000102d308a8 _CF_forwarding_prep_0 + 120
    5   libswiftCore.dylib                  0x0000000105129168 _TTSf4g_d___TFSSCfT12_cocoaStringPs9AnyObject__SS + 120
    6   libswiftCore.dylib                  0x00000001050e9913 _TFSSCfT12_cocoaStringPs9AnyObject__SS + 19
    7   libswiftFoundation.dylib            0x0000000105487d70 _TF10Foundation24_convertNSStringToStringFGSqCSo8NSString_SS + 16
    8   smartworkout-ios                    0x00000001027b9c4b _TToFC16smartworkout_ios8Workoutss9isDeletedGSqSS_ + 75
    9   Foundation                          0x00000001031b319b -[NSObject(NSKeyValueCoding) setValue:forKey:] + 288
    10  SwiftDDP                            0x000000010288e036 _TTSf4s_n_n___TFC8SwiftDDP14MeteorDocumentcfT2idSS6fieldsGSqCSo12NSDictionary__S0_ + 262
    11  smartworkout-ios                    0x00000001027ba540 _TFC16smartworkout_ios8WorkoutscfT2idSS6fieldsGSqCSo12NSDictionary__S0_ + 784
    12  smartworkout-ios                    0x00000001027ba21e _TFC16smartworkout_ios8WorkoutsCfT2idSS6fieldsGSqCSo12NSDictionary__S0_ + 78
    13  SwiftDDP                            0x000000010287ab2c _TFC8SwiftDDP16MeteorCollection16documentWasAddedfTSS2idSS6fieldsGSqCSo12NSDictionary__T_ + 108
    14  SwiftDDP                            0x000000010288201b _TTWC8SwiftDDP18AbstractCollectionS_20MeteorCollectionTypeS_FS1_16documentWasAddedfTSS2idSS6fieldsGSqCSo12NSDictionary__T_ + 123
    15  SwiftDDP                            0x000000010289fdef _TTSf4gs_gs_g_d___TFCC8SwiftDDP6Meteor6Client16documentWasAddedfTSS2idSS6fieldsGSqCSo12NSDictionary__T_ + 447
    16  SwiftDDP                            0x000000010289f43d _TFCC8SwiftDDP6Meteor6Client16documentWasAddedfTSS2idSS6fieldsGSqCSo12NSDictionary__T_ + 29
    17  SwiftDDP                            0x000000010286cb77 _TFFC8SwiftDDP9DDPClient17ddpMessageHandlerFzVS_10DDPMessageT_U0_FT_T_ + 199
    18  Foundation                          0x000000010325b630 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 7
    19  Foundation                          0x0000000103196805 -[NSBlockOperation main] + 101
    20  Foundation                          0x0000000103179725 -[__NSOperationInternal _start:] + 646
    21  Foundation                          0x0000000103179336 __NSOQSchedule_f + 194
    22  libdispatch.dylib                   0x00000001061d83eb _dispatch_client_callout + 8
    23  libdispatch.dylib                   0x00000001061be82c _dispatch_queue_drain + 2215
    24  libdispatch.dylib                   0x00000001061bdd4d _dispatch_queue_invoke + 601
    25  libdispatch.dylib                   0x00000001061c0996 _dispatch_root_queue_drain + 1420
    26  libdispatch.dylib                   0x00000001061c0405 _dispatch_worker_thread3 + 111
    27  libsystem_pthread.dylib             0x00000001065154de _pthread_wqthread + 1129
    28  libsystem_pthread.dylib             0x0000000106513341 start_wqthread + 13
)

I have tried to change type of fields isPrivate and isDeleted in Swift class to optional Bool/String/Int without any results.

siegesmund commented 8 years ago

I'm not sure why this would be happening. From the stack trace, it looks like the problem might your NSDictionary property, not the boolean. Try omitting that property and see if it works. If it does, we know what to focus on.

achirkof commented 8 years ago

@siegesmund I think it is not to my NSDictionary property for var workoutExercises: NSDictionary? because if I change it for String/Dictionary or somthing else I get the same error and stack. I think it might be linked to MeteorDocument class

public class MeteorDocument : NSObject {
    required public init(id: String, fields: NSDictionary?)
    public func update(fields: NSDictionary?, cleared: [String]?)
}

What do you think?

achirkof commented 8 years ago

So.. After debugging and manipulate variable types in collection I found right combination:

class Workouts: MeteorDocument {
    var _id: String?
    var createdBy: String?
    var createdAt: NSDate?
    var workoutName: String?
    var workoutDesc: String?
    var workoutDuration: String?
    var workoutExercises: NSDictionary?
    var isPrivate: AnyObject?
    var isDeleted: AnyObject?
}

I's interesting because variables isPrivate and isDeleted boolean, but then I receive a response these are not.

siegesmund commented 8 years ago

Glad you got it working. In your subscription, isPrivate and isDeleted are both integers, not bools. It appears Meteor is sending them as integers, if I am understanding what you've posted. Can you just create a handler that corrects for that?

achirkof commented 8 years ago

@siegesmund Yes, Meteor return Int value(0 or 1). And I tried to set variable type for isPrivate & isDeleted to optional Int, but it didn't help. About handler, I'm novice in swift and don't know how to do it yet.

siegesmund commented 8 years ago

@achirkof What does the console show when you print isPrivate or isDeleted?

achirkof commented 8 years ago

Here is my collection class:

import UIKit
import SwiftDDP

class Workouts: MeteorDocument {

    var id: String?
    var createdBy: String?
    var createdAt: NSDate?
    var workoutName: String?
    var workoutDesc: String?
    var workoutDuration: NSNumber?
    var workoutExercises: [[String: AnyObject]]?
    var isPrivate: AnyObject?
    var isDeleted: AnyObject?

}

Document from MongoDB collection:

{
        "_id" : "NRZiZBB8ruGneytFh",
        "workoutName" : "Test",
        "workoutDesc" : "<p>Description</p>",
        "createdBy" : "BeNTtaupkj5JfpJcH",
        "createdAt" : ISODate("2016-04-21T11:54:52.949Z"),
        "isPrivate" : true,
        "isDeleted" : false
}

My ViewController code:

override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        let workouts = MeteorCollection<Workouts>(name: "workouts")
        Meteor.subscribe("workouts") {

            let workout = workouts.findOne("NRZiZBB8ruGneytFh")
            print("WORKOUT: \(workout)")
            print("WORKOUT IS DELETED VALUE: \(workout?.isDeleted)")
            print("WORKOUT IS PRIVATE VALUE: \(workout?.isPrivate)")
        }
    }

And message I received:

Subscribing to ID 2a082CB79e7A44ED9E0bA15a93db7b68
[Debug] [DDP] [DDP Background Data Queue :: NSOperation 0x7fe3bc0a0480 (QOS: BACKGROUND)] [260] ddpMessageHandler > Received message: {
    msg = ready;
    subs =     (
        2a082CB79e7A44ED9E0bA15a93db7b68
    );
}
WORKOUT: nil
WORKOUT IS DELETED VALUE: nil
WORKOUT IS PRIVATE VALUE: nil
[Debug] [DDP] [DDP Background Data Queue :: NSOperation 0x7fe3b9ff1c50 (QOS: BACKGROUND)] [260] ddpMessageHandler > Received message: {
    collection = workouts;
    fields =     {
        createdAt =         {
            "$date" = 1461239692949;
        };
        createdBy = BeNTtaupkj5JfpJcH;
        isDeleted = 0;
        isPrivate = 1;
        workoutDesc = "<p>Description</p>";
        workoutExercises =         (
                        {
                "_id" = mQdCocGXeHQhnpKjh;
                exerciseDesc = "<li>\U0421\U044f\U0434\U044c\U0442\U0435 \U043d\U0430 \U043f\U043e\U043b. \U0420\U0430\U0441\U0441\U0442\U0430\U0432\U044c\U0442\U0435 \U043d\U043e\U0433\U0438 \U043f\U0435\U0440\U0435\U0434 \U0441\U043e\U0431\U043e\U0439.</li><li>\U0412\U044b\U0442\U044f\U043d\U0443\U0432 \U0440\U0443\U043a\U0438 \U043f\U0430\U0440\U0430\U043b\U043b\U0435\U043b\U044c\U043d\U043e \U043f\U043e\U043b\U0443, \U043d\U0430\U043a\U043b\U043e\U043d\U0438\U0442\U0435\U0441\U044c \U0432\U043f\U0435\U0440\U0435\U0434, \U043d\U0430\U0441\U043a\U043e\U043b\U044c\U043a\U043e \U044d\U0442\U043e \U0432\U043e\U0437\U043c\U043e\U0436\U043d\U043e. \U0417\U0430\U0434\U0435\U0440\U0436\U0438\U0442\U0435\U0441\U044c \U0432 \U044d\U0442\U043e\U043c \U043f\U043e\U043b\U043e\U0436\U0435\U043d\U0438\U0438 \U043d\U0430 10-20 \U0441\U0435\U043a\U0443\U043d\U0434.</li>";
                exerciseImgs =                 (
                                        {
                        "_collectionName" = exerciseImages;
                        "_downloadRoute" = "/cdn/storage";
                        "_id" = g8HBqxNmwjnX2Srws;
                        "_prefix" = f18fc795aff7bc461588902a7840de5674a3cffabf709dc39af62a6022f4ebd0;
                        "_storagePath" = "/tmp/uploads/exercise-images";
                        extension = jpg;
                        isAudio = 0;
                        isImage = 1;
                        isVideo = 0;
                        meta =                         {
                        };
                        name = "418_1-300x200.jpg";
                        path = "/tmp/uploads/exercise-images/evBlwkiWRGYiWhqwV.jpg";
                        size = 14914;
                        type = "image/jpeg";
                        versions =                         {
                            original =                             {
                                extension = jpg;
                                path = "/tmp/uploads/exercise-images/evBlwkiWRGYiWhqwV.jpg";
                                size = 14914;
                                type = "image/jpeg";
                            };
                        };
                    },
                                        {
                        "_collectionName" = exerciseImages;
                        "_downloadRoute" = "/cdn/storage";
                        "_id" = xzhiiuAQ7TwKSS5rq;
                        "_prefix" = f18fc795aff7bc461588902a7840de5674a3cffabf709dc39af62a6022f4ebd0;
                        "_storagePath" = "/tmp/uploads/exercise-images";
                        extension = jpg;
                        isAudio = 0;
                        isImage = 1;
                        isVideo = 0;
                        meta =                         {
                        };
                        name = "418_2-300x200.jpg";
                        path = "/tmp/uploads/exercise-images/LEODojbWdxGJAqOcj.jpg";
                        size = 14914;
                        type = "image/jpeg";
                        versions =                         {
                            original =                             {
                                extension = jpg;
                                path = "/tmp/uploads/exercise-images/LEODojbWdxGJAqOcj.jpg";
                                size = 14914;
                                type = "image/jpeg";
                            };
                        };
                    }
                );
                exerciseName = "\U0420\U0430\U0441\U0442\U044f\U0436\U043a\U0430 \U043c\U044b\U0448\U0446 \U0432 \U043f\U043e\U043b\U043e\U0436\U0435\U043d\U0438\U0438 \U0441\U0438\U0434\U044f";
                mainMuscle = "\U0411\U0435\U0434\U0440\U0430";
                mechanicsType = "\U0414\U0440\U0443\U0433\U043e\U0435";
            }
        );
        workoutName = Test;
    };
    id = NRZiZBB8ruGneytFh;
    msg = added;
}

Maybe I'm doing something wrong because I receive nil.

siegesmund commented 8 years ago

And you've tried:

class Workouts: MeteorDocument {

    var _id: String?
    var createdBy: String?
    var createdAt: NSDate?
    var workoutName: String?
    var workoutDesc: String?
    var workoutDuration: String?
    var workoutExercises: NSDictionary?
    var isPrivate: Bool?
    var isDeleted: Bool?

}

?

achirkof commented 8 years ago

Yes. It was first what i'v tried. But as you can see Meteor return 1 and 0. Now I receive nil not only for separate fields but for whole document and I can't understand why.

override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        let workouts = MeteorCollection<Workouts>(name: "workouts")
        Meteor.subscribe("workouts") {

            let workout = workouts.findOne("NRZiZBB8ruGneytFh")
            print("WORKOUT: \(workout)")
            print("WORKOUT IS DELETED VALUE: \(workout?.isDeleted)")
            print("WORKOUT IS PRIVATE VALUE: \(workout?.isPrivate)")
        }
    }

and print

Subscribing to ID 2a082CB79e7A44ED9E0bA15a93db7b68
[Debug] [DDP] [DDP Background Data Queue :: NSOperation 0x7fe3bc0a0480 (QOS: BACKGROUND)] [260] ddpMessageHandler > Received message: {
    msg = ready;
    subs =     (
        2a082CB79e7A44ED9E0bA15a93db7b68
    );
}
WORKOUT: nil
WORKOUT IS DELETED VALUE: nil
WORKOUT IS PRIVATE VALUE: nil

But as you can see in my previous message document with this ID exists. The same situation for let workout = workouts.sorted. It returns empty array [].

sefasenturk95 commented 8 years ago

Hi @achirkof, is this problem still unsolved?

I think you have to implement the init init(id: String, fields: NSDictionary?) and the override func setValue(value: AnyObject?, forKey key: String) methods in your MeteorDocument to set the variables correctly. Here is an example;

`import Foundation import SwiftDDP

class DocumentUser: MeteorDocument {

var _id: String?
var services: NSMutableDictionary?
var emails: NSMutableArray?
var roles: NSMutableArray?
var profile: [String:AnyObject]?

required init(id: String, fields: NSDictionary?) {
    super.init(id: id, fields: fields)

    self._id = id
}

override func update(fields: NSDictionary?, cleared: [String]?) {
    super.update(fields, cleared: cleared)
}

override func setValue(value: AnyObject?, forKey key: String) {
    if (key == "_id") {
        self._id = value as? String
    }

    if (key == "services") {
        self.services = value as? NSMutableDictionary
    }

    if (key == "emails") {
        self.emails = value as? NSMutableArray
    }

    if (key == "roles") {
        self.roles = value as? NSMutableArray
    }

    if (key == "profile") {
        self.profile = value as? [String:AnyObject]
    }
}

}`

If you still can't get it to work I can help you out via Teamviewer or Screenhero.

sjkummer commented 5 years ago

Sorry for commenting such an old issue, but I just ran into that too.

However, there is no need to overwrite "setValue" and bloat your code. Just add @objc before the vars of your MeteorDocument, e.g.:

class Workouts : MeteorDocument {

@objc var createdBy?
...

}

This way, the Objective-C setValue mechanism still works with newer Swift versions.

This probably should be added to the README