Let's say I need to communicate with a class that provides a protocol and calls delegate methods when an operation is complete, as so:
#protocol SomeObjectDelegate
#required
- (void)stuffDone:(id)anObject;
- (void)stuffFailed;
#end
#interface SomeObject : NSObject
{
}
#end
Now, I've decided that while I could make another class implement the stuffDone: delegate method, I've decided that I'd rather encapsulate the process into a block which is written somewhere close to where SomeObject is instantiated, called, etc. How might I do this? Or in other words, if you look at this famous article on blocks (in the Replace Callbacks section); how might I write a method in SomeObject that accepts a completionHandler: of sorts?
It sounds like you wish to communicate with an existing class which is designed to take a delegate object. There are a number of approaches, including:
using a category to add block-based variants of the appropriate methods;
use a derived class to add the block-based variants; and
write a class which implements the protocol and calls your blocks.
Here is one way to do (3). First let's assume your SomeObject is:
#protocol SomeObjectDelegate
#required
- (void)stuffDone:(id)anObject;
- (void)stuffFailed;
#end
#interface SomeObject : NSObject
{
}
+ (void) testCallback:(id<SomeObjectDelegate>)delegate;
#end
#implementation SomeObject
+ (void) testCallback:(id<SomeObjectDelegate>)delegate
{
[delegate stuffDone:[NSNumber numberWithInt:42]];
[delegate stuffFailed];
}
#end
so we have some way to test - you will have a real SomeObject.
Now define a class which implements the protocol and calls your supplied blocks:
#import "SomeObject.h"
typedef void (^StuffDoneBlock)(id anObject);
typedef void (^StuffFailedBlock)();
#interface SomeObjectBlockDelegate : NSObject<SomeObjectDelegate>
{
StuffDoneBlock stuffDoneCallback;
StuffFailedBlock stuffFailedCallback;
}
- (id) initWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail;
- (void)dealloc;
+ (SomeObjectBlockDelegate *) someObjectBlockDelegateWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail;
// protocol
- (void)stuffDone:(id)anObject;
- (void)stuffFailed;
#end
This class saves the blocks you pass in and calls them in response to the protocol callbacks. The implementation is straightforward:
#implementation SomeObjectBlockDelegate
- (id) initWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail
{
if (self = [super init])
{
// copy blocks onto heap
stuffDoneCallback = Block_copy(done);
stuffFailedCallback = Block_copy(fail);
}
return self;
}
- (void)dealloc
{
Block_release(stuffDoneCallback);
Block_release(stuffFailedCallback);
[super dealloc];
}
+ (SomeObjectBlockDelegate *) someObjectBlockDelegateWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail
{
return (SomeObjectBlockDelegate *)[[[SomeObjectBlockDelegate alloc] initWithOnDone:done andOnFail:fail] autorelease];
}
// protocol
- (void)stuffDone:(id)anObject
{
stuffDoneCallback(anObject);
}
- (void)stuffFailed
{
stuffFailedCallback();
}
#end
The only thing you need to remember is to Block_copy() the blocks when initializing and to Block_release() them later - this is because blocks are stack allocated and your object may outlive its creating stack frame; Block_copy() creates a copy in the heap.
Now you can all a delegate-based method passing it blocks:
[SomeObject testCallback:[SomeObjectBlockDelegate
someObjectBlockDelegateWithOnDone:^(id anObject) { NSLog(#"Done: %#", anObject); }
andOnFail:^{ NSLog(#"Failed"); }
]
];
You can use this technique to wrap blocks for any protocol.
ARC Addendum
In response to the comment: to make this ARC compatible just remove the calls to Block_copy() leaving direct assignments:
stuffDoneCallback = done;
stuffFailedCallback = fail;
and remove the dealloc method. You can also change Blockcopy to copy, i.e. stuffDoneCallback = [done copy];, and this is what you might assume is needed from reading the ARC documentation. However it is not as the assignment is to a strong variable which causes ARC to retain the assigned value - and retaining a stack block copies it to the heap. Therefore the ARC code generated produces the same results with or without the copy.
You could do something like this:
typedef void (^AZCallback)(NSError *);
AZCallback callback = ^(NSError *error) {
if (error == nil) {
NSLog(#"succeeded!");
} else {
NSLog(#"failed: %#", error);
}
};
SomeObject *o = [[SomeObject alloc] init];
[o setCallback:callback]; // you *MUST* -copy the block
[o doStuff];
...etc;
Then inside SomeObject, you could do:
if ([self hadError]) {
callback([self error]);
} else {
callback(nil);
}
The below link explains how the call backs using delegates could be easily replaced with blocks.
The examples includes UITableview,UIAlertview and ModalViewController.
click me
Hope this helps.
Related
I want to design a class (TrackingClass) that would be in charge of tracking the calls to some methods of an other class (TrackedClass), i.e. of setting up the method swizzling from what I understood.
So let's say I load up an array with #selectors of the instance methods of TrackedClass i'm interested in.
Here is the pseudo-code I would like to run :
#implementation BCTrackedClass
-(void)doA
{
}
#end
and
#implementation BCTrackingClass
#import "BCTrackingClass.h"
#import "BCTrackedClass.h"
#include <objc/runtime.h>
#include <objc/objc-runtime.h>
#implementation BCTrackingClass
void myMethodIMP(id self, SEL _cmd);
void myMethodIMP(id self, SEL _cmd)
{
//NSLog(#"_cmd : %#",NSStringFromSelector(_cmd));
[BCTrackingClass logCallForMethod:NSStringFromSelector(_cmd)];
objc_msgSend(self,
NSSelectorFromString([NSString stringWithFormat:#"tracked%#",NSStringFromSelector(_cmd)]));
}
+(void)setUpTrackingForClass:(Class)aClass andMethodArray:(NSArray*)anArray //Array of selectorsStrings of methods to track
{
for (NSString* selectorString in anArray)
{
SEL selector = NSSelectorFromString(selectorString);
SEL trackedSelector = NSSelectorFromString([NSString stringWithFormat:#"tracked%#",selectorString]);
class_addMethod(aClass,
trackedSelector,
(IMP) myMethodIMP, "v#:");
//Swizzle the original method with the tracked one
Method original = class_getInstanceMethod(aClass,
selector);
Method swizzled = class_getInstanceMethod(aClass,
trackedSelector);
method_exchangeImplementations(original, swizzled);
}
}
+(void)logCallForMethod:(NSString*)aSelectorString
{
NSLog(#"%#",aSelectorString);
}
#end
Theoretically, I'm just missing the bit of code where I could effectively create this new instance method trackedSelector. Can I achieve that ?
Edit
I updated the code with some new piece of information, am I getting closer ?
Edit 2
I set up a Github repository with a Demo application if people want to dynamically try out their ideas.
Source : BCTrackingClass on Github
Edit 3
I finally come up with a working version of the code (cf Github repo, or just above). My next problem is : I want my class to be instance based (currently, all my methods are class methods), so that I can assign a property #property NSMutableDictionnary* to instances of the class for call logging.
I'm not sure how to achieve that. Any ides ?
Do you want to do it for all instances of all objects of that class?
for some selectors or all of them?
...
If what you want is to track specific instances, then the simplest route is to use isa swizzling, doing that, more or less (the code is absolutely untested)
#interface ClassTracker
+ (void)trackObject:(id)object;
#end
static const char key;
#implementation ClassTracker
+ (void)trackObject:(id)object
{
objc_setAssociatedObject(object, &key, [object class], OBJC_ASSOCIATION_ASSIGN);
object_setClass(object, [ClassTracker class]);
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
Class aClass = objc_getAssociatedObject(self, &key);
return [aClass instanceMethodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
Class aClass = objc_getAssociatedObject(self, &key);
// do your tracing here
object_setClass(self, aClass);
[invocation invoke];
object_setClass(self, [ClassTracker class]);
}
// dealloc is magical in the sense that you really want to undo your hooking
// and not resume it ever!
- (void)dealloc
{
Class aClass = objc_getAssociatedObject(self, &key);
object_setClass(self, aClass);
[self dealloc];
}
#end
If it's used for reverse engineering or debug purposes, that should (with minor adaptations) do the trick.
If you intend that to be fast, then you have to do instance method swizzling, knowing their type and so forth.
My "solution" has the drawback that it will only trace entering calls, IOW if a selector calls other ones, since the isa swizzling is paused to recurse the call, then you don't see the new ones until you restore the isa swizzling.
There may be a way to forward the invocation to the original class, without undoing isa swizzling, but I reckon I was too lazy to search for it.
How do I prevent a particular class from being subclassed?
I am not aware of such functionality (say final keyword for example) in the language. However Apple says it has done so for all classes in AddressBookUI.framework (in iOS)
For educational purposes, how can I achieve the same functionality, or how would they have done such thing?
From iOS7 Release Notes(Requires login) :
Here's one way: override allocWithZone: from within your "final" class (substituting MyFinalClassName for your actual class name) like this:
+ (id)allocWithZone:(struct _NSZone *)zone
{
if (self != [MyFinalClassName class]) {
NSAssert(nil, #"Subclassing MyFinalClassName not allowed.");
return nil;
}
return [super allocWithZone:zone];
}
This will prevent a subclass that is not a member of MyFinalClassName from being alloc'ed (and therefore init'ed as well), since NSObject's allocWithZone: must be called eventually, and by refusing to call super from your "final" class, you will prevent this.
There's a simpler way to prevent subclassing in Xcode 6 as a result of Swift interop. To prevent Swift classes from being subclassed in Objective-C the objc_subclassing_restricted is added to all class definitions in the {ProjectName}-Swift.h file.
You can use this in your projects:
#if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted)
# define FOO_FINAL __attribute__((objc_subclassing_restricted))
#else
# define FOO_FINAL
#endif
FOO_FINAL
#interface Foo : NSObject
#end
#interface Bar : Foo
#end
The compiler will halt on the definition of Bar with Cannot subclass a class with objc_subclassing_restricted attribute
Here is possible solution:
#interface FinalClass : NSObject
#end
#implementation FinalClass
- (id)init
{
if (self.class != [FinalClass class]) {
return nil;
}
self = [super init];
if (self) {
// instance initialization
}
return self;
}
#end
#interface InvalidSubclass : FinalClass
#end
#implementation InvalidSubclass
- (id)init
{
self = [super init];
if (self) {
}
return self;
}
#end
I'm not sure this is 100% guaranteed because it's runtime-checking anyway, but it should be enough to block and warn people that they should not subclass this. Subclass might skip superclass's init, but then the instance will not be usable because it's not fully initialised by superclass.
Something like the following will ensure that every time an "impossible subclass" calls +alloc, an object will be allocated that is an instance of FinalClass, and not the subclass. This is essentially what NSObject's +alloc method does, but here we specify an explicit class to create. This is how NSObject allocates instances (in Obj-C 2), but there is no guarantee this will always be the case, so you may want to add an appropriate -dealloc which calls object_dispose. This method also means you don't get a nil object back if you try to instantiate a subclass - you do get an instance of FinalClass.
#interface FinalClass: NSObject
//...
+ (id)alloc; // Optional
#end
// ...
#import <objc/runtime.h>
#implementation FinalClass
+ (id)alloc {
if (![self isMemberOfClass:[FinalClass class]]) {
// Emit warning about invalid subclass being ignored.
}
self = class_createInstance([FinalClass class], 0);
if (self == nil) {
// Error handling
}
return self;
}
#end
#interface InvalidSubclass : FinalClass
// Anything not in FinalClass will not work as +alloc will
// create a FinalClass instance.
#end
Note: I'm not sure I'd use this myself - specifying that a class shouldn't be subclassed is more in the nature of a design-contract with the programmer rather than an enforced rule at compile- or runtime.
Here's a little example of using weak arguments:
#interface MYTestObject : NSObject
#end
#implementation MYTestObject {
void(^_block)(void);
}
- (void)dealloc {
NSLog(#"DEALLOC!");
}
- (id)init {
if (self = [super init]) {
[self doSomethingWithObject:self];
}
return self;
}
- (void)doSomethingWithObject:(id __weak /* <- weak argument! */)obj {
_block = ^{
NSLog(#"%p", obj);
};
}
#end
And it works: -dealloc is called!
Also, if you remove __weak you'll get a retain-cycle and it's absolutely correct.
Wonder, if that's just a side-effect and it's completely unsafe to use weak arguments? Or is it a specified behavior and I'm just a bad google-user?
Two observations:
I'd be inclined to make a local __weak reference within doSomethingWithObject, rather than make it a __weak argument as illustrated in Avoid Strong Reference Cycles when Capturing self.
I don't think that it is, as you asked, "completely unsafe to use weak arguments." But if nothing else, it's the more common pattern to have a local __weak variable and strikes me as more appropriate as an implementation detail of doSomethingWithObject rather than part of the method's public interface.
I'd also make block a property with the copy memory qualifier. As the docs say
You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it's best practice for the property attribute to show the resultant behavior.
Thus:
#interface MYTestObject : NSObject
#property (nonatomic, copy) void(^block)(void);
#end
#implementation MYTestObject
- (void)dealloc {
NSLog(#"DEALLOC!");
}
- (id)init {
if (self = [super init]) {
[self doSomethingWithObject:self];
}
return self;
}
- (void)doSomethingWithObject:(MYTestObject *)obj {
typeof(obj) __weak weakObj = obj;
self.block = ^{
NSLog(#"%p", weakObj);
};
}
#end
I am trying to write a subclass of NSOutputStream to perform a very simple function - keep track of the total number of bytes sent to the stream. However, I am running into an unexpected problem initializing an instance of the function. Here is the code:
#import <Foundation/Foundation.h>
#interface TrackingOutputStream : NSOutputStream {
unsigned long long bytesWritten;
}
#property (readonly) unsigned long long bytesWritten;
#end
---------------------------
#import "TrackingOutputStream.h"
#implementation TrackingOutputStream
#synthesize bytesWritten;
- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)length {
NSInteger written = [super write:buffer maxLength:length];
bytesWritten += written;
return written;
}
#end
However, when I try to initialize this class:
TrackingOutputStream *os = [[[TrackingOutputStream alloc] initToFileAtPath:#"/tmp/test" append:NO] autorelease];
I get the following error:
-[TrackingOutputStream initToFileAtPath:append:]: unrecognized selector sent to instance 0x101a187e0
I've tried adding an explicit constructor to the class that calls super, but it doesn't make any difference (as it shouldn't).
If you're working with an API that expects an instance of NSOutputStream, it can be cumbersome to implement all the methods of NSOutputStream in order to forward them to the wrapped (delegate) instance. You can use method-forwarding approach that will allow you to add behavior without writing all the wrapper methods. This involves writing a simple implementation of forwardingTargetForSelector: and respondsToSelector:
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (class_respondsToSelector([self class], aSelector)) { return self; }
if ([self.delegate respondsToSelector:aSelector]) { return self.delegate; }
return [super forwardingTargetForSelector:aSelector];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
if (class_respondsToSelector([self class], aSelector)) { return YES; }
if ([self.delegate respondsToSelector:aSelector]) { return YES; }
return [super respondsToSelector:aSelector];
}
For a longer, detailed description please see the blog post on using Objective-C duck-typing to more easily subclass NSOutputStream. Or check out the sample on https://github.com/jwb/ObjC-DuckType
NSOutputStream has very specific subclassing requirements that are documented in the class's documentation.
Note that the documentation explicitly states that you must implement the appropriate initializers fully. I.e. you can't subclass to change the behavior as you described. At least, not easily.
Instead, create a class whose instances wrap an instance of NSOutputStream and add the behavior you desire.
As an additional note, it should be possible to retrieve info on the written data using the - propertyForKey: of NSStream. Check out the
NSStreamDataWrittenToMemoryStreamKey property key.
I'm new to the Objective C business (Java developer most of the time) and am woking on my first killer app now. :-)
At the moment I am somehow confused about the usage of selectors as method arguments. They seem to be a little bit different than delegates in C# for example.
Given the following method signature
-(void)execute:(SEL)callback;
is there a way to enforce the signature for the selector passed to such a method?
The method is expecting a selector of a method with the following signature
-(void)foo:(NSData*)data;
But the SEL (type) is generic, so there is a good chance to pass a wrong selector to the
execute method. OK at least at runtime one would see a funny behavior... but I would like to see a compiler warning/error when this happens.
The quick answer is: no, there is no way to have the compiler enforce the method signature of a method selector that is provided via a SEL argument.
One of the strengths of Objective-C is that it is weakly-typed language, which allows for a lot more dynamic behaviour. Of course, this comes at the cost of compile-time type safety.
In order to do what (I think) you want, the best approach is to use delegates. Cocoa uses delegates to allow another class to implement "callback"-type methods. Here is how it might look:
FooController.h
#protocol FooControllerDelegate
#required:
- (void)handleData:(NSData *)data forFoo:(FooController *)foo;
#end
#interface FooController : NSObject
{
id <FooControllerDelegate> * delegate;
}
#property (assign) id <FooControllerDelegate> * delegate;
- (void)doStuff;
#end
FooController.m
#interface FooController (delegateCalls)
- (void)handleData:(NSData *)data;
#end
#implementation FooController
#synthesize delegate;
- (id)init
{
if ((self = [super init]) == nil) { return nil; }
delegate = nil;
...
return self;
}
- (void)doStuff
{
...
[self handleData:data];
}
- (void)handleData:(NSData *)data
{
if (delegate != nil)
{
[delegate handleData:data forFoo:self];
}
else
{
return;
// or throw an error
// or handle it yourself
}
}
#end
Using the #required keyword in your delegate protocol will prevent you from assigning a delegate to a FooController that does not implement the method exactly as described in the protocol. Attempting to provide a delegate that does not match the #required protocol method will result in a compiler error.
Here is how you would create a delegate class to work with the above code:
#interface MyFooHandler <FooControllerDelegate> : NSObject
{
}
- (void)handleData:(NSData *)data forFoo:(FooController *)foo;
#end
#implementation MyFooHandler
- (void)handleData:(NSData *)data forFoo:(FooController *)foo
{
// do something here
}
#end
And here is how you would use everything:
FooController * foo = [[FooController alloc] init];
MyFooHandler * fooHandler = [[MyFooHandler alloc] init];
...
[foo setDelegate:fooHandler]; // this would cause a compiler error if fooHandler
// did not implement the protocol properly
...
[foo doStuff]; // this will call the delegate method on fooHandler
...
[fooHandler release];
[foo release];
To directly answer your question, no, the SEL type allows any type of selector, not just ones with a specific signature.
You may want to consider passing an object instead of a SEL, and document that the passed object should respond to a particular message. For example:
- (void)execute:(id)object
{
// Do the execute stuff, then...
if ([object respondsToSelector:#selector(notifyOnExecute:)]) {
[object notifyOnExecute:self];
}
// You could handle the "else" case here, if desired
}
If you want to enforce the data handling, use isKindOfClass inside your selector. This works a lot like instanceof which you are familiar with in Java.