i would like to add extra data to EKEvent, i tried NSDictionary (there is a lot of data to add) but it doesn't work..
sample code:
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc]init];
[eventStore setValue:dictionary forKey:MAIN_DICTIONARY];
any ideas?
You're using setValue:forKey: in a wrong way. That a look here. There are different options to achieve what you want: category, subclassing or create a class that contains the EKEvent and the NSMutableDictionary. It depends on how you need to use the EKEvent.
You cannot do it this way, because even with key-value coding you can only set (declared or non declared) properties known by the instance. Basically the accessors (setter, getter) are executed. But there is no property MAIN_THREAD,no setter setMAIN_THREAD: in EKEvent.
If you want to extend instances of a foreign class that are created by the system (the instances, not the class), there are to ways to add data:
You create an own class, let's say MyEvent and give them a reference to the system instance (EKEvent) as a property plus the properties you need. When you get an instance of EKEvent you look-up your list of MyEventss using the identifier. With that you have the full access to your data.
You use associated objects. But you have to take care that they are not handled by the instance, i. e. while copying.
The first solution is better by far. Simple sample code:
#interface MyEvent : NSObject
#property (readonly) EKEvent* systemEvent;
#property id customProperty;
- (instancetype)eventForSystemEvent:(EKEvent*)systemEvent;
#end
#implemenation MyEvent
// Look-Up
NSMutableDictionary *eventLookUp;
+ (void)initialize
{
if( self == [MyEvent class])
{
eventLookUp = [NSMutableDictionary new];
}
}
- (instancetype)eventForSystemEvent:(EKEvent*)systemEvent
{
return eventLookUp[systemEvent.calendarItemIdentifier];
}
// Instance creation
- (instancetype)initWithSystemEvent:(EKEvent*)systemEvent
{
// Usual initializer
…
eventLookUp[systemEvent.calendarItemIdentifier] = systemEvent;
return self;
}
+ (instancetype)newEventWithSystemEvent:(EKEvent*)systemEvent
{
return [[self alloc] initWithSystemEvent:systemEvent];
}
#end
Typped in Safari
Related
I have many "model" objects whose properties are defined as "readonly" and shared among various components.
In some cases I need to create local mutable copies of the objects (using them for local mutable state)
I rather not implement NSMutableCopy protocol as the object should be immutable after it is created. The modified object could be "passed" around after copy+mutate operations.
Is there a suggested mechanism , or should I just implement a constructor receiving the "changed" parameters?
For example an object which parses a JSON to native types :
#interface ImmutableObject : NSObject
// various "readonly" properties
...
-(instancetype)initWithJSON:(NSDictionary *)jsonDictionary;
#property (nonatomic, readonly) MyClass1 *prop1;
#property (nonatomic, readonly) MyClass2 *prop2;
...
#property (nonatomic, readonly) NSArray<MyClass100 *> *prop100;
#end
#implementation
-(instancetype)initWithJSON:(NSDictionary *)jsonDictionary {
self = [super init];
[self parseDictionaryToNative:jsonDictionary];
return self;
}
#end
Somewhere in code:
ImmutableObject *mutated = [immutableObject mutableCopy]; // best way to accomplish this?
// change some values...
mutated.prop1 = ... // change the value to something new
self.state = [mutated copy]; // save the new object
#spinalwrap is correct, but in this case there is no reason to create the extra copy before storing it. NSMutableArray is a subclass of NSArray, so can be used anywhere an NSArray can be used (and this is very common). Same for yours. In your particular case, you'd probably do it this way:
MutableObject *mutated = [immutableObject mutableCopy]; // create an instance of MutableObject
mutated.prop1 = ... // change the value to something new
self.state = mutated; // Since `state` is an immutable type,
// attempts to mutate this later will be compiler errors
This is safe because you know that this block of code is the only block that has a reference to the mutable version of the object (because you created it here).
That said, once you've created a mutable subclass, you now need to consider the possibility that any ImmutableObject you are passed might actually be a MutableObject, and so make defensive copies (just as is done with NSArray, NSString, etc.) For example:
- (void)cacheObject:(ImmutableObject *)object {
// Need to copy here because object might really be a MutableObject
[self.cache addObject:[object copy]];
}
This is made fairly efficient by implementing copy on ImmutableObject and return self, and implementing copy on MutableObject as an actual copy, usually like this:
ImmutableObject.m
- (ImmutableObject *)copy {
return self;
}
MutableObject.m
// as in spinalwrap's example
- (MutableObject *)mutableCopy {
MutableObject *instance = [MutableObject new];
instance.prop1 = [self.prop1 copy]; // depends what you want here and what kind of class the properties are... do you need a deep copy? that might be a bit more work.
// etc...
return instance;
}
// No need to duplicate code here. Just declare it immutable;
// no one else has a pointer to it
- (ImmutableObject *)copy {
return (ImmutableObject *)[self mutableCopy];
}
So the copy is almost free if the object was immutable already. I say "fairly efficient" because it still causes some unnecessary copies of mutable objects that are never mutated. Swift's copy-on-write system for value types was specifically created to deal with this problem in ObjC. But the above is the common pattern in ObjC.
note that NSMutableArray, NSMutableData etc. are different classes than their immutable counterparts. So in this case, you maybe should define a MutableObject class with the same interface as the ImmutableObject class (but with mutable properties) and use that if you want to have a mutable object.
MutableObject *mutated = [immutableObject mutableCopy]; // create an instance of MutableObject
mutated.prop1 = ... // change the value to something new
self.state = [mutated copy]; // creates an ImmutableObject
the implementation of ImmutableObject's mutableCopy could be something like:
- (MutableObject *) mutableCopy
{
MutableObject *instance = [MutableObject new];
instance.prop1 = [self.prop1 copy]; // depends what you want here and what kind of class the properties are... do you need a deep copy? that might be a bit more work.
// etc...
return instance;
}
and MutableObject's copy method could look like this:
- (ImmutableObject *) copy
{
ImmutableObject *instance = [ImmutableObject new];
instance.prop1 = [self.prop1 copy];
// etc...
return instance;
}
You're not forced to use the NSMutableCopy protocol formally, but you can.
Say I create a RLMObject that has a relationship, which I proceed to save in my realm database. After that is complete, I decide to call initWithValues on the newly created object and return this copied object to the front end for use. I noticed that the object it has a relationship with is still considered instantiated.
Is there a way to make it such that when I call initWithValues to create an uninstantiated copy of my object, to ensure all my properties are uninstantiated as well?
There is no builtin way to achieve that. You would need to create a standalone copy of the object yourself. Relationships could be cyclic, so that a generic solution for that would be non-trivial.
If I understand you correctly, for example, you can implement the NSCopying protocol in each managed class:
- (instancetype)copyWithZone:(NSZone *)zone {
//SomeClass *object = (SomeClass *)[super.class allocWithZone:zone];
SomeClass *object = [SomeClass new];
object->_isClone = YES;
object->_name = self.name.copy;
object->_age = self.age;
return object;
}
Then, after removing the original, the copy remains available.
Can also be implement NSCopying protocol in category for RLMResults:
- (instancetype)copyWithZone:(NSZone *)zone {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:self.count];
for (RLMObject *object in self) {
[array addObject:object.copy];
}
return array.copy;
}
Use case:
RLMResults *objects = [SomeObject objectsWhere:#"ANY nested.age < 99"];
NSArray<SomeObject*> *clones = objects.copy;
The NSOrderedSet Class Reference Overview says:
You can use ordered sets as an alternative to arrays when the order of elements is important and performance in testing whether an object is contained in the set is a consideration— testing for membership of an array is slower than testing for membership of a set.
Which methods are considered "testing for membership"? Just containsObject:? Or, will indexOfObjectPassingTest: also be faster?
I'm asking because if I just have the object's ID (from the server for example) and want to check if the ordered set contains an object with that ID, I'd use indexOfObjectPassingTest:. But, that method, since it tests every object in the collection, seems that it'd be just as slow as it is for an array. On the other hand, containsObject: seems that it'd be faster since it takes advantage of the NSObject methods hash & isEqual:. I could just create a probe object with the ID I have, and then use containsObject:. But then, if the ordered set already contains an object with that ID, I'll just discard of the probe object and update the properties on the object already in the ordered set. It seems like extra work to have to create a probe object first. In that case, is it even worth using an ordered set over an array?
Also, I would be sorting objects by their date, not their ID.
I'd use an NSMutableDictionary with object IDs mapping to objects, as St3fan suggested, but I also want to display the objects in a UITableView.
The best way to test this is by writing some small benchmarks. I don't know how many objects you are dealing with but if it is less then a few hundred then you probably won't notice much difference between containsObject:, indexOfObjectPassingText: or even just iterating over all objects manually.
It sounds like an NSMutableDictionary is more appropriate for your use case actually. Why don't you store your objects in a dictionary that is indexed by your object's ID? Then you can really quickly find them by ID and you can also iterate easily over them if needed.
You can override -isEqual: and -hash in you class. If you do, it'll work with NSOrderedSet's fast lookup. It can be as simple as:
- (BOOL)isEqual:(id)otherObject
{
return self.myID == otherObject.myID;
}
- (NSUInteger)hash
{
return self.myID;
}
Here's a full example:
#import <XCTest/XCTest.h>
#interface MyClass : NSObject
#property (nonatomic) NSInteger myID;
#property (nonatomic, strong) NSDate *date;
#end
#implementation MyClass
- (BOOL)isEqual:(MyClass*)otherObject
{
return self.myID == otherObject.myID;
}
- (NSUInteger)hash
{
return self.myID;
}
#end
#interface MyTests : XCTestCase
#end
#implementation MyTests
- (void)testExample
{
MyClass *obj1 = [[MyClass alloc] init];
obj1.myID = 1;
obj1.date = [NSDate dateWithTimeIntervalSince1970:20000];
MyClass *obj2 = [[MyClass alloc] init];
obj2.myID = 2;
obj2.date = [NSDate dateWithTimeIntervalSince1970:10000];
MyClass *obj3 = [[MyClass alloc] init];
obj3.myID = 1;
obj3.date = [NSDate dateWithTimeIntervalSince1970:30000];
MyClass *obj4 = [[MyClass alloc] init];
obj4.myID = 3;
obj4.date = [NSDate dateWithTimeIntervalSince1970:30000];
NSOrderedSet *set = [[NSOrderedSet alloc] initWithArray:#[obj1, obj2]];
XCTAssertEqualObjects(((MyClass *)[set firstObject]).date, obj1.date);
XCTAssertEqualObjects(((MyClass *)[set lastObject]).date, obj2.date);
XCTAssertTrue([set containsObject:obj1]);
XCTAssertTrue([set containsObject:obj3]);
XCTAssertFalse([set containsObject:obj4]);
}
#end
Could someone share some knowledge on whats best practice / code convention on using #property iVars in init methods or designated initializers?
please see my example:
#interface MyClass ()
#property(nonatomic,strong) nsstring *tempString;
#property(nonatomic,strong) NSMutableArray *arrItems;
#end
#implementation ViewController
- (id)init
{
if (self = [super init]) {
//Is this best practice / correct
_tempString = #"";
_arrItems = [[NSMutableArray alloc] initWithCapacity:0];
...
...
//Or this
self.tempString = #"";
self.arrItems = [[NSMutableArray alloc] initWithCapacity:0];
}
return self;
}
#end
Any advice on why one or the other should be used?
Thanks...
Apple's guidance on this topic is included in the aptly named section Don’t Use Accessor Methods in Initializer Methods and dealloc.
Read this thread: Why shouldn't I use Objective C 2.0 accessors in init/dealloc?
In other words if you are not goiung to use KVO you can use second approach:
//Or this
self.tempString = #"";
self.arrItems = [[NSMutableArray alloc] initWithCapacity:0];
But be care full with alloc-init, don't forget about autorelease.
It's typically better to use property notation when you define it, partly(mostly?) for the reason Jeremy mentioned.
Debugging a particular variable is a whole lot easier when you can set a breakpoint in method setter override and have it apply to ALL code paths that modify the variable.
Another reason is to keep a consistent memory management model, although it is less important since you are using ARC. If you weren't however, and strong was retain, then you would make sure that the object you are setting to the property is autoreleased everywhere you set the property, and not have to deal with releasing the current value if you are directly setting the variable.
Consistency is important for maintenance/readability and debugging, no matter what practices you use.
I prefer the lazy instantiation method for properties.
After you #synthesize you can override your getter to lazily instantiate your property
For Example:
-(NSString *)tempString {
if(!tempString) {
_tempString = #"";
}
return _tempString;
}
and
-(NSMutableArray *)arrItems {
if(!_arrItems) {
_arrItems = [[NSMutableArray alloc] initWithCapacity:0];
}
return _arrItems;
}
If you do want to set your property in the init method, use dot notation self.myProperty so that it uses the defined setter for the property and not the private class method directly.
According to Apple, you should not use accessors in init... or dealloc methods:
You should always access the instance variables directly from within
an initialization method because at the time a property is set, the
rest of the object may not yet be completely initialized. Even if you
don’t provide custom accessor methods or know of any side effects from
within your own class, a future subclass may very well override the
behavior.
Taken from this doc: Encapsulating Data.
My app crashed after I called addImageToQueue. I added initWithObjects: forKeys: count: but it doesn't helped me.
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '*** -[NSDictionary initWithObjects:forKeys:count:]:
method only defined for abstract class.
Define -[DictionaryWithTag initWithObjects:forKeys:count:]!'
my code
- (void)addImageToQueue:(NSDictionary *)dict
{
DictionaryWithTag *dictTag = [DictionaryWithTag dictionaryWithDictionary:dict];
}
#interface DictionaryWithTag : NSDictionary
#property (nonatomic, assign) int tag;
- (id)initWithObjects:(id *)objects forKeys:(id *)keys count:(NSUInteger)count;
#end
#implementation DictionaryWithTag
#synthesize tag;
- (id)initWithObjects:(id *)objects forKeys:(id *)keys count:(NSUInteger)count
{
return [super initWithObjects:objects forKeys:keys count:count];
}
#end
Are you subclassing NSDictionary? That's not a common thing to do in Cocoa-land, which might explain why you're not seeing the results you expect.
NSDictionary is a class cluster. That means that you never actually work with an instance of NSDictionary, but rather with one of its private subclasses. See Apple's description of a class cluster here. From that doc:
You create and interact with instances of the cluster just as you would any other class. Behind the scenes, though, when you create an instance of the public class, the class returns an object of the appropriate subclass based on the creation method that you invoke. (You don’t, and can’t, choose the actual class of the instance.)
What your error message is telling you is that if you want to subclass NSDictionary, you have to implement your own backend storage for it (for example by writing a hash table in C). It's not just asking you to declare that method, it's asking you to write it from scratch, handling the storage yourself. That's because subclassing a class cluster directly like that is the same as saying you want to provide a new implementation for how dictionaries work. As I'm sure you can tell, that's a significant task.
Assuming you definitely want to subclass NSDictionary, your best bet is to write your subclass to contain a normal NSMutableDictionary as a property, and use that to handle your storage. This tutorial shows you one way to do that. That's not actually that hard, you just need to pass the required methods through to your dictionary property.
You could also try using associative references, which "simulate the addition of object instance variables to an existing class". That way you could associate an NSNumber with your existing dictionary to represent the tag, and no subclassing is needed.
Of course, you could also just have tag as a key in the dictionary, and store the value inside it like any other dictionary key.
From https://stackoverflow.com/a/1191351/467588, this is what I did to make a subclass of NSDictionary works. I just declare an NSDictionary as an instance variable of my class and add some more required methods. It's called "Composite Object" - thanks #mahboudz.
#interface MyCustomNSDictionary : NSDictionary {
NSDictionary *_dict;
}
#end
#implementation MyCustomNSDictionary
- (id)initWithObjects:(const id [])objects forKeys:(const id [])keys count:(NSUInteger)cnt {
_dict = [NSDictionary dictionaryWithObjects:objects forKeys:keys count:cnt];
return self;
}
- (NSUInteger)count {
return [_dict count];
}
- (id)objectForKey:(id)aKey {
return [_dict objectForKey:aKey];
}
- (NSEnumerator *)keyEnumerator {
return [_dict keyEnumerator];
}
#end
I just did a little trick.
I'm not sure that its the best solution (or even it is good to do it).
#interface MyDictionary : NSDictionary
#end
#implementation MyDictionary
+ (id) allocMyDictionary
{
return [[self alloc] init];
}
- (id) init
{
self = (MyDictionary *)[[NSDictionary alloc] init];
return self;
}
#end
This worked fine for me.