I have some recursive block code in objective-c that is causing a EXC_BAD_ACCESS error.
- (void) doSomethingWithCompletion:(void (^)())completion {
if (completion) {
dispatch_async(dispatch_get_main_queue(), completion);
}
}
- (void) testBlocks {
NSString *testString = #"hello";
__block NSInteger count = 0;
__block __weak void (^weak_block)(NSString *);
void(^strong_block)(NSString *);
weak_block = strong_block = ^(NSString *str) {
[self doSomethingWithCompletion:^{
NSLog(#"number: %zd", count);
if (++count < 10) {
weak_block(str);
}
}];
};
strong_block(testString);
}
The error happens on weak_block(str) which i assume is because it is released when dispatch_async is called. Calling strong_block(str) in it's place when it's declared with __block like so:
__block void(^strong_block)(NSString *);
Causes a warning 'Capturing 'strong_block' strongly in this block is likely to lead to a retain cycle'.
So I changed the testBlock method to not use a weak reference like so:
- (void) testBlocks {
NSString *testString = #"hello";
__block NSInteger count = 0;
__block void (^inner_block)(NSString *);
void(^strong_block)(NSString *);
inner_block = strong_block = ^(NSString *str) {
[self doSomethingWithCompletion:^{
NSLog(#"number: %zd", count);
if (++count < 10) {
inner_block(str);
}
}];
};
strong_block(testString);
}
But I am unsure if this causes a retain cycle or if adding
__block void (^inner_block)(NSString *) = weak_block;
inside the block instead would cause a retain cycle as well. What is the correct way to handle this situation?
It crashes because the block (pointed to by weak_block, strong_block) has already been deallocated by the time the "completion" block runs, and calling a block with a nil block pointer crashes.
The block is deallocated because there are no strong references to it after testBlocks returns.
The second one will have a retain cycle because the block captures inner_block, which holds a strong reference to itself.
The proper way is to make a strong reference from the captured weak reference inside the block, and let the completion block capture that:
- (void) testBlocks {
NSString *testString = #"hello";
__block NSInteger count = 0;
__block __weak void (^weak_block)(NSString *);
void(^strong_block)(NSString *);
weak_block = strong_block = ^(NSString *str) {
void(^inner_block)(NSString *) = weak_block;
[self doSomethingWithCompletion:^{
NSLog(#"number: %zd", count);
if (++count < 10) {
inner_block(str);
}
}];
};
strong_block(testString);
}
Not sure this is proof of either possibility but if you add a weak block property and examine that one after all block stuff has run...
...
#property (weak) void (^true_weak_block)(NSString *);
#property (weak) NSString *weak_string;
...
- (void) testBlocks {
NSString *strong_string = [NSString stringWithFormat:#"%#", #"some string"]; // note that you can not use a string literal in this example..
self.weak_string = strong_string;
NSString *testString = #"hello";
__block NSInteger count = 0;
__block void (^inner_block)(NSString *);
void(^strong_block)(NSString *);
inner_block = strong_block = ^(NSString *str) {
[self doSomethingWithCompletion:^{
NSLog(#"number: %zd", count);
if (++count < 10) {
inner_block(str);
}
}];
};
self.true_week_block = strong_block;
[self test];
strong_block(testString);
}
- (void)test {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"%#", self.true_week_block); // not deallocated
NSLog(#"%#", self.weak_string); // deallocated
});
}
In my test the block is never deallocated and also the memory address remains the same over time, even if you change regular assignment of the two strong blocks to use copy instead of the implicitly retained assignment.
Related
I have a class with the following method in one of my library's public headers. A user of my class will pass in the selector and class.
-(void)someMethodWithSelector(SEL)aSelector ofClass:(Class)clazz
At compile time, I don't know what the selector will look like, how many parameters will be passed, etc... but what I want is to be able to swizzle the passed selector at runtime, perform some extra logic, and call the original method afterwards.
I know how to swizzle class and instance methods, but I'm unsure how I would proceed given this scenario.
Has anyone had any experience dealing with a similar approach?
MikeAsh
was able to figure out this problem, so all the credit of this answer goes to him
#import Foundation;
#import ObjectiveC;
static NSMutableSet *swizzledClasses;
static NSMutableDictionary *swizzledBlocks; // Class -> SEL (as string) -> block
static IMP forwardingIMP;
static dispatch_once_t once;
void Swizzle(Class c, SEL sel, void (^block)(NSInvocation *)) {
dispatch_once(&once, ^{
swizzledClasses = [NSMutableSet set];
swizzledBlocks = [NSMutableDictionary dictionary];
forwardingIMP = class_getMethodImplementation([NSObject class], #selector(thisReallyShouldNotExistItWouldBeExtremelyWeirdIfItDid));
});
if(![swizzledClasses containsObject: c]) {
SEL fwdSel = #selector(forwardInvocation:);
Method m = class_getInstanceMethod(c, fwdSel);
__block IMP orig;
IMP imp = imp_implementationWithBlock(^(id self, NSInvocation *invocation) {
NSString *selStr = NSStringFromSelector([invocation selector]);
void (^block)(NSInvocation *) = swizzledBlocks[c][selStr];
if(block != nil) {
NSString *originalStr = [#"omniswizzle_" stringByAppendingString: selStr];
[invocation setSelector: NSSelectorFromString(originalStr)];
block(invocation);
} else {
((void (*)(id, SEL, NSInvocation *))orig)(self, fwdSel, invocation);
}
});
orig = method_setImplementation(m, imp);
[swizzledClasses addObject: c];
}
NSMutableDictionary *classDict = swizzledBlocks[c];
if(classDict == nil) {
classDict = [NSMutableDictionary dictionary];
swizzledBlocks[(id)c] = classDict;
}
classDict[NSStringFromSelector(sel)] = block;
Method m = class_getInstanceMethod(c, sel);
NSString *newSelStr = [#"omniswizzle_" stringByAppendingString: NSStringFromSelector(sel)];
SEL newSel = NSSelectorFromString(newSelStr);
class_addMethod(c, newSel, method_getImplementation(m), method_getTypeEncoding(m));
method_setImplementation(m, forwardingIMP);
}
Here is how we would call the function:
int main(int argc, const char * argv[]) {
#autoreleasepool {
Swizzle([NSBundle class], #selector(objectForInfoDictionaryKey:), ^(NSInvocation *inv) {
NSLog(#"invocation is %# - calling now", inv);
[inv invoke];
NSLog(#"after");
});
NSLog(#"%#", [[NSBundle bundleForClass: [NSString class]] objectForInfoDictionaryKey: (__bridge NSString *)kCFBundleVersionKey]);
}
return 0;
}
I have an async method called getCount: which goes to a web URL, counts some stuff, and invokes a callback with the count when it's done.
I have another method which is synchronous and needs to take those results, puts them into a message, and returns that message. Here are the two together:
- (NSString *)describe {
__block bool gotCount = NO;
[self getCount:^(int count) {
NSLog(#"Got the count: %i", count);
_count = count; // _count is an ivar of the object with this method.
gotCount = YES;
}];
// Pause here until the count has been fetched.
while (!gotCount) {
[NSThread sleepForTimeInterval:0.05];
}
return [NSString stringWithFormat:#"The count is %i", _count];
}
My callback is never called when I try this. It never prints
Got the count 0
or any other value for count in this scenario.
If I comment out the while loop, that message does get printed out. So I know that the getCount: method works, there's just something wrong with my loop waiting for it to arrive.
I need getCount: to remain asynchronous (there's other places it gets used where that's more important) and I need describe to remain synchronous. How can I handle this?
one possible thing: if your describe method is in the main thread then you call getCount method also from the main thread and all web callbacks are in the main thread. BUT you block the main thread with the thread sleep -> you can not get call back from the web to get a count.
Edited:
try to call getCount method from another thread. use e.g.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self getCount:^(int count) {
NSLog(#"Got the count: %i", count);
_count = count; // _count is an ivar of the object with this method.
gotCount = YES;
}];
});
Edited 2:
I tried this code and it works fine -> something probably wrong with the threads in your getCount method.
- (NSString *)describe {
__block bool gotCount = NO;
__block NSInteger _count;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:5.00];
_count = 5;
gotCount = YES;
});
// Pause here until the count has been fetched.
while (!gotCount) {
[NSThread sleepForTimeInterval:0.05];
}
return [NSString stringWithFormat:#"The count is %li", _count];
}
a way that would work but is QUITE the hack (which apple employed in older APIs on the mac all the time) would be to run the runloop while waiting:
note: this relies on the callback being on the same queue as the describe method
see: JSON async request [SAME ISSUE]
a self contained WORKING example:
#import <Foundation/Foundation.h>
#interface T : NSObject
- (NSString *)describe;
#end
#implementation T {
int _count;
}
- (void)getCount:(void (^)(int c)) handler {
dispatch_async(dispatch_get_global_queue(0,0), ^ {
sleep(5);
dispatch_sync(dispatch_get_main_queue(), ^{
handler(55);
});
});
}
- (NSString *)describe {
__block bool gotCount = NO;
[self getCount:^(int count) {
NSLog(#"Got the count: %i", count);
_count = count; // _count is an ivar of the object with this method.
gotCount = YES;
}];
// Pause here until the count has been fetched.
while (!gotCount) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
return [NSString stringWithFormat:#"The count is %i", _count];
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
T *t = [T new];
NSLog(#"describe: %#", [t describe]);
}
}
At runtime I need to be able to get the argument types of a method. The following is what gets printed:
I have read on other threads that at run-time time objective c treats all objects passed to a method as arguments as id. If this approach doesn't work any other suggestions on a way to read argument types?
Log
2014-02-07 15:47:08.962 OCInjection[55727:70b] #
2014-02-07 15:47:08.964 OCInjection[55727:70b] :
Code
Class class = NSClassFromString(injectionBinding);
unsigned int methodCount;
Method *methodList = class_copyMethodList(class, &methodCount);
for (int i = 0; i < methodCount; i++)
{
Method method = methodList[i];
SEL selector = method_getName(method);
NSMethodSignature *signature = [class instanceMethodSignatureForSelector:selector];
NSUInteger numberOfArguments = [signature numberOfArguments];
for (int i=0 ; i<numberOfArguments ; i++)
{
NSString *type = [NSString stringWithUTF8String:[signature getArgumentTypeAtIndex:i]];
NSLog(type);
}
}
According to
-getArgumentTypeAtIndex:
and
Decode Class from #encoded type string
I think there is no method to get the "real" argument type.
Doesn't seem like it's possible to do this. I ended up using a proxy object to send the message to, and capture it. Probably not the ideal way, but I haven't found a better solution.
#interface DIContructorInjectorProxy()
#property (nonatomic, strong) id realObject;
#end
#implementation DIContructorInjectorProxy
#define Inject(x) [DIContructorInjectorProxy _injectMacro:x]
- (id)initWithClass:(Class)class
{
self.realObject = [[class alloc] init];
}
+ (id)_injectMacro:(id)x
{
if ([x isKindOfClass:NSClassFromString(#"Protocol")])
return NSStringFromProtocol(x);
else
return NSStringFromClass(x);
}
- (id)withConstructor
{
// Just making the method call for defining a constructor more readable by a call to this method first
return self;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSMutableString *selectorName = [NSStringFromSelector(anInvocation.selector) mutableCopy];
NSUInteger numberOfColonsInMethodName = [selectorName replaceOccurrencesOfString:#":"
withString:#":"
options:NSLiteralSearch
range:NSMakeRange(0, selectorName.length)];
[anInvocation retainArguments];
NSMutableArray *argumentsPassedToSelector = [NSMutableArray array];
for (int i=2 ; i<numberOfColonsInMethodName+2 ; i++)
{
NSString *argument;
[anInvocation getArgument:&argument atIndex:i];
[argumentsPassedToSelector addObject:[NSString stringWithFormat:#"%#", argument]];
}
// Store arguments somewhere
return;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [self.realObject methodSignatureForSelector:aSelector];
}
#end
How the user uses this to define method arguments
[self bindProtocol:#protocol(DataStorage) toClass:[InMemoryDataStorage class]];
// withConstructor returns an appropriate proxy object
// Then when the init method is called, it calls forwardInvocation,
// and from there I save all the info I need about the method and arguments
(void)[[[self bindProtocol:#protocol(GoogleClient) toClass:[GoogleClientEngine class]] withConstructor]
initWithDataStorage:Inject(#protocol(DataStorage))];
Am getting a "Missing context for method declaration" for my overridden description method. Can you tell what's wrong with the code?
#import <Foundation/Foundation.h>
#import "BNRItem.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
// Create a mutable array object, store its address in items variable
NSMutableArray *items = [[NSMutableArray alloc]init];
BNRItem *p = [[BNRItem alloc]init];
NSLog(#"%# %# %# %d", [p itemName], [p dateCreated], [p serialNumber], [p valueInDollars]);
// This creates a new NSString, "Red Sofa" and gives it to the BNRItem
[p setItemName:#"Red Sofa"];
// This creates a new NSString, "A1B2C" and gives it to the BNRItem
[p setSerialNumber:#"A1B2C"];
// We send the value 100 to be used as the valueInDollars of this BNRItem
[p setValueInDollars:100];
// Destroy the array pointed to by items
items = nil;
}
return 0;
}
-(NSString *)description // Missing context for method declaration
{
NSString *descriptionString =
[[NSString alloc]initWithFormat:#"%# (%#): Worth $%d, recorded on %#",
itemName;
serialNumber;
valueInDollars;
dateCreated];
return descriptionString;
}
BNRItem.m
#import "BNRItem.h"
#implementation BNRItem
-(void)setItemName:(NSString *)str {
itemName = str;
}
-(NSString *)itemName {
return itemName;
}
-(void)setSerialNumber:(NSString *)str {
serialNumber = str;
}
-(NSString *)serialNumber {
return serialNumber;
}
-(void)setValueInDollars:(int)i {
valueInDollars = i;
}
-(int)valueInDollars {
return valueInDollars;
}
-(NSDate *)dateCreated {
return dateCreated;
}
-(NSString *)description
{
NSString *descriptionString =
[[NSString alloc]initWithFormat:#"%# (%#): Worth $%d, recorded on %#",
itemName,
serialNumber; // Expected "]"
valueInDollars, // Expression result unused
dateCreated]; //Extraneous "]" before ";"
return descriptionString;
}
#end
Your method appears to be free floating inside main.m. An instance method needs to be placed inside the implementation section of a class. (between #implementation and #end).
My guess is that you should move that code into BNRItem.m.
If you have something like a char at the veryfirst beginning of your Header oder .m file, its also likely that this error accures.
^//
// EMServices.m
// MyController
//
// Created by EMart on 09.01.14.
// Copyright (c) 2014 EMart. All rights reserved.
//
I am currently using the pragmatic screencast on Objective-C to help me program in objective-c. I have a background in Java and C++, but I am having a very difficult time getting used to everything in Objective(Mostly because I am not comfortable with the syntax).
Below is the error I am receiving with all the code.
I am also getting a warning in movie.m class as well: Wirtable atomic property 'title'
cannot be pair a synthesized setter/getter with a user defined setter/getter
thanks for your help.
I am receive this error
Current language: auto; currently objective-c
warning: Couldn't find class validation function, calling methods on uninitialized objects may deadlock your program.
Program received signal: “EXC_BAD_ACCESS”.
I ran it through the debugger and the address of movie in the code below is in red
main.m
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Movie *movie = [[Movie alloc] initWithTitle:#"iron man"
andRating:5
andYear:2008];
[movie play];
NSLog(#"our movie is %#", movie);
[pool drain];
return 0;}
Movie.h
interface Movie : NSObject {
NSString *title;
int rating;
int year;
}
- (id)initWithTitle:(NSString *)newTitle
andRating:(int)newRating
andYear:(int) year;
#property(assign) NSString *title;
#property(assign) int rating;
#property(assign) int year;
-(void) play;
#end
Movie.m
#import "Movie.h"
#implementation Movie
#synthesize title;
#synthesize rating;
#synthesize year;
-(id)initWithTitle:(NSString *)newTitle
andRating:(int)newRating
andYear:(int)newYear;
{
self = [super init];
if(nil != self){
self.title = newTitle;
self.rating = newRating;
self.year = newYear;
}
return self;
}
-(NSString *) description{
NSString *oldDescription = [super description];
return [NSString stringWithFormat: #"%# title =%#, rating =%d year=%#",
oldDescription, self.title, self.rating, self.year];
}
- (void)setTitle:(NSString *)newTitle {
title = [newTitle capitalizedString];
}
-(void) play {
NSLog(#"Playing %#", self);
}
You use year=%# when it should be year=%d.
Some more random thoughts:
You should retain or better even copy the title instead of assigning it.
The init method should be named
-(id)initWithTitle:(NSString *)aTitle
rating:(int)aRating
year:(int)aYear;
Don't forget a dealloc method then.
Your title property is an object type and so should in generally be either retain or copy -- in the case of NSString properties, it is traditional to use copy to avoid issues when you're passed an NSMutableString instead.
#property (copy) NSString* title;
Since you explicitly define the setter, you then need to implement this policy yourself, something like this:
- (void)setTitle:(NSString *)newTitle
{
[title release];
title = [[newTitle capitalizedString] copy];
}
You'll also need to include a dealloc method to clean up:
- (void) dealloc
{
[title release];
[super dealloc];
}