diff --git a/AppleCommon/GBViewMetal.m b/AppleCommon/GBViewMetal.m index 6a45dab..088d402 100644 --- a/AppleCommon/GBViewMetal.m +++ b/AppleCommon/GBViewMetal.m @@ -1,7 +1,9 @@ #import #import "GBViewMetal.h" #pragma clang diagnostic ignored "-Wpartial-availability" - +#if !TARGET_OS_IPHONE +#import "../Cocoa/NSObject+DefaultsObserver.h" +#endif static const vector_float2 rect[] = { @@ -74,8 +76,13 @@ static const vector_float2 rect[] = options:MTLResourceStorageModeShared]; output_resolution = (simd_float2){view.drawableSize.width, view.drawableSize.height}; + /* TODO: NSObject+DefaultsObserver can replace the less flexible `addDefaultObserver` in iOS */ +#if TARGET_OS_IPHONE [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadShader) name:@"GBFilterChanged" object:nil]; [self loadShader]; +#else + [self observeStandardDefaultsKey:@"GBFilter" selector:@selector(loadShader)]; +#endif } - (void) loadShader diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 9826bd4..911244a 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -13,6 +13,7 @@ #import "GBPaletteEditorController.h" #import "GBObjectView.h" #import "GBPaletteView.h" +#import "NSObject+DefaultsObserver.h" #define likely(x) GB_likely(x) #define unlikely(x) GB_unlikely(x) @@ -298,16 +299,6 @@ static void debuggerReloadCallback(GB_gameboy_t *gb) GB_set_palette(&_gb, [GBPaletteEditorController userPalette]); } -- (void) updateBorderMode -{ - _borderModeChanged = true; -} - -- (void) updateRumbleMode -{ - GB_set_rumble_mode(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRumbleMode"]); -} - - (void) initCommon { GB_init(&_gb, [self internalModel]); @@ -317,22 +308,52 @@ static void debuggerReloadCallback(GB_gameboy_t *gb) GB_set_log_callback(&_gb, (GB_log_callback_t) consoleLog); GB_set_input_callback(&_gb, (GB_input_callback_t) consoleInput); GB_set_async_input_callback(&_gb, (GB_input_callback_t) asyncConsoleInput); - GB_set_color_correction_mode(&_gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]); - GB_set_light_temperature(&_gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]); - GB_set_interference_volume(&_gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"]); - GB_set_border_mode(&_gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]); [self updatePalette]; GB_set_rgb_encode_callback(&_gb, rgbEncode); GB_set_camera_get_pixel_callback(&_gb, cameraGetPixel); GB_set_camera_update_request_callback(&_gb, cameraRequestUpdate); - GB_set_highpass_filter_mode(&_gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]); - GB_set_rewind_length(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); - GB_set_rtc_mode(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"]); GB_apu_set_sample_callback(&_gb, audioCallback); GB_set_rumble_callback(&_gb, rumbleCallback); GB_set_infrared_callback(&_gb, infraredStateChanged); GB_set_debugger_reload_callback(&_gb, debuggerReloadCallback); - [self updateRumbleMode]; + + GB_gameboy_t *gb = &_gb; + __unsafe_unretained Document *weakSelf = self; + + [self observeStandardDefaultsKey:@"GBColorCorrection" withBlock:^(NSNumber *value) { + GB_set_color_correction_mode(gb, value.unsignedIntValue); + }]; + + [self observeStandardDefaultsKey:@"GBLightTemperature" withBlock:^(NSNumber *value) { + GB_set_light_temperature(gb, value.doubleValue); + }]; + + [self observeStandardDefaultsKey:@"GBInterferenceVolume" withBlock:^(NSNumber *value) { + GB_set_interference_volume(gb, value.doubleValue); + }]; + + GB_set_border_mode(&_gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]); + [self observeStandardDefaultsKey:@"GBBorderMode" withBlock:^(NSNumber *value) { + _borderModeChanged = true; + }]; + + [self observeStandardDefaultsKey:@"GBHighpassFilter" withBlock:^(NSNumber *value) { + GB_set_highpass_filter_mode(gb, value.unsignedIntValue); + }]; + + [self observeStandardDefaultsKey:@"GBRewindLength" withBlock:^(NSNumber *value) { + [weakSelf performAtomicBlock:^{ + GB_set_rewind_length(gb, value.unsignedIntValue); + }]; + }]; + + [self observeStandardDefaultsKey:@"GBRTCMode" withBlock:^(NSNumber *value) { + GB_set_rtc_mode(gb, value.unsignedIntValue); + }]; + + [self observeStandardDefaultsKey:@"GBRumbleMode" withBlock:^(NSNumber *value) { + GB_set_rumble_mode(gb, value.unsignedIntValue); + }]; } - (void) updateMinSize @@ -766,82 +787,54 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) accessibilityDescription:@"Print"]; } - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateHighpassFilter) - name:@"GBHighpassFilterChanged" - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateColorCorrectionMode) - name:@"GBColorCorrectionChanged" - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateLightTemperature) - name:@"GBLightTemperatureChanged" - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateInterferenceVolume) - name:@"GBInterferenceVolumeChanged" - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateFrameBlendingMode) - name:@"GBFrameBlendingModeChanged" - object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updatePalette) name:@"GBColorPaletteChanged" object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateBorderMode) - name:@"GBBorderModeChanged" - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateRumbleMode) - name:@"GBRumbleModeChanged" - object:nil]; + __unsafe_unretained Document *weakSelf = self; + [self observeStandardDefaultsKey:@"GBFrameBlendingMode" + withBlock:^(NSNumber *value) { + weakSelf.view.frameBlendingMode = (GB_frame_blending_mode_t)value.unsignedIntValue; + }]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateRewindLength) - name:@"GBRewindLengthChanged" - object:nil]; + [self observeStandardDefaultsKey:@"GBDMGModel" withBlock:^(id newValue) { + weakSelf->_modelsChanging = true; + if (weakSelf->_currentModel == MODEL_DMG) { + [weakSelf reset:nil]; + } + weakSelf->_modelsChanging = false; + }]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateRTCMode) - name:@"GBRTCModeChanged" - object:nil]; - + [self observeStandardDefaultsKey:@"GBSGBModel" withBlock:^(id newValue) { + weakSelf->_modelsChanging = true; + if (weakSelf->_currentModel == MODEL_SGB) { + [weakSelf reset:nil]; + } + weakSelf->_modelsChanging = false; + }]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(dmgModelChanged) - name:@"GBDMGModelChanged" - object:nil]; + [self observeStandardDefaultsKey:@"GBCGBModel" withBlock:^(id newValue) { + weakSelf->_modelsChanging = true; + if (weakSelf->_currentModel == MODEL_CGB) { + [weakSelf reset:nil]; + } + weakSelf->_modelsChanging = false; + }]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(sgbModelChanged) - name:@"GBSGBModelChanged" - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(cgbModelChanged) - name:@"GBCGBModelChanged" - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(agbModelChanged) - name:@"GBAGBModelChanged" - object:nil]; + [self observeStandardDefaultsKey:@"GBAGBModel" withBlock:^(id newValue) { + weakSelf->_modelsChanging = true; + if (weakSelf->_currentModel == MODEL_AGB) { + [weakSelf reset:nil]; + } + weakSelf->_modelsChanging = false; + }]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateVolume) - name:@"GBVolumeChanged" - object:nil]; + [self observeStandardDefaultsKey:@"GBVolume" withBlock:^(id newValue) { + weakSelf->_volume = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"]; + }]; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) { _currentModel = MODEL_DMG; @@ -2173,96 +2166,6 @@ static bool is_path_writeable(const char *path) }]; } -- (void) updateVolume -{ - _volume = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"]; -} - -- (void) updateHighpassFilter -{ - if (GB_is_inited(&_gb)) { - GB_set_highpass_filter_mode(&_gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]); - } -} - -- (void) updateColorCorrectionMode -{ - if (GB_is_inited(&_gb)) { - GB_set_color_correction_mode(&_gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]); - } -} - -- (void) updateLightTemperature -{ - if (GB_is_inited(&_gb)) { - GB_set_light_temperature(&_gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]); - } -} - -- (void) updateInterferenceVolume -{ - if (GB_is_inited(&_gb)) { - GB_set_interference_volume(&_gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"]); - } -} - -- (void) updateFrameBlendingMode -{ - self.view.frameBlendingMode = (GB_frame_blending_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"]; -} - -- (void) updateRewindLength -{ - [self performAtomicBlock:^{ - if (GB_is_inited(&_gb)) { - GB_set_rewind_length(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); - } - }]; -} - -- (void) updateRTCMode -{ - if (GB_is_inited(&_gb)) { - GB_set_rtc_mode(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"]); - } -} - -- (void)dmgModelChanged -{ - _modelsChanging = true; - if (_currentModel == MODEL_DMG) { - [self reset:nil]; - } - _modelsChanging = false; -} - -- (void)sgbModelChanged -{ - _modelsChanging = true; - if (_currentModel == MODEL_SGB) { - [self reset:nil]; - } - _modelsChanging = false; -} - -- (void)cgbModelChanged -{ - _modelsChanging = true; - if (_currentModel == MODEL_CGB) { - [self reset:nil]; - } - _modelsChanging = false; -} - -- (void)agbModelChanged -{ - _modelsChanging = true; - if (_currentModel == MODEL_AGB) { - [self reset:nil]; - } - _modelsChanging = false; -} - - (void)setFileURL:(NSURL *)fileURL { [super setFileURL:fileURL]; diff --git a/Cocoa/GBGLShader.m b/Cocoa/GBGLShader.m index 920226b..1d39090 100644 --- a/Cocoa/GBGLShader.m +++ b/Cocoa/GBGLShader.m @@ -163,7 +163,7 @@ void main(void) {\n\ /* OpenGL is black magic. Closing one view causes others to be completely black unless we reload their shaders */ /* We're probably not freeing thing in the right place. */ - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged$DefaultsObserver" object:nil]; } + (GLuint)shaderWithContents:(NSString*)contents type:(GLenum)type diff --git a/Cocoa/GBOpenGLView.m b/Cocoa/GBOpenGLView.m index 0ce9047..aa401e8 100644 --- a/Cocoa/GBOpenGLView.m +++ b/Cocoa/GBOpenGLView.m @@ -1,5 +1,6 @@ #import "GBOpenGLView.h" #import "GBView.h" +#import "NSObject+DefaultsObserver.h" #import @implementation GBOpenGLView @@ -27,13 +28,12 @@ - (instancetype)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat *)format { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(filterChanged) name:@"GBFilterChanged" object:nil]; + __unsafe_unretained GBOpenGLView *weakSelf = self; + [self observeStandardDefaultsKey:@"GBFilter" withBlock:^(id newValue) { + weakSelf.shader = nil; + [weakSelf setNeedsDisplay:true]; + + }]; return [super initWithFrame:frameRect pixelFormat:format]; } - -- (void) filterChanged -{ - self.shader = nil; - [self setNeedsDisplay:true]; -} @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 52a6d4f..32461f7 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -360,14 +360,12 @@ static inline NSString *keyEquivalentString(NSMenuItem *item) { [[NSUserDefaults standardUserDefaults] setObject:[[self class] filterList][[sender indexOfSelectedItem]] forKey:@"GBFilter"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil]; } - (IBAction)highpassFilterChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) forKey:@"GBHighpassFilter"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBHighpassFilterChanged" object:nil]; } @@ -399,43 +397,36 @@ static inline NSString *keyEquivalentString(NSMenuItem *item) { [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] != NSOnState forKey:@"GBAspectRatioUnkept"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBAspectChanged" object:nil]; } - (IBAction)colorCorrectionChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag) forKey:@"GBColorCorrection"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorCorrectionChanged" object:nil]; } - (IBAction)lightTemperatureChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0) forKey:@"GBLightTemperature"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBLightTemperatureChanged" object:nil]; } - (IBAction)interferenceVolumeChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0) forKey:@"GBInterferenceVolume"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBInterferenceVolumeChanged" object:nil]; } - (IBAction)volumeChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0) forKey:@"GBVolume"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBVolumeChanged" object:nil]; } - (IBAction)franeBlendingModeChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) forKey:@"GBFrameBlendingMode"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBFrameBlendingModeChanged" object:nil]; - } - (void)updatePalettesMenu @@ -490,14 +481,12 @@ static inline NSString *keyEquivalentString(NSMenuItem *item) { [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag) forKey:@"GBBorderMode"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBBorderModeChanged" object:nil]; } - (IBAction)rumbleModeChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag) forKey:@"GBRumbleMode"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBRumbleModeChanged" object:nil]; } - (IBAction)hotkey1Changed:(id)sender @@ -516,15 +505,12 @@ static inline NSString *keyEquivalentString(NSMenuItem *item) { [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) forKey:@"GBRewindLength"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBRewindLengthChanged" object:nil]; } - (IBAction)rtcModeChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) forKey:@"GBRTCMode"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBRTCModeChanged" object:nil]; - } - (IBAction)changeAutoUpdates:(id)sender @@ -813,7 +799,6 @@ static inline NSString *keyEquivalentString(NSMenuItem *item) { [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) forKey:@"GBDMGModel"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBDMGModelChanged" object:nil]; } @@ -821,21 +806,18 @@ static inline NSString *keyEquivalentString(NSMenuItem *item) { [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) forKey:@"GBSGBModel"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBSGBModelChanged" object:nil]; } - (IBAction)cgbModelChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) forKey:@"GBCGBModel"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBCGBModelChanged" object:nil]; } - (IBAction)agbModelChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) forKey:@"GBAGBModel"]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"GBAGBModelChanged" object:nil]; } - (IBAction)reloadButtonsData:(id)sender diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index ae15813..f6dcf2e 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -5,6 +5,7 @@ #import "GBViewMetal.h" #import "GBButtons.h" #import "NSString+StringForKey.h" +#import "NSObject+DefaultsObserver.h" #import "Document.h" #define JOYSTICK_HIGH 0x4000 @@ -138,7 +139,10 @@ static const uint8_t workboy_vk_to_key[] = { { [self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; + __unsafe_unretained GBView *weakSelf = self; + [self observeStandardDefaultsKey:@"GBAspectRatioUnkept" withBlock:^(id newValue) { + [weakSelf setFrame:weakSelf.superview.frame]; + }]; tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingMouseMoved owner:self @@ -161,11 +165,6 @@ static const uint8_t workboy_vk_to_key[] = { }); } -- (void) ratioKeepingChanged -{ - [self setFrame:self.superview.frame]; -} - - (void)dealloc { if (mouse_hidden) { diff --git a/Cocoa/NSObject+DefaultsObserver.h b/Cocoa/NSObject+DefaultsObserver.h new file mode 100644 index 0000000..18469f8 --- /dev/null +++ b/Cocoa/NSObject+DefaultsObserver.h @@ -0,0 +1,6 @@ +#import + +@interface NSObject (DefaultsObserver) +- (void)observeStandardDefaultsKey:(NSString *)key withBlock:(void(^)(id newValue))block; +- (void)observeStandardDefaultsKey:(NSString *)key selector:(SEL)selector; +@end diff --git a/Cocoa/NSObject+DefaultsObserver.m b/Cocoa/NSObject+DefaultsObserver.m new file mode 100644 index 0000000..8dac912 --- /dev/null +++ b/Cocoa/NSObject+DefaultsObserver.m @@ -0,0 +1,72 @@ +#import "NSObject+DefaultsObserver.h" +#import +#import + +@interface GBUserDefaultsObserverHelper : NSObject +@end + +@implementation GBUserDefaultsObserverHelper ++ (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + [[NSNotificationCenter defaultCenter] postNotificationName:[keyPath stringByAppendingString:@"Changed$DefaultsObserver"] + object:nil + userInfo:@{ + @"value": change[NSKeyValueChangeNewKey] + }]; +} + ++ (void)startObservingKey:(NSString *)key +{ + if (!NSThread.isMainThread) { + dispatch_sync(dispatch_get_main_queue(), ^{ + [self startObservingKey:key]; + }); + return; + } + static NSMutableSet *set = nil; + if (!set) { + set = [NSMutableSet set]; + } + if ([set containsObject:key]) return; + [set addObject:key]; + [[NSUserDefaults standardUserDefaults] addObserver:(id)self + forKeyPath:key + options:NSKeyValueObservingOptionNew + context:nil]; +} +@end + +@implementation NSObject (DefaultsObserver) +- (void)observeStandardDefaultsKey:(NSString *)key selector:(SEL)selector +{ + __weak id weakSelf = self; + [self observeStandardDefaultsKey:key + withBlock:^(id newValue) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [weakSelf performSelector:selector withObject:newValue]; +#pragma clang diagnostic pop + }]; +} + +- (void)observeStandardDefaultsKey:(NSString *)key withBlock:(void(^)(id newValue))block +{ + NSString *notificationName = [key stringByAppendingString:@"Changed$DefaultsObserver"]; + objc_setAssociatedObject(self, sel_registerName(notificationName.UTF8String), block, OBJC_ASSOCIATION_RETAIN); + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(standardDefaultsKeyChanged:) + name:notificationName + object:nil]; + [GBUserDefaultsObserverHelper startObservingKey:key]; + block([[NSUserDefaults standardUserDefaults] objectForKey:key]); +} + +- (void)standardDefaultsKeyChanged:(NSNotification *)notification +{ + SEL selector = sel_registerName(notification.name.UTF8String); + ((void(^)(id))objc_getAssociatedObject(self, selector))(notification.userInfo[@"value"]); +} +@end