I want to make myself things easier so I'm creating a dictionary that is used by other function (to reach key by object or object by key) but that dictionary is always static. Is this fine way to do it or I need property or something else?
+ (NSDictionary *)dictionaryWithCategoriesAndStrings
{
return #{
kNewsCategoryAll : #(NewsCategoryAll),
kNewsCategoryRadio : #(NewsCategoryRadio),
kNewsCategoryEconomics : #(NewsCategoryEconomics),
kNewsCategoryCulture : #(NewsCategoryCulture),
kNewsCategorySport : #(NewsCategorySport),
kNewsCategoryTravel : #(NewsCategoryTravel),
kNewsCategoryMusic : #(NewsCategoryMusic),
kNewsCategorySociety : #(NewsCategorySociety),
kNewsCategoryHealth : #(NewsCategoryHealth)
};
}
So now I always access this same dictionary through function [self dictionaryWithCategoriesAndString];
Note: Those are keys are static strings declared at top and objects are NSNumbers with integer.
Rather than exposing the static to the entire class, you could create it within the method, and initialise it only once with gcd:
This is thread safe.
+ (NSDictionary *)dictionaryWithCategoriesAndStrings {
static NSDictionary *dict;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dict = #{
kNewsCategoryAll : #(NewsCategoryAll),
kNewsCategoryRadio : #(NewsCategoryRadio),
kNewsCategoryEconomics : #(NewsCategoryEconomics),
kNewsCategoryCulture : #(NewsCategoryCulture),
kNewsCategorySport : #(NewsCategorySport),
kNewsCategoryTravel : #(NewsCategoryTravel),
kNewsCategoryMusic : #(NewsCategoryMusic),
kNewsCategorySociety : #(NewsCategorySociety),
kNewsCategoryHealth : #(NewsCategoryHealth)
};
});
return dict;
}
You will be creating a new NSDictionary every time you call this method, so you won't really be accessing the same dictionary as it will just be a new identical one each time. You won't be able to make a property either if you're using it statically. Maybe something a little more like this just so you are accessing the same dictionary each time.
To access this in an instance method, you could use [[self class] dictionaryWithCategoriesAndStrings].
static NSDictionary* dict;
+ (NSDictionary *)dictionaryWithCategoriesAndStrings
{
if(dict == nil)
{
dict = #{
kNewsCategoryAll : #(NewsCategoryAll),
kNewsCategoryRadio : #(NewsCategoryRadio),
kNewsCategoryEconomics : #(NewsCategoryEconomics),
kNewsCategoryCulture : #(NewsCategoryCulture),
kNewsCategorySport : #(NewsCategorySport),
kNewsCategoryTravel : #(NewsCategoryTravel),
kNewsCategoryMusic : #(NewsCategoryMusic),
kNewsCategorySociety : #(NewsCategorySociety),
kNewsCategoryHealth : #(NewsCategoryHealth)
};
}
return dict;
}
Almost right. Every time that selector is executed, a new NSDictionary is created. That is bad. It should only be created once, and it should only be created lazily.
#property (strong, nonatomic) NSDictionary *categoryDict;
- (NSDictionary *) categoryDict
{
if( !_categoryDict)
{
_categoryDict = #{ #"Key" :#"value" , ....};
}
return _categoryDict;
}
Now the dictionary is only created once. and you can get to the dictionary using dot notation.
Related
In my MTLModel subclass I have this:
#property (assign, nonatomic) NSInteger catId;
And of course this in the implementation:
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"catId" : #"cat_id"
};
}
But what if my server friends decide to change cat_id to a string in the JSON response? How can I handle this case, and convert it to an int so that I don't get Mantle errors?
We also used Mantle for quite some time, but at the end migrated to handwritten parser/serializers, as the task itself seem to be trivial.
Though, we also have this kind of problems: what if server return something we do not expect (e.g. NSDictionary instead of NSString).
To prevent our app from crashing we use this simple tool: Fuzzer.
Basically the tool provides a method that takes a sample and a block. The block evaluates several times, each time bringing slightly mutated version of the sample. You can check behaviour of the models/parsers/serializers using the mutants, to ensure that your code handles unexpected data gracefully.
Here is example taken from project's README:
- (void)test {
NSDictionary *sample = #{
#“name” : #“John Doe”,
#“age” : #42
};
UserDeserializer *deserializer = [UserDeserializer new];
FZRRunner *runner = [FZRRunner runnerWithBuiltinMutationsForSample:sample];
NSArray *reports = [runner enumerateMutantsUsingBlock:^(NSDictionary *mutant) {
[deserializer deserializeUser:mutant];
}];
XCTAssertEqual(reports.count, 0);
}
if([obj isKindOfClass:NSClassFromString(#"__NSCFNumber")])
{
//if it is int or number
}
else
{
}
May be above method will help you
I need to have one property which is not present in JSON but it need to be set when I try to use this Model Object. I think it'd be easier to show you the example...
Here is my sample JSON:
[{
"name" : "Safari",
"key" : "safari",
"app_url_scheme" : "http://www.twitter.com",
"actions" :
[{
"key" : "show_profile",
"url_format" : "http://www.twitter.com/{{profile_screenname}}"
}]
}
]
And JSONKeyPathsByPropertyKey method looks like this:
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
return #{
#"appName" : #"name",
#"appKey" : #"key",
#"appURLScheme" : #"app_url_scheme",
#"appActions" : #"actions",
#"isInstalled" : NSNull.null
};
}
So, as you can see there is this isInstalled method which should be ignored during mapping (as there isn't field like this in JSON) but I need to set this property as soon as this Object is fully mapped. What is more I need to set it based on other property provided in JSON. How to achieve this?
I've found solution like this:
#"videoType" : #"#Selector(videoTypeFromString:, type)",
//! implemented on instance you are parsing
- (NSUInteger)videoTypeFromString:(NSString *)type
{
if ([type isEqualToString:#"shortVideo"]) {
return VideoTypeShort;
}
return VideoTypeLong;
}
But this #selector is never being called...
How can I use Github Mantle to choose a property class based on another property in the same class? (or in the worse case another part of the JSON object).
For example if i have an object like this:
{
"content": {"mention_text": "some text"},
"created_at": 1411750819000,
"id": 600,
"type": "mention"
}
I want to make a transformer like this:
+(NSValueTransformer *)contentJSONTransformer {
return [MTLValueTransformer transformerWithBlock:^id(NSDictionary* contentDict) {
return [MTLJSONAdapter modelOfClass:ETMentionActivityContent.class fromJSONDictionary:contentDict error:nil];
}];
}
But the dictionary passed to the transformer only includes the 'content' piece of the JSON, so I don't have access to the 'type' field. Is there anyway to access the rest of the object? Or somehow base the model class of 'content' on the 'type'?
I have previously been forced to do hack solutions like this:
+(NSValueTransformer *)contentJSONTransformer {
return [MTLValueTransformer transformerWithBlock:^id(NSDictionary* contentDict) {
if (contentDict[#"mention_text"]) {
return [MTLJSONAdapter modelOfClass:ETMentionActivityContent.class fromJSONDictionary:contentDict error:nil];
} else {
return [MTLJSONAdapter modelOfClass:ETActivityContent.class fromJSONDictionary:contentDict error:nil];
}
}];
}
You can pass the type information by modifying the JSONKeyPathsByPropertyKey method:
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
return #{
NSStringFromSelector(#selector(content)) : #[ #"type", #"content" ],
};
}
Then in contentJSONTransformer, you can access the "type" property:
+ (NSValueTransformer *)contentJSONTransformer
{
return [MTLValueTransformer ...
...
NSString *type = value[#"type"];
id content = value[#"content"];
];
}
I've had a similar issue, and I suspect my solution isn't much better than yours.
I have a common base class for my Mantle objects, and after each is constructed, I call a configure method to give them chance to set up properties that are dependent on more than one "base" (== JSON) property.
Like this:
+(id)entityWithDictionary:(NSDictionary*)dictionary {
NSError* error = nil;
Class derivedClass = [self classWithDictionary:dictionary];
NSAssert(derivedClass,#"entityWithDictionary failed to make derivedClass");
HVEntity* entity = [MTLJSONAdapter modelOfClass:derivedClass fromJSONDictionary:dictionary error:&error];
NSAssert(entity,#"entityWithDictionary failed to make object");
entity.raw = dictionary;
[entity configureWithDictionary:dictionary]; // Give the new entity a chance to set up derived properties
return entity;
}
While using Mantle, is there a possibility to before returning the object we are creating (on this case via JSON) to verify that X and Y properties are not nil?
Imagine this class:
#interface Person : MTLModel <MTLJSONSerializing>
#property(nonatomic,strong,readonly)NSString *name;
#property(nonatomic,strong,readonly)NSString *age;
#end
I want a way to verify that if the JSON I received doesn't have the name (for some reason there was an issue on server's DB) I will return a nil Person, as it doesn't make sense to create that object without that property set.
Whilst you could override the initialiser. It seems more concise to override the validate: as this is called at the last stage before Mantle returns the deserialised object. Makes sense to put all your validation logic in a validate method...
See the final line of the MTLJSONAdapter
id model = [self.modelClass modelWithDictionary:dictionaryValue error:error];
return [model validate:error] ? model : nil;
This tells us that if our custom model returns NO from validate, then Mantle will discard the object.
So you could in your subclass simply perform the following:
- (BOOL)validate:(NSError **)error {
return [super validate:error] && self.name.length > 0;
}
Ideally in your own implementation you probably want to return an appropriate error.
The validate method will then call Foundation's validateValue:forKey:error: for every property that you registered with Mantle in JSONKeyPathsByPropertyKey. So if you want a more controlled validation setup you could also validate your data here..
You can use the MTLJSONSerializing protocol method classForParsingJSONDictionary: to return nil rather than an invalid object:
// In your MTLModelSubclass.m
//
+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary {
if (JSONDictionary[#"name"] == nil || JSONDictionary[#"age"] == nil) {
return nil;
}
return self.class;
}
In fact I don't use Mantle, but for validation I use another GitHub Library called RPJSONValidator
It tells you the type that you expect and what type the value has been arrived.
A simple example code
NSError *error;
[RPJSONValidator validateValuesFrom:json
withRequirements:#{
#"phoneNumber" : [RPValidatorPredicate.isString lengthIsGreaterThanOrEqualTo:#7],
#"name" : RPValidatorPredicate.isString,
#"age" : RPValidatorPredicate.isNumber.isOptional,
#"weight" : RPValidatorPredicate.isString,
#"ssn" : RPValidatorPredicate.isNull,
#"height" : RPValidatorPredicate.isString,
#"children" : RPValidatorPredicate.isArray,
#"parents" : [RPValidatorPredicate.isArray lengthIsGreaterThan:#1]
} error:&error];
if(error) {
NSLog(#"%#", [RPJSONValidator prettyStringGivenRPJSONValidatorError:error]);
} else {
NSLog(#"Woohoo, no errors!");
}
Each key-value pair describes requirements for each JSON value. For example, the key-value pair #"name" : RPValidatorPredicate.isString will place a requirement on the JSON value with key "name" to be an NSString. We can also chain requirements. For example, #"age" : RPValidatorPredicate.isNumber.isOptional will place a requirement on the value of "age" to be an NSNumber, but only if it exists in the JSON.
I use a very old version of Mantle. YMMV
You can override the [MTLModel modelWithExternalRepresentation] selector. Make sure to call [super modelWithExternalRepresentation] and then add your own code to check for validate data.
I followed a small issue opened on Mantle:
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError *__autoreleasing *)error
{
BOOL isValid = NO;
if (self = [super initWithDictionary:dictionaryValue error:error])
{
isValid = ...
}
return isValid?self:nil;
}
So in the end just override:
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError *__autoreleasing *)error
Very new to Objective c. My interface and implementation looks like this:
// MyAuth.h
// #interface
+ (instancetype)sharedToken;
// MyAuth.m
//#implementation
+ (instancetype)sharedToken {
static MyAuth *_sharedToken = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedToken = [[NSUserDefaults standardUserDefaults] valueForKey:#"token"];
});
return _sharedToken;
}
Now I'm trying to get the length of the sharedToken but am stuck here. What I've tried
[MyAuth sharedToken].length // doesn't work
How can I get the length of `sharedToken
You want a string pulled from NSUserDefaults to be accessible everywhere in your app, via this method.
The return type of the method needs to be the type of the object you're actually returning:
+ (NSString *)sharedToken;
instancetype says that the method returns an instance of the class which runs the method.
The pointer you use for the string should also have the correct type:
static NSString *_sharedToken = nil;
Now the compiler will let you send length to the result of the method call.