How to store data with id keys which are not NSCopying - ios

I need to realize an interface that mimics MKMapView in some sense. Specifically, I need to support methods
-(void)addAnnotation:(id<MyAnnotation>)annotation;
-(UIView *)viewForAnnotation:(id<MyAnnotation>)annotation;
Internally there is a mapping which maps an annotation to an UIView object (which may be null).
So how should I store these annotations and corresponding views? The natural choice would be NSMutableDictionary:
#property(nonatomic) NSMutableDictionary *m_dict;
-(void)addAnnotation:(id<MyAnnotation>)annotation {
UIView *view = ....;
if (view) {
[_m_dict setObject:view forKey:annotation];
}
else {
[_m_dict setObject:[NSNull null] forKey:annotation];
}
}
-(UIView *)viewForAnnotation:(id<MyAnnotation>)annotation {
id ret = [_m_dict objectForKey:annotation];
if ([ret isKindOfClass:[UIView class]]) {
return ret;
}
return nil;
}
But it does not work, because there is no guarantee that id<MyAnnotation> conforms NSCopying.
I thought about saving id<MyAnnotation> to a raw pointer and then storing the pointer into NSNumber (which conforms NSCopying). But I am not sure is this a good idea and how to implement it safely.

You can make a unique string as keypath, and save it in your annotation.
#property(nonatomic) NSMutableDictionary *m_dict;
-(void)addAnnotation:(id<MyAnnotation>)annotation {
UIView *view = ....;
NSString *unique_id = ....;
[annotation setValue:unique_id forKey:#"_unique_id"];
if (view) {
[_m_dict setObject:view forKey:unique_id];
}
else {
[_m_dict setObject:[NSNull null] forKey:unique_id];
}
}
-(UIView *)viewForAnnotation:(id<MyAnnotation>)annotation {
id ret = [_m_dict objectForKey:annotation valueForKey:#"_unique_id"];
if ([ret isKindOfClass:[UIView class]]) {
return ret;
}
return nil;
}

Related

#NotEmpty annotation in Objective C?

Is there possibility to create custom annotation which will check if parameter of method is empty array or empty string? Something like #NotEmpty in Java. I already use _Nonnull and check parameter with NSParameterAssert but I am curious does we can write custom annotation?
Thanks.
You can use macros to define an inline function.
#define isNil(x) nil ==x
Objective-C has no customizable annotations of this sort, but one of its major strengths is the versatility of its runtime.
And so, if we really wanted to, we could implement this with a wrapper class type:
#interface NotEmpty<Object> : NSProxy
#property(readonly,copy) Object object;
+ (instancetype)notEmpty:(Object)object;
- (instancetype)initWithObject:(Object)object;
#end
#implementation NotEmpty {
id _object;
}
- (id)object {
return _object;
}
+ (instancetype)notEmpty:(id)object {
return [[self alloc] initWithObject:object];
}
- (instancetype)initWithObject:(id)object {
if ([object respondsToSelector:#selector(length)]) {
NSParameterAssert([object length] != 0);
} else {
NSParameterAssert([object count] != 0);
}
_object = [object copy];
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
if (selector == #selector(object)) {
return [NSMethodSignature signatureWithObjCTypes:"#:"];
} else {
return [_object methodSignatureForSelector:selector];
}
}
- (void)forwardInvocation:(NSInvocation *)invocation {
invocation.target = _object;
[invocation invoke];
}
#end
#interface SomeClass : NSObject
#end
#implementation SomeClass
- (void)method:(NotEmpty<NSString*> *)nonEmptyString {
// Call NSString methods, option 1
unsigned long length1 = [(id)nonEmptyString length];
// Call NSString methods, option 2
unsigned long length2 = nonEmptyString.object.length;
// Note that just printing `nonEmptyString` (not nonEmptyString.object)
// will print an opaque value. If this is a concern, then also forward
// #selector(description) from -methodSignatureForSelector:.
NSLog(#"Received: %# (length1: %lu length2: %lu)", nonEmptyString.object, length1, length2);
}
#end
int main() {
SomeClass *sc = [SomeClass new];
[sc method:[NotEmpty notEmpty:#"Not an empty string"]];
// [sc method:[NotEmpty notEmpty:#""]]; // Raises error
}
Do note that this will cause some small performance penalties.

The fast way to compere Array with different objects type

I have a one array with fetch results from coredata and it's contain object myObject with property myObjId.
And i have MKAnnotationon my MapView, so I have an array with custom annotation with Id:
self.mapView.annotations return object CustomPinAnnotation with myId property.
What i want to do is sync this two results without reloading existing pins on the map. How to find data in annotations array which is NOT in the results array from CoreData?
I wrote that method to check right before annotation will be added:
-(BOOL)isAlreadyDisplayOnMap:(NSNumber*)pinId {
for(CustomPinAnnotation *an in self.mapView.annotations) {
if(![an isKindOfClass:[MKUserLocation class]]) {
if([an.favourId integerValue] == [pinId integerValue]) {
return YES;
}
}
}
return NO;
}
But this is terribly slow and should be a better way to compare arrays.
You may want to try using block:
__block BOOL alreadyDisplayed;
[self.mapView.annotations enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
CustomPinAnnotation *an = (CustomPinAnnotation *)obj;
if(![an isKindOfClass:[MKUserLocation class]]) {
if([an.favourId integerValue] == [pinId integerValue]) {
alreadyDisplayed = YES;
*stop = YES;
}
}
}];

Core data - Check for null - iOS

On entering a new data into my core data for my given entity, how do I check if the entry for a particular attribute is null?
I have three attribute
name, mail and mailedCustomer.
I add data as follows:
SalesPerson *data = (SalesPerson *)[NSEntityDescription insertNewObjectForEntityForName:#"SalesPerson" inManagedObjectContext:self.managedObjectContext];
[data setName:name];
[data setEmail:userEmail];
NSLog(#" mailed personel%#",data.mailedCustomer);
if([data.mailedCustomer != nil){
NSLog(#"inside condition");
[data setMailedCustomer:#"//"];
}
This doesn't work for me. Im trying to append some strings. So when I enter for the first time I need that attribute to be #"//" then append on further calls.
NSLog(#" mailed personnel %#",data.mailedCustomer);
The above NSLog gives me:
mailed personnel (null)
If I get what you want, your if statement is incorrect. You're now checking if it's NOT nil (meaning it has some value), and then you're resetting it to //. If you want it to be // and then append values, you have to check if it IS nil and then set it to //:
if (!data.mailedCustomer) {
NSLog(#"inside condition");
[data setMailedCustomer:#"//"];
}
#interface NSObject (PE)
- (BOOL) isNull:(NSObject*) object;
- (BOOL) isNotNull:(NSObject*) object;
#end
#implementation NSObject (PE)
- (BOOL) isNull:(NSObject*) object {
if (!object)
return YES;
else if (object == [NSNull null])
return YES;
else if ([object isKindOfClass: [NSString class]]) {
return ([((NSString*)object) isEqualToString:#""]
|| [((NSString*)object) isEqualToString:#"null"]
|| [((NSString*)object) isEqualToString:#"<null>"]);
}
return NO;
}
- (BOOL) isNotNull:(NSObject*) object {
return ![self isNull:object];
}
#end
if([self isNotNull:some object])
{
not null
}
else
{
null
}

Encode All Properties of NSObject to NSData

So I'm new to iOS, but I'm a bit baffled by the complexity of a simple task. I'm trying to store my custom NSObject class called 'Vehicle' in NSUserDefaults. Obviously, this can't be done, so I'll need to encode it to NSDATA first. Fine.
But that means that I need to encode each property of the class as well in the decode...
Inside my Vehicle class...
- (void) encodeWithCoder: (NSCoder *) coder
{
[coder encodeInt: x forKey: #"x"];
[coder encodeInt: y forKey: #"y"];
[coder encodeInt: direction forKey: #"direction"];
} // encodeWithCoder
- (id) initWithCoder: (NSCoder *) coder
{
if (self = [super init]) {
x = [coder decodeIntForKey: #"x"];
y = [coder decodeIntForKey: #"y"];
direction = [coder decodeIntForKey: #"direction"];
}
return (self);
} // initWithCoder
If I end up adding a new property to the vehicle class, I've got to add the encode and decode logic too. This is the same for creating a copy of a class using CopyWithZone. This leaves 3 or 4 areas where adding a new property to a class can go wrong.
I currently program mostly in LabVIEW, and we have the ability to take a class, and feed it to an encoder, which will do all the versioning and property manipulation automatically.
So I guess my question is:
Is this not heard of in iOS?
If it's not possible, is there a way to enumerate through all properties in a class and write a function to do this automatically.
You can use the objective-c runtime to find all the properties of an object and decode them, but I wouldn't recommend it. If you'd ilke, I can create a simple example for you.
EDIT: Here's an example:
#import <objc/runtime.h>
void decodePropertiesOfObjectFromCoder(id obj, NSCoder *coder)
{
// copy the property list
unsigned propertyCount;
objc_property_t *properties = class_copyPropertyList([obj class], &propertyCount);
for (int i = 0; i < propertyCount; i++) {
objc_property_t property = properties[i];
char *readonly = property_copyAttributeValue(property, "R");
if (readonly)
{
free(readonly);
continue;
}
NSString *propName = [NSString stringWithUTF8String:property_getName(property)];
#try
{
[obj setValue:[coder decodeObjectForKey:propName] forKey:propName];
}
#catch (NSException *exception) {
if (![exception.name isEqualToString:#"NSUnknownKeyException"])
{
#throw exception;
}
NSLog(#"Couldn't decode value for key %#.", propName);
}
}
free(properties);
}
void encodePropertiesOfObjectToCoder(id obj, NSCoder *coder)
{
// copy the property list
unsigned propertyCount;
objc_property_t *properties = class_copyPropertyList([obj class], &propertyCount);
for (int i = 0; i < propertyCount; i++) {
objc_property_t property = properties[i];
char *readonly = property_copyAttributeValue(property, "R");
if (readonly)
{
free(readonly);
continue;
}
NSString *propName = [NSString stringWithUTF8String:property_getName(property)];
#try {
[coder encodeObject:[obj valueForKey:propName] forKey:propName];
}
#catch (NSException *exception) {
if (![exception.name isEqualToString:#"NSUnknownKeyException"])
{
#throw exception;
}
NSLog(#"Couldn't encode value for key %#.", propName);
}
}
free(properties);
}
__attribute__((constructor))
static void setDefaultNSCodingHandler()
{
class_addMethod([NSObject class], #selector(encodeWithCoder:), imp_implementationWithBlock((__bridge void *)[^(id self, NSCoder *coder) {
encodePropertiesOfObjectToCoder(self, coder);
} copy]), "v#:#");
class_addMethod([NSObject class], #selector(initWithCoder:), imp_implementationWithBlock((__bridge void *)[^(id self, NSCoder *coder) {
if ((self = [NSObject instanceMethodForSelector:#selector(init)](self, #selector(init))))
{
decodePropertiesOfObjectFromCoder(self, coder);
}
return self;
} copy]), "v#:#");
}
This allows you to encode any object that exposes enough properties to reconstruct itself.

Case Insensitive Search in NSMutableDictionary

HI, I have a NSMutableDicitionary contains both lowercase and uppercase keys. So currently i don't know how to find the key in the dictionary irrespective key using objective c.
Categories to the rescue.
Ok, so it's an old post...
#interface NSDictionary (caseINsensitive)
-(id) objectForCaseInsensitiveKey:(id)aKey;
#end
#interface NSMutableDictionary (caseINsensitive)
-(void) setObject:(id) obj forCaseInsensitiveKey:(id)aKey ;
#end
#implementation NSDictionary (caseINsensitive)
-(id) objectForCaseInsensitiveKey:(id)aKey {
for (NSString *key in self.allKeys) {
if ([key compare:aKey options:NSCaseInsensitiveSearch] == NSOrderedSame) {
return [self objectForKey:key];
}
}
return nil;
}
#end
#implementation NSMutableDictionary (caseINsensitive)
-(void) setObject:(id) obj forCaseInsensitiveKey:(id)aKey {
for (NSString *key in self.allKeys) {
if ([key compare:aKey options:NSCaseInsensitiveSearch] == NSOrderedSame) {
[self setObject:obj forKey:key];
return;
}
}
[self setObject:obj forKey:aKey];
}
#end
enjoy.
Do you have control over the creation of the keys? If you do, I'd just force the keys to either lower or upper case when you're creating them. This way when you need to look up something, you don't have to worry about mixed case keys.
You can do this to get the object as an alternative to subclassing.
__block id object;
[dictionary enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent
UsingBlock:^(id key, id obj, BOOL *stop){
if ( [key isKindOfClass:[NSString class]] ) {
if ( [(NSString*)key caseInsensitiveCompare:aString] == NSOrderedSame ) {
object = obj; // retain if wish to.
*stop = YES;
}
}
}];
You can use a #define shorthand if you find yourself doing this a lot in your code.
Don't think there's any easy way. Your best option might be to create a subclass of NSMutableDictionary, and override the objectForKey and setObject:ForKey methoods. Then in your overridden methods ensure that all keys are converted to lowercase (or uppercase), before passing them up to the superclass methdods.
Something along the lines of the following should work:
#Interface CaseInsensitveMutableDictionary : MutableDictionary {}
#end
#implementation CaseInsensitveMutableDictionary
- (void) setObject: (id) anObject forKey: (id) aKey {
[super setObject:anObject forKey:[skey lowercaseString]];
}
- (id) objectForKey: (id) aKey {
return [super objectForKey: [aKey lowercaseString]];
}
#end

Resources