Inherit from NSNotification - ios

I want to create a subclass of NSNotification. I don't want to create a category or anything else.
As you may know NSNotification is a Class Cluster, like NSArray or NSString.
I'm aware that a subclass of a cluster class needs to:
Declare its own storage
Override all initializer methods of the superclass
Override the superclass’s primitive methods (described below)
This is my subclass (nothing fancy):
#interface MYNotification : NSNotification
#end
#implementation MYNotification
- (NSString *)name { return nil; }
- (id)object { return nil; }
- (NSDictionary *)userInfo { return nil; }
- (instancetype)initWithName:(NSString *)name object:(id)object userInfo:(NSDictionary *)userInfo
{
return self = [super initWithName:name object:object userInfo:userInfo];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
return self = [super initWithCoder:aDecoder];
}
#end
When I use it, I get an extraordinary:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** initialization method -initWithName:object:userInfo: cannot be sent to an abstract object of class MYNotification: Create a concrete instance!'
What else do I have to do in order to inherit from NSNotification?

The problem was that was trying to call the superclass initialiser. You can't do that because is an abstract class. So, in the initializer you just have to init your storage.
Because this is horrible, I end up creating a category for NSNotification instead. There I added three kind methods:
Static constructor for my custom notification: Here I configure userInfo to be used as the storage.
Method to add information to the storage: The notification observer will call this to update userInfo.
Method to process the information submitted by the observes: After the post method has finished, the notification has collected all the information needed. We just have to process it and return it. This is optional if you are not interested in collecting data.
In the end, the category it's just a helper to deal with userInfo.
Thanks #Paulw11 for your comment!

Related

Objective C: save array of items (NSCoder)

I am working on challenges from iOS Big Nerd Ranch book of 12th chapter and there is a problem of saving an array of items to the disk. I have BNRDrawView UIView that has an array finishedLines that holds items BNRLine defined by me. I want to use NSCoder for the purpose. So, I implemented inside BNRDrawView two methods:
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:_finishedLines forKey:finishedLinesKey];
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
_finishedLines = [decoder decodeObjectForKey:finishedLinesKey];
}
return self;
}
and trying to save finishedLines array whenever it is changed like this:
[NSKeyedArchiver archiveRootObject:self.finishedLines
toFile:self.finishedLinesPath];
Loading I am trying to do inside initWithFrame BNRDrawView method:
- (instancetype)initWithFrame:(CGRect)r
{
...
self.finishedLinesPath = #"/Users/nikitavlasenko/Desktop/XCodeProjects/MyFirstApp/TouchTracker/savedLines/finishedLines";
BOOL fileExists = [[NSFileManager defaultManager]
fileExistsAtPath:self.finishedLinesPath];
if (fileExists) {
self.finishedLines = [NSKeyedUnarchiver
unarchiveObjectWithFile:self.finishedLinesPath];
}
...
It gives me the error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BNRLine encodeWithCoder:]: unrecognized selector sent to instance 0x7fbd32510c40'
From here I have two questions:
Do I need to implement encodeWithCoder: and initWithCoder: for ALL of the classes - let's suppose that I have a plenty of different ones, defined by me - that are inside my array that I am trying to save?
If you look above to my initWithCoder: method, you can see that I need to call [super initWithCoder:decoder], but do I also need to call BNRDrawView's initializer method initWithFrame: there?
Yes, you need to implement NSCoder in all objects that are stored inside the array.
No, you don't need to call initWithFrame - initWithCoder is sufficient.

Instance not storing value

