Xcode debugger steps over methods that execute via forwardInvocation - ios

I noticed the following behavior when using OCMock, and after poking around I discovered it is an issue with how the Xcode debugger handles methods that are not implemented. Here's an example:
#implementation ForwardingClass
- (id)init {
self = [super init];
self.delegate = [[DelegateClass alloc] init];
return self;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:self.delegate];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.delegate methodSignatureForSelector:sel];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return YES;
}
#end
#implementation DelegateClass
- (void)forwardedMethod {
NSLog(#"got it!");
}
#end
Now we can call it:
ForwardingClass *forwardingClass = [[ForwardingClass alloc] init];
// Xcode will not step into this method
[(id)forwardingClass forwardedMethod];
Xcode will not step into the forwardedMethod. Even worse, if I set a breakpoint in forwardInvocation it won't break there. The net effect is that you can't step into any object that has been partially mocked by OCMock.
Any ideas how to fix this?
Is it a bug (or unintended "feature") in Xcode?
I've tried this on both Xcode 6 and Xcode 7 beta 4.

Related

How to call block defined as property in another class?

I am calling the block from second class which has been declared and maintained in first class.
In ViewController.h
#property (copy) void (^simpleBlock)(NSString*);
In View Controller.m
- (void)viewDidLoad {
[super viewDidLoad];
self.simpleBlock = ^(NSString *str)
{
NSLog(#"Hello My Name is: %#",str);
};
}
In SecondViewController.m
In ViewDidload
ViewController *VC = [[ViewController alloc]init];
VC.simpleBlock(#"Harjot");//bad execution error
Please suggest me some solutions because the code is giving me bad execution error.
How can i call the block in any another way?
It's the correct way of run the block. However if you try to run a block that is nil you'll have a crash - so you should always check that it's not nil before calling it:
ViewController *vc = [[ViewController alloc] init];
if (vc.simpleClock) {
vc.simpleBlock(#"Harjot");//this will not get called
}
The reason why in your case the block is nil is because you set it in viewDidLoad - however viewDidLoad is not called until its view is ready to go on screen. For testing purposes try to move the assignment from viewDidLoad to init and this should work:
- (instancetype)init
{
self [super init];
if (self) {
_simpleBlock = ^(NSString *str)
{
NSLog(#"Hello My Name is: %#",str);
};
}
return self;
}

Work with private Speech Recognition Frameworks on iOS 8

I need to add Speech Recognition to an App for a Personal Project.
I need the iOS built-in speech recognition framework because it is fast, accurate and it can also recognise your contact names and other information about yourself.
So far, I think I have found the framework which contains the headers for the speech recognition on iOS 8: the SAObjects.framework
I got the headers of Github and added them successfully in my Xcode Project.
The headers I have tried so far are these:
<SAObjects/SASRecognition.h>
<SAObjects/SASStartSpeechDictation.h>
<SAObjects/SASSpeechRecognized.h>
However, I am not sure how to work with them. For instance, these are two possible methods that can fire a Speech Recognition:
SASStartSpeechDictation *object1 = [SASStartSpeechDictation startSpeechDictation];
SASSpeechRecognized *object2 = [SASSpeechRecognized speechRecognized];
When I debug it though, I cannot find any string in any of these objects. So, obviously something is wrong. Maybe I need to set a notification observer?
Another Solution could be to start a Dictation (through the Keyboard)
to a hidden text field (without the keyboard showing).
Like the Activator action for Jailbroken devices, if you are familiar with it.
But I haven't found any methods that can start the Keyboard dictation, or the Activator action source code to find it out.
Maybe someone has experimented with these things and can give me some help?
Please tell me if you need more information about this question :)
Thanks a lot!
So, I managed to find an answer myself. I luckily found a Github repo with some helpful code: https://github.com/erica/useful-things
The code I found is under the appstore unsafe pack/DictationHelper directory. This code helps to use the UIDictationController and start and stop the Dictation, and get the text value. Of course, without any Text Fields...
Important: In order for this to work, you need to have the headers of the UIKit framework, link the framework to the Target and import them in the Project!
However, I modified the code a bit, because the sample code is only available to speak for a specific duration. I needed to stop speaking by pressing a button.
This is the modified code, for anyone who might be interested in the future:
DicationHelper.h:
/*
Erica Sadun, http://ericasadun.com
NOT APP STORE SAFE BUT HANDY
Siri-ready devices only. Will not work in simulator.
Example:
[SpeechHelper speakModalString:#"Please say something"];
[[DictationHelper sharedInstance] dictateWithDuration:5.0f completion:^(NSString *dictationString) {
if (dictationString)
NSLog(#"You said:'%#'", dictationString);
else
NSLog(#"No response");}];
//-> OR: (My modification)
[SpeechHelper speakModalString:#"Please say something"];
[[DictationHelper sharedInstance] startDictation:0 completion:^(NSString *dictationString) {
if (dictationString)
NSLog(#"You said:'%#'", dictationString);
else
NSLog(#"No response");}];
// Then you need to call this to stop the Dictation: [[DictationHelper sharedInstance] stopDictation]
*/
#import <UIKit/UIKit.h>
//#import <Foundation/Foundation.h>
extern NSString *const DictationStringResults;
typedef void (^DictationBlock)(NSString *dictationString);
#interface DictationHelper : NSObject
+ (instancetype) sharedInstance;
- (void) dictateWithDuration: (CGFloat) duration;
- (void) dictateWithDuration: (CGFloat) duration completion:(DictationBlock) completionBlock;
-(void) startDictation:(CGFloat) whatever completion:(DictationBlock) completionBlock;
-(void) stopDictationWithFallback;
#property (nonatomic, readonly) BOOL inUse;
#end
DictationHelper.m
/*
Erica Sadun, http://ericasadun.com
NOT APP STORE SAFE BUT HANDY
Siri-ready devices only. Will not work in simulator.
*/
#import "DictationHelper.h"
#define MAKELIVE(_CLASSNAME_) Class _CLASSNAME_ = NSClassFromString((NSString *)CFSTR(#_CLASSNAME_));
NSString *const DictationStringResults = #"Dictation String Results";
static DictationHelper *sharedInstance = nil;
#class UIDictationController;
#interface UIDictationController
+ (UIDictationController *) sharedInstance;
- (void) startDictation;
- (void) stopDictation;
- (void) preheatIfNecessary;
#end;
#interface DictationHelper () <UITextFieldDelegate>
#end
#implementation DictationHelper
{
UITextField *secretTextField;
id dictationController;
DictationBlock completion;
BOOL handled;
}
- (void) preheat
{
if (!secretTextField)
{
secretTextField = [[UITextField alloc] initWithFrame:CGRectZero];
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
[window addSubview:secretTextField];
secretTextField.inputView = [[UIView alloc] init];
secretTextField.delegate = self;
}
if (!dictationController)
{
MAKELIVE(UIDictationController);
dictationController = [UIDictationController sharedInstance];
[dictationController preheatIfNecessary];
}
}
+ (instancetype) sharedInstance
{
if (!sharedInstance)
{
sharedInstance = [[self alloc] init];
[sharedInstance preheat];
}
return sharedInstance;
}
- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *tftext = textField.text;
tftext = [tftext stringByReplacingCharactersInRange:range withString:string];
[[NSNotificationCenter defaultCenter] postNotificationName:DictationStringResults object:tftext];
if (completion) completion(tftext);
// Treat this dictation as handled
handled = YES;
_inUse = NO;
completion = nil;
// Resign first responder
[textField resignFirstResponder];
return YES;
}
- (void) fallback
{
// 1. Test completion
if (!completion) return;
// 2. Check for handled
if (handled)
{
_inUse = NO;
handled = NO;
return;
}
// 3. Assume the dictation didn't work
completion(nil);
// 4. Reset everything
handled = NO;
_inUse = NO;
completion = nil;
// 5. Resign first responder
[secretTextField resignFirstResponder];
}
-(void) startDictation:(CGFloat) whatever completion:(DictationBlock) completionBlock{
if (completionBlock) completion = completionBlock;
if (_inUse)
{
NSLog(#"Error: Dictation Helper already in use");
return;
}
_inUse = YES;
handled = NO;
secretTextField.text = #"";
[secretTextField becomeFirstResponder];
[[UIDevice currentDevice] playInputClick];
[dictationController startDictation];
}
- (void) dictateWithDuration: (CGFloat) numberOfSeconds
{
if (_inUse)
{
NSLog(#"Error: Dictation Helper already in use");
return;
}
_inUse = YES;
handled = NO;
secretTextField.text = #"";
[secretTextField becomeFirstResponder];
[[UIDevice currentDevice] playInputClick];
[dictationController startDictation];
[self performSelector:#selector(stopDictation) withObject:nil afterDelay:numberOfSeconds];
[self performSelector:#selector(fallback) withObject:nil afterDelay:numberOfSeconds + 1.0f];
}
- (void) dictateWithDuration: (CGFloat) duration completion:(DictationBlock) completionBlock
{
if (completionBlock) completion = completionBlock;
[self dictateWithDuration:duration];
}
- (void) stopDictation
{
[dictationController stopDictation];
}
- (void) stopDictationWithFallback
{
[self performSelector:#selector(stopDictation) withObject:nil afterDelay:0.0];
[self performSelector:#selector(fallback) withObject:nil afterDelay:1.0f];
}
#end
#undef MAKELIVE

Login flow failing after upgrading to iOS9

After upgrading my app to iOS9 I’am getting an error in my app which says:
: objc[344]: Cannot form weak reference to instance (0x15919e00) of class LoginVC. It is possible that this object was over-released, or is in the process of deallocation.
Below is the function in which i get this error:
-(void)dismissLogin {
self.isLoggingIn = NO;
[self stopLoginAnimation];
[self dismissViewControllerAnimated:YES completion:NO];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self.appDelegate setLoginVC:nil];
[self.view removeFromSuperview];
//[APPDEL selectTabBar];
}
The app gets stuck at the login screen and doesn't switch to next screens.
This error doesn’t come in iOS8.
Can anyone help me with this issue.
Make sure you are not using instance being deallocated.
I have the same issue. It was not occurring in iOS 8 but occurred in iOS 9. Because I was overriding setDelegate method like this.
-(void)setDelegate:(id<UICollectionViewDelegate>)delegate{
_internalDelegate = delegate;
[super setDelegate:self];
}
So in iOS 9, OS sets delegate to nil on de-allocation, but I was setting it to self. So quick fix was
-(void)setDelegate:(id<UICollectionViewDelegate>)delegate{
_internalDelegate = delegate;
if (delegate) {
//Set delegate to self only if original delegate is not nil
[super setDelegate:self];
}else{
[super setDelegate:delegate];
}
}
I ran into this issue recently and this helped me come to the conclusion that I did. The only issue I have with the solution provided above is that if you need the subclass to gain functionality even when its internalDelegate is nil, it just won't work.
Here's the solution I came up with that both prevents the crash and allows functionality to exist even with a nil internalDelegate. Figured I'd share in case anyone else came across this.
Create a second internal property, I called this weakSelf
#property (nonatomic, weak) LoginVC *weakSelf;
Inside any initialization methods, set weakSelf to self
- (id)init {
if ((self = [super init])) {
self.weakSelf = self;
}
}
Update delegate method
- (void)setDelegate:(id)delegate {
_internalDelegate = delegate;
[super setDelegate:self.weakSelf];
}

Common Initializer for Objective-C Class

I've been programming in Objective-C for years now, and I always bump into this problem: If you have an object with multiple initializers, but there is code in common to all of them, how do you extract it out?
The best I've been able to come up with is something like this:
#implementation Example
- (void)privateInitWithString:(NSString*)aString
{
self.str = aString;
}
- (id)init
{
self = [super initWithWindowNibName:#"MyNib"]
if(self) {
[self privateInitWithString:#""];
}
return self;
}
- (id)initWithString:(NSString*)aString
{
self = [super initWithWindowNibName:#"MyNib"]
if(self) {
[self privateInitWithString:aString];
}
return self;
}
#end
There is a lot of duplication in the individual initializers which a code smell. However I can't think of a way to get one initializer to "fall through" to another one because there is no guarantee that self has been set before calling [super init]
Is there a best practice for this that I'm missing?
You write one "designated initialiser". That initialiser handles all the different situations. In your case, initWithString seems a good candidate. And init just becomes
- (instancetype)init { return [self initWithString:#""]; }
You can read more about it here:
https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/Initialization/Initialization.html

CALayer delegation causes zombie crash -- why?

I am new to Core Animation and having trouble implementing a CALayer object with the drawLayer method in a delegate.
I have narrowed the problem down to a very simple test. I have a main viewController named LBViewController that pushes a secondary viewController called Level2ViewController. In the level 2 controller, in viewWillAppear:, I create a CALayer object with it's delegate=self (i.e. the level 2 controller). Whether or not I actually implement the drawLayer:inContext: method I have the same problem -- when I return to the main viewController I get a zombie crash. In the profiler it appears that the object in trouble is the level 2 viewController object -- which is being dealloc'ed after it's popped.
I've tried using a subclassed CALayer object instead of the delegate and it works fine. If I comment out the delegate assignment it also runs fine. I would like to understand why delegation is causing this problem. Any advice is greatly appreciated.
Here's my code ---
Level2ViewController
#implementation Level2ViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CALayer *box1 = [[CALayer alloc] init];
box1.delegate = self; // problem disappears if I comment out this assignment
box1.backgroundColor = [UIColor redColor].CGColor;
box1.frame = CGRectMake(10,10,200,300);
[self.view.layer addSublayer:box1];
[box1 setNeedsDisplay];
}
// makes no difference whether or not this method is defined as long
// as box1.delegate == self
- (void)drawLayer:(CALayer *)theLayer inContext:(CGContextRef)theContext
{
CGContextSaveGState(theContext);
CGContextSetStrokeColorWithColor(theContext, [UIColor blackColor].CGColor);
CGContextSetLineWidth(theContext, 3);
CGContextAddRect(theContext, CGRectMake(5, 5, 40, 40));
CGContextStrokePath(theContext);
CGContextRestoreGState(theContext);
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
The method in LBViewController (the main controller) that pushes the level 2 view controller
- (IBAction)testAction:(id)sender {
Level2ViewController *controller = [[Level2ViewController alloc]
initWithNibName:#"Level2ViewController" bundle:nil];
controller.title = #"Level2";
// this push statement is where the profiler tells me the messaged zombie has been malloc'ed
[self.navigationController pushViewController:controller animated:YES];
[controller release];
}
You may want to set the layer's delegate to nil before the delegate object is released. So in your Leve2ViewController do this:
-(void)viewWillDisappear:(BOOL)animated
{
if (box1) {
box1.delegate = nil;
}
box1 = nil;
}
Obviously this requires, that box1 is turned into a field (so it is accessible in viewWillDisappear:)
Since you create box1in viewWillAppear: the code above uses viewWillDisappear:. Recently, when I ran into a similar problem, I had a separate delegate object in which I used init and dealloc.
Note: You call [super viewDidAppear:animated]; in viewWillAppear. Looks like a typo or copy/paste glitch :-)

Resources