Is it possible to use the class methods of some NSObject I've made to controls all existing instances?
I want a delegate class to be able to send messages to the Object's class methods which can then react appropriately with everything out there.
Sure. Just have all the objects register for notifications when they're created. For example:
- (id)init {
self = [super init];
if (!self) return nil;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(announceYourself:)
name:#"MYCLASS_ANNOUNCE"
object:nil];
return self;
}
+ (void)identifyInstances {
[[NSNotificationCenter defaultCenter] postNotificationName:#"MYCLASS_ANNOUNCE" object:self];
}
- (void)announceYourself:(id)notification {
NSLog(#"Instance %p reporting in!", self);
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self]; // VERY IMPORTANT -- IT WILL CRASH WITHOUT THIS
[super dealloc];
}
Not without using some collection of the objects. The simplest way to do it would be to store the objects in an array and send them all a message when you want something to happen. Specifically:
static NSMutableArray *allObjects = nil;
+ (void) initialize {
if (!allObjects) {
allObjects = [NSMutableArray new];
}
}
- (id) init {
if (self = [super init]) {
//Each object gets held in an array
[allObjects addObject:self];
}
return self;
}
+ (void) doSomethingToEveryObject {
[allObjects makeObjectsPerformSelector:#selector(doSomethingToThisObject)];
}
- (void) doSomethingToThisObject {
NSLog(#"Did something to %#",self];
}
- (void) release {
[super release];
//When the retain count reaches 1, then all external references have disappeared,
//and the only remaining reference is in the objects array.
if (self.retainCount == 1) {
[allObjects removeObject:self];
}
}
Related
I have such a helper class.
#interface CustomOnScreenshot : NSObject;
#property (copy) void (^finishedCallback)(id sender);
-(instancetype)initWithCallback: (void (^)(id sender))callback;
+(instancetype)onScreenshot:(void (^)(id sender))callback;
#end
#implementation CustomOnScreenshot
-(instancetype)initWithCallback: (void (^)(id sender))callback{
self = [super init];
if (self) {
self.finishedCallback = callback;
[self subscribeEvent];
}
return self;
}
+(instancetype)onScreenshot:(void (^)(id sender))callback{
CustomOnScreenshot * onScreenShot = [self new];
[onScreenShot setFinishedCallback:callback];
return onScreenShot;
}
-(void)subscribeEvent{
NSLog(#"CustomOnScreenshot subscribeEvent");
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doOnScreenShot:) name:UIApplicationUserDidTakeScreenshotNotification object:nil];
}
-(void)unsubscribeEvent{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationUserDidTakeScreenshotNotification object:nil];
}
-(void)doOnScreenShot: (id)sender{
if (self.finishedCallback) {
self.finishedCallback(sender);
}
}
-(void)dealloc{
NSLog(#"CustomOnScreenshot dealloc");
[self unsubscribeEvent];
}
The problem is that if you use it as intended, then the object is immediately destroyed
- (void)viewDidLoad {
[super viewDidLoad];
[CustomOnScreenshot onScreenshot:^(id sender) {
// CUSTOM code
}];
}
Log:
CustomOnScreenshot subscribeEvent
CustomOnScreenshot dealloc
All works only when I use the result in property, but I find this excessive
#property (strong, nonatomic) CustomOnScreenshot * customOnScreenshot;
- (void)viewDidLoad {
[super viewDidLoad];
self.customOnScreenshot = [CustomOnScreenshot onScreenshot:^(id sender) {
// CUSTOM code
}];
}
If you don't have a strong reference to the CustomOnScreenshot instance, then the object will be deallocated by ARC as as soon as +onScreenshot: finishes execution.
That's why the #property fixes it.
If you don't want an #property, then I'd suggest a singleton.
I'm using SpriteKit in Objective C for Mac OS. I'm a noob when it comes to working with NSNotification, but I used the following method in another app I was working on, but it's now not working. Perhaps there's a better method, but I want to be able to capture key presses based on what state the app is in.
The Main Issue
The state begins in GameBeginState. Once the spacebar is pressed, it moves into GamePlayState. If I press any other keys, it shows that it's using the keyPressed: method from GameBeginState as opposed to the keyPressed method in GamePlayState. Any ideas why? I can't figure this out for the life of me.
Details
I created a custom view called CustomNotificationHandler and set my main SKView in the project.
Here's the code for CustomNotificationHandler:
CustomNotificationHandler
#import "CustomNotificationHandler.h"
#implementation CustomNotificationHandler:SKView {
// Add instance variables here
}
- (id) initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
// Allocate and initialize your instance variables here
}
return self;
}
- (void) keyDown:(NSEvent *)theEvent {
[[NSNotificationCenter defaultCenter] postNotificationName:#"KeyPressedNotificationKey"
object:nil
userInfo:#{#"keyCode" : #(theEvent.keyCode)}];
}
#end
In my main class GameScene I have a GKStateMachine set up that initializes 3 states. The classes for the states are below:
GamePlayState.m
#import "GameReadyState.h"
#import "GamePlayState.h"
#interface GameReadyState()
#property (weak) GameScene *scene;
#end
#implementation GameReadyState
-(instancetype) initWithScene:(GameScene*)scene {
if (self = [super init]) {
self.scene = scene;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyPressed:) name:#"KeyPressedNotificationKey" object:nil];
}
return self;
}
-(void) didEnterWithPreviousState:(GKState *)previousState {
}
-(void) willExitWithNextState:(GKState *)nextState {
}
-(BOOL) isValidNextState:(Class)stateClass {
return stateClass == [GamePlayState class];
}
-(void)keyPressed:(NSNotification*)notification {
NSNumber *keyCodeObject = notification.userInfo[#"keyCode"];
NSInteger keyCode = keyCodeObject.integerValue;
NSLog(#"GAME READY key = %ld", (long)keyCode);
switch (keyCode) {
case 49:
[self.stateMachine enterState:[GamePlayState class]];
break;
default:
break;
}
}
#end
Here's the 2nd state...pretty much the same as the 1st:
GamePlayState
#import "GamePlayState.h"
#import "GamePauseState.h"
#interface GamePlayState()
#property (weak) GameScene *scene;
#end
#implementation GamePlayState
-(instancetype) initWithScene:(GameScene*)scene {
if (self = [super init]) {
self.scene = scene;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyPressed:) name:#"KeyPressedNotificationKey" object:nil];
}
return self;
}
-(void) didEnterWithPreviousState:(GKState *)previousState {
[_scene setPaused:NO];
}
-(BOOL) isValidNextState:(Class)stateClass {
return stateClass == [GamePauseState class];
}
-(void) updateWithDeltaTime:(NSTimeInterval)seconds {
[_scene.sequence moveLeft];
}
-(void)keyPressed:(NSNotification*)notification {
NSNumber *keyCodeObject = notification.userInfo[#"keyCode"];
NSInteger keyCode = keyCodeObject.integerValue;
NSLog(#"GAME PLAY key = %ld", (long)keyCode);
switch (keyCode) {
case 53:
[self.stateMachine enterState:[GamePauseState class]];
[_scene setPaused:YES];
break;
default:
break;
}
}
#end
so my problem is this:
I'm trying to have a singleton for a custom view xib. When I fire a notification it's not getting caught by the observer, because apparently my combobox has a different 'self' than '_sharedInstance'.
here is the code - initWithCoder:
-(id)initWithCoder:(NSCoder *)coder{
if (_sharedInstance){
self = _sharedInstance;
return self;
}else{
_sharedInstance = [super initWithCoder:coder];
return _sharedInstance;
}
}
awakeFromNib: (because in initWithCoder: _sharedInstance.cmbBox is nil)
-(void)awakeFromNib{
_sharedInstance.cmbBox.delegate = _sharedInstance;
}
The delegate that should catch the combobox selection:
// NSComboBoxDelegate:
-(void)comboBoxSelectionDidChange:(NSNotification *)notification{
if (notification.object == _sharedInstance.cmbBox){
NSLog(#"Do Something!");
[[NSNotificationCenter defaultCenter] postNotificationName:#"DoSomething" object:_sharedInstance userInfo:nil];
}
}
The problem is notification.object != _sharedInstance.cmbBox, so it never enters the if statement...
Did I do something stupid? :)
We have on class that is observe the notification with -[NSNotificationCenter addObserverForName:object:queue:usingBlock:] but it is randomly crash with EXC_BAD_ACCESS.
#interface Test: NSObject {
id obs;
}
#end
#implementation Test
- (id)init {
self = [super init];
if (self) {
Test * __weak weakSelf = self;
self->obs = [[NSNotificationCenter defaultCenter] addObserverForName:Notification object:nil queue:nil usingBlock:^(NSNotification *note) {
Test *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
[strongSelf handleNotification:note];
}];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self->obs];
self->obs = nil;
}
#end
We found that sometimes when the notification is posted NSNotificationCenter has already get the observer and prepare to call the block for the notification but in the mean time, this object is dealloced and so with the self->obs, which make it crash on [NSNotification postNotification...] method because of the zombie self->obs
Are there any workaround or fix on this issue?
PS. This is ARC code.
Are there any workaround for randomly crash?
Enable zombies - this will cause the exception to breakpoint on the offending line. From there it will be a lot easier to figure out than the general EXC_BAD_ACCESS crash.
I'm trying the following:
Whenever an OAuth token changes, I want to update some of my view controllers. The view controllers I want to update all inherit from a view controller. This view controller will listen for notifications whenever a new access token is set or whenever the access token is cleared. Whenever the access token is set or cleared, I want to set a selector to a method that should be executed once the view controller will be displayed (i.e. on -viewWillAppear:).
Somehow the blocks inside the addObserverForName:object:queue:usingBlock: don't seem to get called. I can't get it to log even. Because of this the selectors never change. From what I've read using the __block attribute on an ivar should allow the ivar to be changed from within a block.
#interface SDViewController ()
- (void)reloadData;
- (void)clearData;
#end
#implementation SDViewController
{
__block SEL selectorOnViewWillAppear;
}
- (id)initWithDataSource:(id <SDViewControllerDataSource>)dataSource
{
self = [super init];
if (self)
{
selectorOnViewWillAppear = #selector(reloadData);
}
return self;
}
- (void)viewDidLoad
{
NSLog(#"view did load");
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserverForName:kAccessTokenChangedNotification object:self queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification *note) {
NSLog(#"selector: test1");
selectorOnViewWillAppear = #selector(reloadData);
}];
[[NSNotificationCenter defaultCenter] addObserverForName:kAccessTokenClearedNotification object:self queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification *note) {
NSLog(#"selector: test2");
selectorOnViewWillAppear = #selector(clearData);
}];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (selectorOnViewWillAppear)
[self performSelector:selectorOnViewWillAppear];
}
- (void)reloadData
{
NSLog(#"reloadData");
selectorOnViewWillAppear = nil;
}
- (void)clearData
{
NSLog(#"clearData");
selectorOnViewWillAppear = nil;
}
#end
Fixed it by changing the observer code from this:
[[NSNotificationCenter defaultCenter]
addObserverForName:kAccessTokenClearedNotification
object:self
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
NSLog(#"selector: test2");
selectorOnViewWillAppear = #selector(clearData);
}];
To this:
[[NSNotificationCenter defaultCenter]
addObserverForName:kAccessTokenClearedNotification
object:nil
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
NSLog(#"selector: test2");
selectorOnViewWillAppear = #selector(clearData);
}];
The Apple documentation related to this issue is found here. The object in the
-addObserverForName:object:queue:usingBlock: method should be the object that generates the notifications. Setting the object to nil fixed the issue.