I want to use a block as a callback function. I store my block as a property in my class that does the calling back:
#interface MyParameter
{
float myValue;
void (^callback)(float);
}
#property(copy) void (^callback)(float);
#end
#implementation MyParameter
#synthesize callback;
- (void) valueChanged
{
callback(myValue);
}
#end
Then I set the callback:
MyParameter * param = [[MyParameter alloc] init];
[param setCallback:^(float value) { [self doSomethingWithValue:value]; }];
So far so simple. In reality, MyParameteris a generic abstraction which holds details on a runtime-determined number of different types of parameter. I can have continuous (float) values, discrete values (int) and booleans. So next, I tried this:
#interface MyParameter
{
float floatVal;
int intVal;
void (^contCallback)(float);
void (^discCallback)(int); // And a boolean one too but let's keep this short
ParamType type; // enum of types ie enum ParamType { Continuous, Discrete, Boolean };
}
#property(copy) void (^contCallback)(float);
#property(copy) void (^discCallback)int);
#end
#implementation MyParameter
#synthesize contCallback;
#synthesize discCallback;
- (void) valueChanged
{
switch (type) {
case Continuous:
contCallback(floatVal);
break;
case Discrete:
discCallback(intVal);
break;
// Leave default out for brevity
}
}
#end
Which is getting uglier. I want to keep just one ^callback as an ivar/property. I then want to set it like this:
MyParameter *contParam = [[MyParameter alloc] init];
[contParam setCallback:(^float value) { [self doSomethingContinuous:value]; }];
MyParameter *discreteParam = [[MyParameter alloc] init];
[discreteParam setCallback:(^int value) { [self doSomethingDiscrete:value]; }];
Inside MyParameter my valueChanged method would retain the switch/case from above, to decide what to pass in to the callback.
Is this possible?
Pass your parameter as an NSNumber. You can recover its original type using -objCType which will return a string indicating a type encoding. This way you don't have to polymorph your function call.
Alternately, if NSNumber turns out to be too inflexible, just create your own Argument data class that encodes whatever information you need. If necessary, you can also create a Result data class for the return type.
Related
In my macOS Objective-C application, I have created a subclass of NSMutableSet. What I want to achieve is a NSMutableSet that does not use isEqual: as the comparing strategy. Specifically, The set will contain objects of type NSRunningApplication, and I want the set to work based on the equality of the objects bundle identifiers. Following is my implementation:
Header file:
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
#interface BundleIdentifierAwareMutableSet : NSMutableSet
#property (atomic, strong) NSMutableSet *backStorageMutableSet;
#property (atomic, strong) NSMutableArray *backStorageMutableArray;
#end
NS_ASSUME_NONNULL_END
Implementation file:
#import "BundleIdentifierAwareMutableSet.h"
#implementation BundleIdentifierAwareMutableSet
#synthesize backStorageMutableSet;
- (instancetype)init {
self = [super init];
if (self) {
self.backStorageMutableSet = [[NSMutableSet alloc] init];
self.backStorageMutableArray = [[NSMutableArray alloc] init];
}
return self;
}
- (NSUInteger)count {
return [self.backStorageMutableArray count];
}
- (NSRunningApplication *)member:(NSRunningApplication *)object {
__block NSRunningApplication *returnValue = nil;
[self.backStorageMutableArray enumerateObjectsUsingBlock:^(NSRunningApplication * _Nonnull app, NSUInteger __unused idx, BOOL * _Nonnull stop) {
if ([app.bundleIdentifier isEqualToString:[object bundleIdentifier]]) {
returnValue = app;
if (![app isEqual:object]) {
NSLog(#"An ordinary set would have not considered the two objects equal.");
}
*stop = YES;
}
}];
return returnValue;
}
- (NSEnumerator *)objectEnumerator {
self.backStorageMutableSet = [NSMutableSet setWithArray:self.backStorageMutableArray];
return [self.backStorageMutableSet objectEnumerator];
}
- (void)addObject:(NSRunningApplication *)object {
NSRunningApplication *app = [self member:object];
if (app == nil) {
[self.backStorageMutableArray addObject:object];
}
}
- (void)removeObject:(NSRunningApplication *)object {
NSArray *snapShot = [self.backStorageMutableArray copy];
[snapShot enumerateObjectsUsingBlock:^(NSRunningApplication * _Nonnull currentApp, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
if ([[currentApp bundleIdentifier] isEqualToString:[object bundleIdentifier]]) {
[self.backStorageMutableArray removeObject:currentApp];
if (![currentApp isEqual:object]) {
NSLog(#"An ordinary set would have not considered the two objects equal.");
}
}
}];
}
This seems to work, and indeed, When applicable, Xcode logs that an ordinary NSMutableSet would have not considered two members equal. I would like to bring this implementation to the Production App, but I am afraid I have not considered something important, since this is the first time I subclass NSMutableSet. For example, I am worried about the following method:
- (NSEnumerator *)objectEnumerator {
self.backStorageMutableSet = [NSMutableSet setWithArray:self.backStorageMutableArray];
return [self.backStorageMutableSet objectEnumerator];
}
This is the only use I do of the backStorageMutableSet since the rest is backed to the array. Is this fine or can bring troubles ? Will other parts of the subclass bring problems ? Any help will be greatly appreciated. Thanks
Don't do this. Subclassing collections should be the last resort. It can have implications on performance, ... Try to use highest possible abstraction and go down if it doesn't work for you for some reason.
Wrapper object
Wrap the NSRunningApplication in another object and provide your own hash & isEqual: methods.
Application.h:
#interface Application: NSObject
#property (nonatomic, strong, readonly, nonnull) NSRunningApplication *application;
#end
Application.m:
#interface Application ()
#property (nonatomic, strong, nonnull) NSRunningApplication *application;
#end
#implementation Application
- (nonnull instancetype)initWithRunningApplication:(NSRunningApplication *_Nonnull)application {
if ((self = [super init]) == nil) {
// https://developer.apple.com/documentation/objectivec/nsobject/1418641-init?language=objc
//
// The init() method defined in the NSObject class does no initialization; it simply
// returns self. In terms of nullability, callers can assume that the NSObject
// implementation of init() does not return nil.
return nil;
}
self.application = application;
return self;
}
// https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418795-isequal?language=objc
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[Application class]]) {
return NO;
}
Application *app = (Application *)object;
return [self.application.bundleIdentifier isEqualToString:app.application.bundleIdentifier];
}
// https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418859-hash?language=objc
- (NSUInteger)hash {
return self.application.bundleIdentifier.hash;
}
#end
Toll-free bridging & CFMutableSetRef
CFSet is bridged with the NSSet, CFMutableSet is bridged with the NSMutableSet, etc. It means that you can create a set via Core Foundation
API and then use it as NSSet for example. Core Foundation is a powerful
framework which exposes more stuff to you.
You can provide a custom set of callbacks for the CFSet.
/*!
#typedef CFSetCallBacks
Structure containing the callbacks of a CFSet.
#field version The version number of the structure type being passed
in as a parameter to the CFSet creation functions. This
structure is version 0.
#field retain The callback used to add a retain for the set on
values as they are put into the set. This callback returns
the value to store in the set, which is usually the value
parameter passed to this callback, but may be a different
value if a different value should be stored in the set.
The set's allocator is passed as the first argument.
#field release The callback used to remove a retain previously added
for the set from values as they are removed from the
set. The set's allocator is passed as the first
argument.
#field copyDescription The callback used to create a descriptive
string representation of each value in the set. This is
used by the CFCopyDescription() function.
#field equal The callback used to compare values in the set for
equality for some operations.
#field hash The callback used to compare values in the set for
uniqueness for some operations.
*/
typedef struct {
CFIndex version;
CFSetRetainCallBack retain;
CFSetReleaseCallBack release;
CFSetCopyDescriptionCallBack copyDescription;
CFSetEqualCallBack equal;
CFSetHashCallBack hash;
} CFSetCallBacks;
There're predefined sets of callbacks like:
/*!
#constant kCFTypeSetCallBacks
Predefined CFSetCallBacks structure containing a set of callbacks
appropriate for use when the values in a CFSet are all CFTypes.
*/
CF_EXPORT
const CFSetCallBacks kCFTypeSetCallBacks;
Which means that you're not forced to provide all of them, but you're free to modify just some of them. Let's prepare two callback functions:
// typedef CFHashCode (*CFSetHashCallBack)(const void *value);
CFHashCode runningApplicationBundleIdentifierHash(const void *value) {
NSRunningApplication *application = (__bridge NSRunningApplication *)value;
return [application.bundleIdentifier hash];
}
// typedef Boolean (*CFSetEqualCallBack)(const void *value1, const void *value2);
Boolean runningApplicationBundleIdentifierEqual(const void *value1, const void *value2) {
NSRunningApplication *application1 = (__bridge NSRunningApplication *)value1;
NSRunningApplication *application2 = (__bridge NSRunningApplication *)value2;
return [application1.bundleIdentifier isEqualToString:application2.bundleIdentifier];
}
You can use them in this way:
- (NSMutableSet<NSRunningApplication *> *_Nullable)bundleIdentifierAwareMutableSetWithCapacity:(NSUInteger)capacity {
// > Predefined CFSetCallBacks structure containing a set of callbacks
// > appropriate for use when the values in a CFSet are all CFTypes.
//
// Which means that you shouldn't bother about retain, release, ... callbacks,
// they're already set.
//
// CFSetCallbacks can be on stack, because this structure is copied in the
// CFSetCreateMutable function.
CFSetCallBacks callbacks = kCFTypeSetCallBacks;
// Overwrite just the hash & equal callbacks
callbacks.hash = runningApplicationBundleIdentifierHash;
callbacks.equal = runningApplicationBundleIdentifierEqual;
// Try to create a mutable set.
CFMutableSetRef set = CFSetCreateMutable(kCFAllocatorDefault, capacity, &callbacks);
if (set == NULL) {
// Failed, do some error handling or just return nil
return nil;
}
// Transfer the ownership to the Obj-C & ARC => no need to call CFRelease
return (__bridge_transfer NSMutableSet *)set;
}
&
NSMutableSet<NSRunningApplication *> *set = [self bundleIdentifierAwareMutableSetWithCapacity:50];
[set addObjectsFromArray:[[NSWorkspace sharedWorkspace] runningApplications]];
NSLog(#"%#", set);
I'm trying to take a C-style vector and convert it into an NSMutable array object.
Here's the function:
+(NSMutableArray*)arrayFromPoints:(vector<Point2f>&)points
{
NSMutableArray* pointArray = [[NSMutableArray alloc] init];
for (int i=0;i<points.size();i++)
{
Point2f point = points[i];
JBPoint* point2 = [[JBPoint alloc]initWithX:point.x andY:point.y];
[pointArray addObject:point2];
}
return pointArray;
}
Custom point class:
#implementation JBPoint
float _x;
float _y;
-(id) initWithX:(float)x andY:(float)y
{
if (self=[super init])
{
_x = x;
_y=y;
}
return self;
}
-(float)x{ return _x;}
-(float)y {return _y;}
#end
Test output:
for (JBPoint* pnt in array)
{
NSLog(#"%f, %f", pnt.x, pnt.y);
}
I except it to output the array, but every time it just gives me the last value! does anyone know why?
I thought that they were maybe pointing to the same object, but they have different memory addresses.
So I figured out the problem. float _x;
float _y; where being treated like class variables instead of instance variables. Changed the class to:
#interface JBPoint()
{
float _x;
float _y;
}
#end
#implementation JBPoint
-(id) initWithX:(float)x andY:(float)y
{
if (self=[super init])
{
_x = x;
_y=y;
}
return self;
}
-(float)x{ return _x;}
-(float)y {return _y;}
#end
if you wrote
#property (nonatomic, readonly) float x;
#property (nonatomic, readonly) float y;
in your header file you wouldn't need to declare the instance variables (and would have avoided the issue here) and you could delete the getter methods your wrote as that would all be generated by the compiler for you and your custom init method would continue to work (with the most recent compiler).
Its a good idea to do this because:
less code
your intent is clear - 2 variables that are readonly for clients
follows language conventions
If you are using an older compiler (an older version of Xcode) then you would also have to #synthesize the accessors like this:
#synthesize x = _x;
Some interesting asides:
With the most recent complier you didn't need the class extension.
#implementation{
float _x;
float _y;
}
would also have worked.
As referenced in WWDC 2012 session video 413, the current recommended pattern to write an init method is:
...
self = [super init];
if (self) {
...
}
return self;
In Objective-C, it is possible to pass a class as a parameter to a method:
- (void) methodThatTakesClass:(Class)theClass;
And it is possible to pass an instance that is conforming to a protocol as a parameter:
- (void) myConformInstance:(id <MyProtocol>)theObject;
Is it possible to use the combined functionality? A method which takes a class which is conforming to a certain protocol.
Yes. The following is a valid program which will log the NSObject class.
#import <Foundation/Foundation.h>
void f(Class <NSObject> c) {
NSLog(#"%#",c);
}
int main() {
f([NSObject class]);
}
This would cause a compiler error if you tried to pass a class which doesn't conform to NSObject, such as the Object class. You can also use it for methods.
- (void)printClass:(Class <NSObject>)c;
also valid:
#interface Something: Object {
}
- (void) foo:(int(*)(void))bar;
#end
#implementation Something
- (void) foo:(int(*)(void))bar {
return (*bar)();
}
#end
int someFunc( void ) {
return 9;
}
int main ( int argc, char **argv ) {
Something *object = [[Something alloc] init];
printf( "%i\n", [object foo:&someFunc] );
[object release];
return 0;
}
I need to pass a address of the function to a function pointer.Below is the code what i'm
trying to accomplish it.I'm sure that i'm mistaking somewhere so that i'm getting a
runtime exception.How to pass the address of a function to a function pointer.Am i
missing something in this code.
RS232Msg.h
typedef RS232Msg* (*tpNewMsg)(void);
typedef struct
{
int nMessageId;
NSString* szAsciiName;
tpNewMsg pNewMessageFunc;
} stRs232Struct;
#interface RS232Msg : NSObject
{
}
#end
RS232Msg.m
#implementation RS232Msg
-(id)initWithRS232Msg:(int)uMessageId withNewMsg:(tpNewMsg)pNewMsg withAsciiName:(const char*)szAsciiName withData:(void*)pData withSize:(size_t)uDataSize
{
//stmts;
}
#end
RS232Derived.h
#interface RS232MsgRequestSession : RS232Msg{
}
+(RS232Msg*)NewMsg;
RS232Derived.m
#implementation RS232MsgRequestSession
+(id)FromMsg:(RS232Msg*)pMsg
{
pMsg = [RS232MsgRequestSession alloc];
return pMsg;
}
-(id)init
{
if (self = [super initWithRS232Msg:[RS232MsgRequestSession getID] withNewMsg:[RS232MsgRequestSession NewMsg] withAsciiName:NULL withData:&st withSize:sizeof(st)]) {
}
return self;
}
#end
A run time exception happens when i tried to pass the address of the function
withNewMsg:
[RS232MsgRequestSession NewMsg]
to the function pointer pNewMsg() in the initWithRS232Msg
method.
[RS232MsgRequestSession NewMsg] doesn't get you the address of the method. The expression is evaluated and the result object is passed as the argument. While there is a way to access the implementation of a method directly (read this for details), there might be an easier way to achieve what you want.
Selector based approach
Instead of what you're doing right now, you can consider doing something like this,
- (id) initWithTarget:(id)aTarget action:(SEL)aSelector ... {
// save these two for later reference.
}
and later,
if ( [target respondsToSelector:theSelector] ) {
result = [target performSelector:theSelector];
}
This way you can achieve what you want.
Blocks based approach
Truth be told, Blocks are turning out to be the best addition to Objective-C.
Change the typedef to typedef RS232Msg* (^tpNewMsg)(void);
Now the init method would become,
-(id)init
{
self = [super initWithR232Msg:[RS232MsgRequestSession getID]
withNewMsg:^{
return [RS232MsgRequestSession NewMsg];
}
withAsciiName:NULL
withData:&st
withSize:sizeof(st)]
if ( self ) {
// do stuff
}
return self;
}
#end
I have an Application Delegate class with a enumeration which looks like this:
typedef enum {
Online = 3500,
DoNotDisturb = 9500,
Offline = 18500,
Away = 15500,
Busy = 6500,
BeRightBack = 12500
} status;
Additionally I have a property to set a value from the enumerator in my interface file:
#interface MyAppDelegate : NSObject <UIApplicationDelegate> {
status userStatus;
}
#property (nonatomic, setter=setStatus) status userStatus;
#end
Finally I have the following message in my implementation file:
#implementation Communicator2AppDelegate
- (void)setStatus:(status)_userStatus {
if ([NSThread isMainThread]) {
// some stuff happens here ...
} else {
[self performSelectorOnMainThread:#selector(setStatus:) withObject:_userStatus waitUntilDone:NO];
}
}
My issue is the following: the performSelectorOnMainThread message isn't working because it doesn't accept '_userStatus' as a value. My guess is the message assumes it's an enum, not a real value. I get the following error message upon compilation: "Incompatible type for argument 2 of 'performSelectorOnMainThread:withObject:waitUntilDone.'"
Does anyone have any idea on how to make this work?
You need to pass an object value to this method and enum (that is int) is scalar value. To achieve what you need you must wrap your integer to obj-c object (e.g. NSNumber):
- (void)setStatus:(status)_userStatus {
if ([NSThread isMainThread]) {
// some stuff happens here ...
} else {
[self performSelectorOnMainThread:#selector(setStatus:) withObject:[NSNumber numberWithInt:_userStatus] waitUntilDone:NO];
}
}