I have three classes involved in this problem:
my appDelegate
my data class
my my viewcontroller class
when my app launches I use the didFinishLaunchingWithOptions method in my app delegate to create an instance of the data class and then call the queryMessagesFromBackend method to fill my messages array.
Then in my view controller I create another instance of my data class to access the messages array (dataClass.messages) that i just filled, however its empty. This does not make sense because when the method is called it Logs all the data the messages array has. Why is my new instance showing that the messages array is empty?
I would provide code but that seems useless
In yours "app delegate" make first "data class" object an ivar (or property) of "app delegate"
Declare some getter for that data object in app delegate class.
In your's view controller - get app delegate via shared NSApp instance.
Get shared data object from app delegate with introduced getter.
Tip: U can use an interface (protocol) for making things little cleaner:
#protocol XYZDataProvider <NSObject>
- (XYZDataClass *) data;
#end
...
#interface XYZAppDelegate : NSAppDelegate <XYZDataProvider> {
XYZDataClass *data;
}
#end
...
#implementation XYZAppDelegate
// either can init data in didFinishBlahBlahBlah...
- (void) did ... {
self->data = [XYZDataClass new];
...
}
// implement protocol's required method
- (XYZDataClass *) data {
/// and then return on request...
return self->data;
// or do so-caled lazy-loading:
// if (!self->data) {
// $self->data = [... ..];
// ...
// }
// return self->data;
}
#end
...
#implementation XYZViewController
- (void) processData {
NSAppDelegate *appDelegate = [NSApp delegate];
if ([appDelegate conformsToProtocol:#protocol(XYZDataProvider)]) {
XYZDataClass *data = [(id<XYZDataProvider>)appDelegate data];
// do smth with data
...
}
...
}
#end
Note: code contains errors (written directly in browser...)

Trying to implement Key-Value Observing for first time, get some error

I have trouble implementing a Key-Value Observer at my attempt to follow the MVC pattern. I have a controller class, a model class and a view class. I update my model from the controller class and I want to put a key value observer in my view class to monitor when a NSMutableArray changes in model (like through addObject) and then redraw itself automatically. I used answer in this thread to guide me: How to add observer on NSMutableArray?
Code so far:
From my Scene (using sprite kit if it matters). Setting of letters will be done from Ctrl class, this is just to test.
BarCtrl *barCtrl = [[BarCtrl alloc] init];
BarModel *barModel = [[BarModel alloc] init];
BarView *barView = [[BarView alloc] init];
barCtrl.barModel = barModel;
barCtrl.barView = barView;
barView.barModel = barModel;
ScrabbleDeck *sd = [[ScrabbleDeck alloc] init];
if([barModel addLetter:[sd getLetter] onSide:BarModelSideRight])
NSLog(#"Added letter");
BarModel.h
#import <Foundation/Foundation.h>
#import "Letter.h"
typedef NS_ENUM(int, BarModelSide) {
BarModelSideLeft,
BarModelSideRight
};
#interface BarModel : NSObject
#property (nonatomic, strong) NSMutableArray *addedLetters;
- (instancetype)init;
- (BOOL) addLetter: (Letter*) letter onSide: (BarModelSide) side;
#end
BarModel.m
#import "BarModel.h"
#interface BarModel ()
#property (nonatomic) int capacity;
#end
#implementation BarModel
- (instancetype)init
{
self = [super init];
if (self) {
self.capacity = letterCapacity;
_addedLetters = [[NSMutableArray alloc] init];
}
return self;
}
// We'll use automatic notifications for this example
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:#"arrayLetter"]) {
return YES;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (BOOL) addLetter: (Letter*) letter onSide: (BarModelSide) side{
if([_addedLetters count] > _capacity){
return FALSE;
}
switch (side) {
case BarModelSideLeft:
[_addedLetters insertObject:letter atIndex:0];
return TRUE;
break;
case BarModelSideRight:
[_addedLetters addObject:letter];
return TRUE;
break;
default:
return FALSE;
break;
}
}
// These methods enable KVC compliance
- (void)insertObject:(id)object inDataAtIndex:(NSUInteger)index
{
self.addedLetters[index] = object;
}
- (void)removeObjectFromDataAtIndex:(NSUInteger)index
{
[self.addedLetters removeObjectAtIndex:index];
}
- (id)objectInDataAtIndex:(NSUInteger)index
{
return self.addedLetters[index];
}
- (NSArray *)dataAtIndexes:(NSIndexSet *)indexes
{
return [self.addedLetters objectsAtIndexes:indexes];
}
- (NSUInteger)countOfData
{
return [self.addedLetters count];
}
#end
BarView.h
#import <SpriteKit/SpriteKit.h>
#import "BarModel.h"
#interface BarView : SKSpriteNode
#property (nonatomic, strong) BarModel *barModel;
#end
BarView.m
#import "BarView.h"
#implementation BarView
static char MyObservationContext;
- (instancetype)init
{
self = [super init];
if (self) {
//_barModel = [[BarModel alloc] init];
}
return self;
}
-(void)setBarModel:(BarModel *)barModel{
if(_barModel != barModel)
_barModel = barModel;
[_barModel addObserver:self
forKeyPath:#"arrayLetter"
options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew)
context:&MyObservationContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// Check if our class, rather than superclass or someone else, added as observer
if (context == &MyObservationContext) {
// Check that the key path is what we want
if ([keyPath isEqualToString:#"arrayLetter"]) {
// Verify we're observing the correct object
if (object == self.barModel) {
[self draw:change];
}
}
}
else {
// Otherwise, call up to superclass implementation
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void) draw: (NSDictionary*) change{
NSLog(#"KVO for our container property, change dictionary is %#", change);
}
#end
When I ru this I get this "error":
2014-08-31 00:23:02.828 Testing[329:60b] Added letter
2014-08-31 00:23:02.830 Testing[329:60b] An instance 0x17803d340 of class BarModel was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
<NSKeyValueObservationInfo 0x17804eb50> (
<NSKeyValueObservance 0x1780cf180: Observer: 0x178111670, Key path: arrayLetter, Options: <New: YES, Old: YES, Prior: NO> Context: 0x100101428, Property: 0x17804eb80>
I tried to follow the instructions in error but can not find where to set break point. Please help me figure this out!
The error is pretty descriptive. You add self as an observer of a BarModel object. At some point that object gets deallocated. But you never remove self as an observer by calling removeObserver:forKeyPath:context:. You need to do that.
First, in setBarModel, make sure to remove self as an observer of the previous value of _barModel.
Next, you probably need to add a dealloc method that does the same thing.
There are multiple problems with the code. In addition to what Tom Harrington said with respect to the specific error that was logged about failing to remove the observation:
You implemented the indexed collection accessors for a (non-existent) property named "data". That is, they have "Data" in their name where the property name should be.
The indexed collection property is addedLetters. So, the indexed collection mutating accessors should be:
- (void)insertObject:(id)object inAddedLettersAtIndex:(NSUInteger)index;
- (void)removeObjectFromAddedLettersAtIndex:(NSUInteger)index;
You don't really need the non-mutating accessors, since you have an array-type public property with a normal getter (i.e. -addedLetters).
By the way, that property is of type NSMutableArray which it should not be. The property should be of type NSArray, backed by an instance variable of type NSMutableArray. That is, the mutability of the type (as opposed to the property) should not be exposed through the public interface. When you do this, you have to manually declare the instance variable (since it should differ from the type of the property and auto-synthesis will get it wrong), make the property copy instead of strong, and implement the setter yourself to do a mutable copy of the passed-in immutable array:
- (void) setAddedLetters:(NSArray*)addedLetters
{
if (addedLetters != _addedLetters)
_addedLetters = [addedLetters mutableCopy];
}
Once you have implemented the indexed collection mutating accessors with the correct names, you must use only those methods to mutate the collection (after initialization). In particular, your -addLetter:onSide: method must not directly operate on the _addedLetters instance variable. This is the part that makes the class KVO-compliant for that property. The mere presence of the indexed collection mutating accessors does not help. They must be used for all actual mutations.
Your implementation of +automaticallyNotifiesObserversForKey: is redundant. Automatic notification is the default.
The BarView class is key-value observing a key path "arrayLetter" on its _barModel object, but that's not the name of the property on BarModel. I suppose you meant to use the key path "addedLetters".
Finally, for proper adherence to MVC design, your view should not have a reference to your model. It should have a reference to the controller. The controller can reflect the model to the view (or, in theory, adapt a model of a different internal design to what the view expects). Or, in a more traditional non-KVO approach, the controller would actually tell the view when something has changed and give it the updated data it should show.
So, your controller could expose its own addedLetters property:
#property (readonly, copy, nonatomic) NSArray* addedLetters;
It could be implemented as a derived property, forwarded through to the _barModel object:
+ (NSSet*)keyPathsForValuesAffectingAddedLetters
{
return [NSSet setWithObject:#"barModel.addedLetters"];
}
- (NSArray*)addedLetters
{
return self.barModel.addedLetters;
}
Then, the view would have a reference to the controller and not the model, and it would key-value observe the "addedLetters" key path on the controller.

about parent object's init method

Parent.h (extends NSObject)
like this:
#implementation InTParent
-(id)init
{
NSLog(#"Parent init method");
if (self = [super init]) {
;
}
return self;
}
-(id)initWithName:(NSString*)name;
{
NSLog(#"Parent initWithName method");
if (self = [self init]) {
;
}
return self;
}
Son.h(extends Parent)
like this:
#implementation InTSon
-(id)init
{
NSLog(#"Son init method");
if (self = [super init]) {
;
}
return self;
}
-(id)initWithName:(NSString*)name;
{
NSLog(#"Son initWithName method");
if (self = [super initWithName:name]) {
;
}
return self;
}
I use this: IntSon *_son = [[IntSon alloc] initWithName:#"asd"];
why the output is:
Son initWithName method --> Parent initWithName method --> Son init method --> Parent init method
But in Java,it perhaps like this :
Son initWithName method --> Parent initWithName method --> Parent init method
Please help me!
To understand this behavior, you have to understand how Objective-C message dispatching works. And this is a good example to illustrate it.
At a high level, any time you call a method on any object, the Objective-C runtime looks for the implementation provided the most-derived (deepest in the class hierarchy) class. If it doesn't find it, it will go the next most-derived, and so on all they way up to NSObject. The first time it finds an implementation matching the selector (method name, roughly), it will execute that implementation. When you call super, you are specifying to send a message to the next most-derived class's implementation of that method.
So in your code, you call alloc on the InTSon class, which returns an instance of IntSon with the isa pointer set to the class object InTSon. The isa pointer is how method implementations are looked for during the process of ascending the class hierarchy.
So after you have an InTSon instance, you call initWithName: on it, and it checks the class pointed to by isa (which is InTSon for an implementation of this method. It finds it, so executes it, resulting in your first output:
"Son initWithName method"
Immediately afterward, you call the superclass implementation of that method, which looks in InTParent for its implementation of initWithName: and executes that code, resulting in your second output:
Parent initWithName method
Now here is where you see a deviation from Java - you call init on self. self, however, is a pointer to an InTSon instance. So when the runtime resolves this message, it looks first for the implementation of init in the InTSon class. Of course, it finds it, and executes the code for that method, which gives you your third output, Son init method. Next you call super, which looks up and executes the InTParent implementation of init, and gives you your final output.
To sum up, regardless of where in the class hierarchy a method is called from, if it's called on self, it will always execute the most derived implementation of that method. Hope this helps, and if you have any questions please let me know!
Your class diagram looks like this:
When you send the initWithName: message to your instance of InTSon, the system looks up the method in InTSon's method table, and finds the method that we call -[InTSon initWithName:]. That method, simplified, looks like this:
// -[InTSon initWithName:]
- (id)initWithName:(NSString *)name {
return [super initWithName:name];
}
This method does [super initWithName:name]. Because it sends the message to super, the system looks in the method table of the superclass of self's class. Your object's class is InTSon, and its superclass is InTParent. So when -[InTSon initWithName:] does [super initWithName:name], the system looks for the method in InTParent's method table. It finds the method that we call -[InTParent initWithName:]. That method, simplified, looks like this:
// -[InTParent initWithName:]
- (id)initWithName:(NSString *)name {
return [self init];
}
This method does [self init]. Because it sends the message to self, the system looks in the method table for self's class. Even though we are in -[InTParent initWithName:], self is still an instance of the InTSon class. So the system looks for an init method in InTSon's method table. It finds the method we call -[InTSon init]. That method, simplified, looks like this:
// -[InTSon init]
- (id)init {
return [super initWithName:#"sdas"];
}
This method does [super initWithName:], so the system looks in the superclass's (InTParent's) method table and finds the -[InTParent initWithName:] method. And as we have just seen, -[InTParent initWithName:] ends up calling [InTSon init]. So we get infinite recursion and the app crashes.
To solve this problem, you need to pick one of InTParent's init methods as its designated initializer. You should read about designated initializers in Apple's documentation.
You should pick the initWithName: method as InTParent's designated initializer. This method must not send any init message to self. It should only send super's designated initializer message (which in the case of NSObject is the init message), and it should send it to super. So you need to write the -[InTParent initWithName:] method like this:
- (id)initWithName:(NSString *)name {
// init is NSObject's designated initializer, and we send it to super.
if ((self = [super init])) {
// your initialization code here
}
return self;
}
By picking a designated initializer, and making your designated initializer only send the superclass's designated initializer message to super, you prevent the infinite recursion.
Because "self" is a "InTSon", so [self init] calls the "InTSon" init method.

NSDictionary: method only defined for abstract class. My app crashed

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.

Resources