I'm using RestKit (the latest version 0.23.1), and I have defined a custom transformer between a string value to a number field as follows:
mapping.valueTransformer = [RKBlockValueTransformer valueTransformerWithValidationBlock:^BOOL(__unsafe_unretained Class inputValueClass, __unsafe_unretained Class outputValueClass) {
return [inputValueClass isSubclassOfClass:[NSString class]];
} transformationBlock:^BOOL(id inputValue, __autoreleasing id *outputValue, __unsafe_unretained Class outputClass, NSError *__autoreleasing *error) {
if (![inputValue isKindOfClass:[NSString class]]) {
return NO;
}
// conversion logic follows...
return YES;
}];
The transformer works properly, however the validation block is not being called (I've set a breakpoint to verify that), which forces me to check in the transformationBlock that the inputValue is indeed an instance of NSString.
Why is the validation block not being invoked, what am I doing wrong?
I couldn't find any issue on this in RestKit GitHub page.
You are setting the transformer directly onto the mapping and as the only transformer so it is assumed that it always applies.
You want to set the transformer to a RKCompoundValueTransformer (Using defaultValueTransformer And your custom transformer) as it does perform validation before applying transformations.
Related
-(instancetype)filter:(BOOL (^)(id value))block {
NSCParameterAssert(block != nil);
Class class = self.class;
return [[self flattenMap:^ id (id value) {
if (block(value)) {
return [class return:value];
} else {
return class.empty;
}
}] setNameWithFormat:#"[%#] -filter:", self.name];
}
This is the implementation of filter of ReactiveCocoa.I don't know what this code means.Also I can't get any reference to the second return method.
return [class return:value];
Also, what does this instancetype mean? Suppose the value is a string and I check whether its length is greater than 2. What will be returned by using filter method?
The filter method invokes the class method of the current class to get a RACStream subclass using that method. Using return: will give a signal that sends the value passed and then completes. Using empty gives a signal that immediately sends completed without sending a next value, which removes the value filtered value from the stream thanks to flattenMap: switching out the signal with the one being created.
I'm stuck on why my transformable classes aren't being called. The following screenshot shows my Entity attribute as a transformable:
According to the documentation, it should automatically call the class "StringEncryptionTransformer" and perform the transformation.
I followed this guide in order to set up the class. I'm using the EncryptionTransformer and StringEncryptionTransformer classes provided, the only change I made was with the encryption to use RNcryptor.
Encryption:
return [RNEncryptor encryptData:data withSettings:kRNCryptorAES256Settings password:[self key] error:&error];
and Decryption:
return [RNDecryptor decryptData:data withPassword:[self key] error:&error];
The saved entity appears never to go through the transformation, is there something I'm missing? I tried adding an initialize to the NSManagedObject, but the results were the same.
you need to register value transformer like below
extension NSValueTransformerName {
static let classNameTransformerName = NSValueTransformerName(rawValue: "ClassNameTransformer")
}
ValueTransformer.setValueTransformer(ClassNameTransformer(), forName: .classNameTransformerName)
I am consuming JSON that is constantly evolving. I started writing code to consume the JSON using Mantle lately. It seemed like a very good choice for what I want to do. However, it seems that if the JSON being consumed has properties that do not exist in the model, the JSON transformation fails. I'm using the [MTLJSONAdapter modelOfClass:fromJSONDictionary:error:]; call to map the JSON.
Thanks in advance,
Upon closer inspection of the code, Mantle does require that all json properties map to something in the model. Otherwise, what will happen is an exception will be thrown for that property.
inside of MTLValidateAndSetValue of MTLModel, it doesn't check for the existence of the property before it sets it.
#try {
if (![obj validateValue:&validatedValue forKey:key error:error]) return NO;
if (forceUpdate || value != validatedValue) {
[obj setValue:validatedValue forKey:key];
}
return YES;
} #catch (NSException *ex) {
NSLog(#"*** Caught exception setting key \"%#\" : %#", key, ex);
// Fail fast in Debug builds.
#if DEBUG
#throw ex;
#else
if (error != NULL) {
*error = [NSError mtl_modelErrorWithException:ex];
}
return NO;
#endif
}
This is problematic if the JSON you are consuming isn't guaranteed to match your model.
I ended up doing a custom JSON representation for my work having the constructor build the object based on the incoming JSON rather than against the model. It will first iterate over the json properties and tries to map them directly to the model properties using implicit mapping. If there are any properties that require special handling, it is up to the subclass to override the init call and apply the transformation manually.
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
typedef void (^RequestProductsCompletionHandler)(BOOL success, NSArray * products);
I am having difficulty understanding what this line of code is doing in .h file.
Please explain in detail
typedef.
void (I know what void do, but whats the purpose here?).
(^RequestProductsCompletionHandler)(BOOL success, NSArray * products);
How to call it?
This is definition of objective-c block type with name RequestProductsCompletionHandler that takes 2 parameters (BOOL and NSArray) and does not have return value. You can call it the same way you would call c function, e.g.:
RequestProductsCompletionHandler handler = ^(BOOL success, NSArray * products){
if (success){
// Process results
}
else{
// Handle error
}
}
...
handler(YES, array);
Vladimir described it well. It defines a variable type which will represent a block that will pass two parameters, a boolean success and an array of products, but the block itself returns void. While you don't need to use the typedef, it makes the method declaration a tad more elegant (and avoids your having to engage in the complicated syntax of block variables).
To give you a practical example, one might infer from the name of the block type and its parameters that this defines a completion block (e.g. a block of code to be performed when some asynchronous operation, like a slow network request, completes). See Using a Block as a Method Argument.
For example, imagine that you had some InventoryManager class from which you could request product information, with a method with an interface defined like so, using your typedef:
- (void)requestProductsWithName:(NSString *)name completion:(RequestProductsCompletionHandler)completion;
And you might use the method like so:
[inventoryManager requestProductsWithName:name completion:^(BOOL success, NSArray * products) {
// when the request completes asynchronously (likely taking a bit of time), this is
// how we want to handle the response when it eventually comes in.
for (Product *product in products) {
NSLog(#"name = %#; qty = %#", product.name, product.quantity);
}
}];
// but this method carries on here while requestProductsWithName runs asynchronously
And, if you looked at the implementation of requestProductsWithName, it could conceivably look something like:
- (void)requestProductsWithName:(NSString *)name completion:(RequestProductsCompletionHandler)completion
{
// initiate some asynchronous request, e.g.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// now do some time consuming network request to get the products here
// when all done, we'll dispatch this back to the caller
dispatch_async(dispatch_get_main_queue(), {
if (products)
completion(YES, products); // success = YES, and return the array of products
else
completion(NO, nil); // success = NO, but no products to pass back
});
});
}
Clearly, this is unlikely to be precisely what your particular completion handler block is doing, but hopefully it illustrates the concept.
Mike Walker created a nice one page site that shows all possibilities to declare a block in Objective-C. This can be helpful to understand your problem as well:
http://fuckingblocksyntax.com
To quote his site, this is how you can define blocks:
As a local variable:
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
As a property:
#property (nonatomic, copy) returnType (^blockName)(parameterTypes);
As a method parameter:
- (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName {...}
As an argument to a method call:
[someObject someMethodThatTakesABlock: ^returnType (parameters) {...}];
As a typedef:
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^(parameters) {...}