2

I have a Objective-C application working fine and smooth, to be more comfortable with Swift I decided to write the unit tests for this app in Swift.

Some concepts in Swift looks weird for me, for example, in my app need to fill up a list of contents provided by server, in Objective-C, this looks (not exactly) like this:

[self.service requestContentsFrom:0 to:10 response:^(NSArray *result, NSArray *errors) {
  ContentModel *firstContent = result[0];
  NSLog("Content name: %@ Content date: %@", firstContent.name, firstContent.date);
}];

I this simple request, if result list (NSArray) be filled, I'll receive a list of ContentModel, because service instance implements some serialization level (thanks to JSONModel)

To make a simple test for this request in Swift, I did something like this:

func testContentsFromZeroToTen() {
  service?.requestContentsFrom(0, 10) { (result, errors) -> Void in

  }
}

The point of my question is, in Swift my result argument comes to [AnyObject]! and looks like this array are filled correctly, but the type inside that is not the same I received by Objective-C call.

If I try to cast to NSArray, for example....

let contents = result as NSArray

... then each content printed in console, looks like this:

(AnyObject) content = (instance_type = Builtin.RawPointer = 0x00007fd605213000 ->     0x000000010fa902c8 (void *)0x000000010fa902f0: __NSCFDictionary)

The most curious is, if I print contents, I can see more consistent information about it:

<__NSCFArray 0x7f95d7608110>(
{
  datetime = 1418855742226;
  id = 1;
  name = "Content 1";
},
{
  datetime = 1418855742326;
  id = 2;
  name = "Content 2";
)

Hey! my model is there, but where is my typed model, instead of a ContentModel, I see __NSCFDictionary

I hope to get an Swift array like this:

let contents = result as [ContentModel]

But when I try the code above, my contents looks like this when I print in console:

([ContentModel]) contents = 2 values {
  [0] = <error: use of undeclared identifier 'cocoarr'
error: 1 errors parsing expression
>

  [1] = <error: use of undeclared identifier 'cocoarr'
error: 1 errors parsing expression
>

}

So the question is, how can I cast Swift array correctly to get my typed NSArray like Objective-C?

UPDATE 1: Added ContentModel interface

#import <Foundation/Foundation.h>
#import "JSONModel.h"
#import "UserModel.h"

@interface ContentModel : JSONModel

@property (nonatomic, assign) NSInteger id;
@property (nonatomic, strong) NSDate *datetime;
@property (nonatomic, strong) NSString *text;

@property (nonatomic, strong) UserModel *user;

@end
3
  • Everything you've done is normal and correct. How do you know there's a problem? Get contents[0] and try to use it as a ContentModel. Can you? If so, no problem. Commented Dec 22, 2014 at 20:23
  • Oh, by the way, could you show your ContentModel declaration? Also, what's cocoarr? That looks like a clue. Commented Dec 22, 2014 at 20:25
  • Thanks for comments @matt, I used the two approaches suggested by @drewang, both approaches jump out of optional binding, seems Swift can't to cast to ContentModel even implicitly. Commented Dec 22, 2014 at 20:46

1 Answer 1

2

First, NSArray is not typed in Objective-C. It is an array of arbitrary classes. That is exactly what [AnyObject] is. AnyObject is a reference to any class.


Now, to access the elements of an AnyObject array as another, more specific type, you have two options:

Convert the entire array all at once:

func testContentsFromZeroToTen() {
  service?.requestContentsFrom(0, 10) { (result, errors) -> Void in
      if let contentModels = result as? [ContentModel] {
          for contentModel in contentModels {
          }
      }
  }
}

Or you can convert one element at a time:

func testContentsFromZeroToTen() {
  service?.requestContentsFrom(0, 10) { (result, errors) -> Void in
      for next in result {
          if let contentModel = next as? ContentModel {
          }
      }
  }
}

This second option allows for the scenario where some of the individual elements are not of the type you are expecting. In the first case, you will skip over the whole array if even one element is not a ContentModel.

Note: You can also do forced casting instead of the optional casting in my examples:

let contentModels = result as [ContentModel]
let contentModel = next as ContentModel

But these will crash you program if the casting fails.


Lastly, when you print out an object in Swift, it will print out some default (usually not very helpful information about it). You can have your custom types implement the Printable or DebugPrintable protocols if you want to customize what your objects print as:

class ContentModel: Printable {
    var description: String {
        return "Some description"
    }
}
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks @drewag, I tried both approaches before but the execution jumps out of optional binding. I updated my question adding my ContentModel interface, maybe will be helpful.
@JanCássio If it is jumping out then the objects in the array are not ContentModels. The only caveat to that is that the debugger might be acting weird as it often does right now in Swift. Check if it is getting into the optional bindings by logging out with println
Thanks again @drewag. Ok so when I do println("Result = (result)") and println("Next = (next)"), the values are printed correctly. The problem happens only in for casting implicitly.
@JanCássio I'm not sure what you mean. There is no implicit casting in Swift
Sorry @drewang, I mean, when I use downcast operation such as Type or when set a type to constant like let model:ContentModel
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.