Is it possible to intercept all method class on any UIViewController subclass - ios

Let's say I wanted to be able to intercept any method calls to a UIViewController subclass.
First of all, I swizzle the +(instancetype)alloc method and I check if the current instance isKindOfClass:[UIViewController class]. If it is I go ahead and instantiate my proxy with the target.
///swizzled Alloc
+ (instancetype)monitoredAlloc {
id obj = [self monitoredAlloc];
if([obj isKindOfClass:[UIViewController class]]) {
id proxy = [PMGProxy proxyWithObject:obj];
return proxy;
}
return [self monitoredAlloc];
}
---------------------------------------
/// Proxy class
#implementation PMGProxy
+ (instancetype)proxyWithObject:(id)obj {
PMGProxy *proxy = [self alloc];
proxy.obj = obj;
return proxy;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation setTarget:_obj];
[invocation invoke];
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.obj methodSignatureForSelector:sel];
}
- (Class)class {
return [self.obj class];
}
The problem is that I get crashes, so I would expect the implementation of my Proxy is wrong... What am I doing wrong?
Here is the exception:
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder:'

From the error it seems that the coder is only happy to accept a different class returned from initCoder: rather than earlier in the process at the alloc stage.
Might be worth looking up NSSecureCoding for more detail on the whole process.
While you’re at it, take a look at the stack track that resulted in your exception, it will give you a bit more perspective on just how deep this rabbit hole goes.

Related

Attempting to segue from Objective-C to Swift VC

i'm trying to segue from objective-c to swift.
However when I try this using the object below I receive the following error
I don't know what i'm doing wrong, i've setup the segue on the storyboard and assigned it to the same ID, created the prepare function and the perform.
- (void)renderer:(id<SCNSceneRenderer>)renderer willRenderScene:(SCNScene *)scene atTime:(NSTimeInterval)time {
// Look for trackables, and draw on each found one.
size_t trackableCount = trackableIds.size();
for (size_t i = 0; i < trackableCount; i++) {
// NSLog(#"this is the variable value: %d", trackableIds[i]);
// Find the trackable for the given trackable ID.
ARTrackable *trackable = arController->findTrackable(trackableIds[i]);
// SCNCamera *camera = self.cameraNode.camera;
SCNNode *trackableNode = self.trackableNodes[i];
if (trackable->visible) {
if (trackableIds[i] == 0) {
NSLog(#"Starbucks");
[self performSegueWithIdentifier:#"brandSegue" sender:self];
} else if (trackableIds[i] == 1) {
NSLog(#"Dortios");
}
} else {
trackableNode.opacity = 0;
}
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"brandSegue"]) {
JSONViewController *destViewController = segue.destinationViewController;
}
}
EDIT - ERROR MESSAGE
2017-11-09 16:57:05.232992+0000 MyARApp[2573:1099927] *** Assertion failure in -[UIApplication _cachedSystemAnimationFenceCreatingIfNecessary:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3698.21.8/UIApplication.m:1707
2017-11-09 16:57:05.233169+0000 MyARApp[2573:1099927] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'accessing _cachedSystemAnimationFence requires the main thread'
*** First throw call stack:
(0x186f51d04 0x1861a0528 0x186f51bd8 0x1878e1c24 0x1905e9c4c 0x19064646c 0x190432338 0x19038fe5c 0x1903fb6a8 0x190470bf0 0x1906ffb00 0x190701434 0x190703cd8 0x19070420c 0x190703c28 0x190467ab4 0x190707ae4 0x190b38854 0x190ca6b30 0x190ca69d4 0x1906f7e18 0x1020a27cc 0x19a66c610 0x19a725a84 0x19a723e00 0x19a724d1c 0x19a5a0cc4 0x19a6717ac 0x19a671b14 0x19a671f8c 0x19a71a67c 0x19a5d24a0 0x19a6e1c90 0x1035b949c 0x1035b945c 0x1035c8110 0x1035bc9a4 0x1035c9104 0x1035d0100 0x186b7afd0 0x186b7ac20)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
After discussion in Chat and fixing various issues, I'll take them one by one:
> *** Assertion failure in -[UIApplication _cachedSystemAnimationFenceCreatingIfNecessary:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3698.21.8/UIApplication.m:1707
> *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'accessing _cachedSystemAnimationFence requires the main thread'
That's the first issue causing a crash. This is talking about an internal method of CocoaTouch needed to be called in main thread.
The issue lies on your performSegueWithIdentifier:sender:. All UI related calls have to be done in main thread.
To fix it:
dispatch_async(dispatch_get_main_queue(), ^(){
[self performSegueWithIdentifier:#"brandSegue" sender:self];
});
Fixing this revealed a second issue:
it segues but it trigger twice, do you know why this may happen?
You are doing this:
for (size_t i = 0; i < trackableCount; i++)
{
if (somethingTest)
{
[self performSegueWithIdentifier:#"brandSegue" sender:self];
}
}
Who said that in your for loop you don't valid multiple times somethingTest?
To fix it (I'm talking about the logic, I didn't do the dispatch_async(dispatch_get_main_queue(){()} part to avoid adding noise to the algorithm).
//Declare a var before the for loop
BOOL seguedNeedsToBeDone = FALSE;
for (size_t i = 0; i < trackableCount; i++)
{
if (somethingTest)
{
seguedNeedsToBeDone = TRUE;
}
}
//Perform the segue after the for loop if needed
if (seguedNeedsToBeDone)
{
[self performSegueWithIdentifier:#"brandSegue" sender:self];
}
Next issue, passing data to the Destination ViewController:
JSONViewController *destViewController = segue.destinationViewController;
destViewController.brand = #"something";
You are mixing Swift & Objective-C, since XCode was complaining about not knowing brand being a property of JSONViewController object, you needed to add #objc before the declaration of the var. More detailed answer can be found here.
Finally, a tip to pass the data of you for loop is using the sender (it's faster in term of coding than creating another var, etc.):
//Calling the performSegue with custom value to pass
[self performSegueWithIdentifier:#"brandSegue" sender:someVarToSend];
//Passing the custom value
destViewController.brand = someVarToSend;
I guess the more safe way is to use navigationController?.pushViewController(_:animated:), instead of using segues.

Objective-C: Calling selectors with variable arguments

I'm facing the following problem and I already tried a lot. I have also read the others Questions in Stackoverflow like:
Objective-C: Calling selectors with multiple arguments
and the Cocoa Core Competencies about Selectors, but I'm searching for the best way to pass a variable of arguments to a selector.
-(void) runAllStatusDelegates : (SEL)selector
{
for (NSValue *val in self.statusDelegates)
{
id<StatusDelegate> delegate = val;
if ([delegate respondsToSelector:selector])
{
[delegate performSelector:selector];
}
}
}
This method is responsible to call the methods inside the delegates. The parameter is a Selector. My Problem is that the selector can have 0 - 3 arguments, as shown below.
-(void) handleBluetoothEnabled:(BOOL)aEnabled
{
if (aEnabled)
{
[self.statusDelegate bluetoothEnabled];
if (_storedPenSerialNumber != nil && ![_storedSerialNumber isEqual:kUnknownPenID])
{
[self runAllStatusDelegates: #selector(penConnected : _storedSerialNumber : _storedFirmware:)];
}
}
else
{
[self.statusDelegate bluetoothDisabled];
}
}
-(void) handleChooseDevice:(BluetoothDeviceList*)aDevices
{
NSLog(#"Handle Choose Device");
[self runAllStatusDelegates: #selector(chooseDevice:aDevices:)];
}
-(void) handleDiscoveryStarted
{
NSLog(#"Discovery Started");
[self runAllStatusDelegates: #selector(searchingForBluetoothDevice)];
[self.statusDelegate handleStatus:#"Searching for your digipen"];
}
This implementation isn't working because the performSelector is not recognizing the selector.
I also tried to implement it with #selector(penConnected::) withObject:_storedSerialNumber but then I have to implement another method with additional arguments as well and I don't want that.
I'm new to objective-c so I'm not so familiar with all possibilities.
My idea is to pass a String and an Array of arguments to runAllStatusDelegates and build up the selector inside that method, but is this the best way or are there more convenient ways?
I am personally not a fan of NSInvocation for complex signatures. Its really great for enqueueing a simple function call on a queue and running it when you need it but for your case, you know the selector so you don't really need to go the invocation route. I typically find invocations are more useful if you don't actually know the selector you want to call at compile time, maybe its determined by your API etc.
So what I would do is simply pass a block into your runAllStatusDelegates method that will execute against all your delegates:
- (void)performSelector:(SEL)selector againstAllDelegatesWithExecutionBlock:(void (^)(id<StatusDelegate>))blockToExecute
{
for (id<StatusDelegate> delegate in self.statusDelegates)
{
if ([delegate respondsToSelector:selector])
{
blockToExecute(delegate);
}
}
}
Then when you want to call your delegates with a function it looks like this:
[self performSelector:#selector(handleAnswerOfLifeFound)
againstAllDelegatesWithExecutionBlock:^(id<StatusDelegate> delegate){
[delegate handleAnswerOfLifeFound];
}];
I guess the only downside might be that you could change the selector and pass a different function into the block. How I would solve this is by actually making sure not all methods are optional, or if they are optional to make the actual check inside the block, this would clean up the signature:
- (void)callAllDelegatesWithBlock:(void (^)(id<StatusDelegate>))blockToExecute
{
for (id<StatusDelegate> delegate in self.statusDelegates)
{
blockToExecute(delegate);
}
}
and then your actual usage for an optional method:
[self callAllDelegatesWithBlock^(id<StatusDelegate> delegate){
if([delegate respondsToSelector:#selector(handleAnswerOfLifeFound)]){
[delegate handleAnswerOfLifeFound];
}
}];
Still error-prone but at least a bit tidier.
You can use NSInvocation for this case
SEL theSelector = #selector(yourSelector:);
NSMethodSignature *aSignature = [NSMethodSignature instanceMethodSignatureForSelector:theSelector];
NSInvocation *anInvocation = [NSInvocation invocationWithMethodSignature:aSignature];
[anInvocation setSelector:theSelector];
[anInvocation setTarget:self];
[anInvocation setArgument:&arg1 atIndex:2];
[anInvocation setArgument:&arg2 atIndex:3];
[anInvocation setArgument:&arg3 atIndex:4];
[anInvocation setArgument:&arg4 atIndex:5];
//Add more
Note that the arguments at index 0 and 1 are reserved for target and selector.
For more info http://www.cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html
you can binding the arguments to the selector
NSDictionary *argInfo=#{#"arg1":arg1,#"arg2":arg2,...};
objc_setAssociatedObject(self,#selector(chooseDevice:aDevices:),argInfo,OBJC_ASSOCIATION_COPY)
[self runAllStatusDelegates: #selector(chooseDevice:aDevices:)];
then in the
-(void) runAllStatusDelegates : (SEL)selector
{
for (NSValue *val in self.statusDelegates)
{
id<StatusDelegate> delegate = val;
if ([delegate respondsToSelector:selector])
{
NSDictionary *argInfo=objc_getAssociatedObject(self, selector);
//call the fun use arginfo
}
}
}

Unrecognized selector sent to instance on if(bool)

I know this questions has been posted a lot, but they are all very specific and do not apply to my problem.
[MirrorStarAV startRecording:]: unrecognized selector sent to instance 0x15561d60
I get this error when calling the following method:
- (bool) startRecording {
bool result = NO;
#synchronized(self) {
if (!_recording) { //Exception is raised on this line
result = [self setUpWriter];
startedAt = [[NSDate date] retain];
_recording = YES;
}
}
return result;
}
I call the method as following:
bool _startRecording(){
return [delegateMSA startRecording];
}
[MirrorStarAV startRecording:]: unrecognized selector sent to instance 0x15561d60
The above error message is basically saying that MirrorStarAV doesn't respond to startRecording: so when you call [delegateMSA startRecording]; it is crashing. The delegateMSA has an instance of MirrorStarAV set to it but instances of MirrorStarAV don't respond to `startRecording:
Whilst YES it would be better for you to change your method to something like
bool _startRecording(){
if ([delegateMSA respondsToSelector:#selector(startRecording)])
{
return [delegateMSA startRecording];
}
return false;
}
but this isn't your issue. Note that [MirrorStarAV startRecording:] has a colon : at the end. You are calling startRecording: somewhere over startRecording
Check before, whether the delegate responds to this method, so you can make sure that the object is also able to respond to this selector:
bool _startRecording(){
if ([delegateMSA respondsToSelector:#selector(startRecording)])
{
return [delegateMSA startRecording];
}
return false;
}
Either way, I would recommend to add this selector to the delegates methods with #required:
#protocol MyRecorderDelegate <NSObject>
#required
- (BOOL)startRecording;
#end
So, you would get a compiler warning, if your delegate was not implementing this method.
Verify if the delegateMSA is an instance of class where you have the startRecording method.
Or do something like
bool _startRecording(){
if([delegateMSA respondsToSelector:#selector(startRecording)]) {
return [delegateMSA startRecording];
}
}

heightForRowAtIndexPath occasionally crashes when loading, NSCoding - iOS

Some information about the way I am saving my data: I have an array of View Controllers that are added and deleted by the user (this is basically a note taking app and the View Controllers are folders). The View Controllers have several dynamic properties that the app needs to save as well as the notes array within them, and then the Note objects themselves have a few properties that need to be saved. The View Controllers and the Notes both of course have the proper NSCoding stuff, this is the one on the View Controller for example:
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.folderName forKey:#"lvcTitle"];
[encoder encodeObject:[NSNumber numberWithInt:self.myPosition] forKey:#"myPosition"];
[encoder encodeObject:self.notes forKey:#"notes"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self.folderName = [decoder decodeObjectForKey:#"lvcTitle"];
NSNumber *gottenPosition = [decoder decodeObjectForKey:#"myPosition"];
int gottenPositionInt = [gottenPosition intValue];
self.myPosition = gottenPositionInt;
self.notes = [decoder decodeObjectForKey:#"notes"];
return self; }
The array of Controllers belongs to a Singleton class. NSCoding is pretty confusing to me even though it's considered to be simple stuff, but so far I've had success with only telling the Singleton to save the Controllers array - which then (successfully) saves all of the contained properties of the View Controllers, their properties and all of the Notes' properties as well. Here is the code in the Singleton:
- (void) saveDataToDisk:(id)object key:(NSString *)key {
NSString *path = [self pathForDataFile];
NSMutableDictionary *rootObject;
rootObject = [NSMutableDictionary dictionary];
[rootObject setValue:object forKey:key];
[NSKeyedArchiver archiveRootObject:rootObject toFile:path]; }
- (void) loadDataFromDisk {
NSString *path = [self pathForDataFile];
NSDictionary *rootObject;
rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if ([rootObject valueForKey:#"controllers"] != nil) {
self.controllers = [NSMutableArray arrayWithArray:[rootObject valueForKey:#"controllers"]];
firstRun = false;
LabeledViewController *lastOneThere = [self.controllers objectAtIndex:self.controllers.count-1];
lastOneThere.isFolderAddView = TRUE;
}else{
firstRun = true;
}
}
I then call the save method several times in the Folder View Controllers:
[singleton saveDataToDisk];
And this will work well several times, until I randomly get a crash right when the app is loading up. The culprit is heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
Note *currentNote = [self.notes objectAtIndex:indexPath.row];
if (currentNote.associatedCellIsSelected) {
return currentNote.myHeight + NOTE_BUTTON_VIEW_HEIGHT;
}
return NORMAL_CELL_FINISHING_HEIGHT; }
I get the following error:
2012-06-07 08:28:33.694 ViewTry[1415:207] -[__NSCFString associatedCellIsSelected]: unrecognized selector sent to instance 0x8904710
2012-06-07 08:28:33.696 ViewTry[1415:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString associatedCellIsSelected]: unrecognized selector sent to instance 0x8904710'
*** First throw call stack:
I understand that "__NSCFString" and "unrecognized selector sent to instance" means that there is a string somewhere there shouldn't be, as associatedCellIsSelected is a bool. However, if I only return "currentNote.myHeight" in heightForRow, I also get the same __NSCF error with myHeight, which is a float. If I take out heightForRow all together, everything works except for the appropriate height definitions.
BTW, the table view that heightForRowAtIndexPath is referencing is made in loadView AFTER the notes array is made and populated. I just don't understand why this error would only pop up every once in a while (like 5-10 opens, savings, closings and reopenings of app), seemingly random - I cannot find the pattern that causes this behavior. Any pointers?
Sorry for the mess, I'm new to iOS programming and I'm sure I'm doing a lot of things wrong here.
Edit - Also, once the app has crashed, it stays crashed every time I reopen it (unless I disable heightForRow) until I uninstall and reinstall it.
When you see an "unrecognized selector" error and the receiver type is not the kind of object that you coded (in this case __NSCFString instead of Note), the odds are that you have a problem where the object you intended to use has been prematurely released and its address space is being reused to allocate the new object.
The fix depends on tracking down where the extra release is happening (or retain is not happening). If you can show the #property declaration for notes it might shed more light on the situation.
One quick thing to do is choose Product->Analyze from the menu and fix anything it flags. It won't catch everything but it's a good sanity check to start.

use subclass of CCLayer for CCScrollLayer

I've created a custom class which is a subclass of CCLayer and trying to use it for CCScrollLayer
The way I do it is:
//Store the my layers to an NSMutableArray
for (AACustomClassLayer *cardLayer in levels) {
[layers addObject:cardLayer];
}
Under the hood of CCScrollLayer it crashes at:
- (void) updatePages
{
// Loop through the array and add the screens if needed.
int i = 0;
for (CCLayer *l in layers_)
{
l.anchorPoint = ccp(0,0);
l.contentSize = [CCDirector sharedDirector].winSize;
l.position = ccp( (i * (self.contentSize.width - self.pagesWidthOffset)), 0 );
if (!l.parent)
[self addChild:l];
i++;
}
}
The implementation for the AACustomClassLayer class (subclass of CCLayer) looks like:
-(id)initWithChapter:(AALevel *)level {
self = [super init];
if (self) {
self.isTouchEnabled = YES;
//Here I'm adding the CCSprite to my layer
}
return self;
}
UPDATE:
Crash log
2012-04-20 14:12:12.344 [15780:10a03] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFDictionary setAnchorPoint:]: unrecognized selector sent to instance 0x884ab40'
*** First throw call stack:
(0x1a75022 0x200fcd6 0x1a76cbd 0x19dbed0 0x19dbcb2 0xd013f 0xcfe2b 0x102370 0x44c15 0xbe45f 0x8a94be 0x8aa274 0x8b9183 0x8b9c38 0x8ad634 0x282def5 0x1a49195 0x19adff2 0x19ac8da 0x19abd84 0x19abc9b 0x8a9c65 0x8ab626 0xbda06 0x22e5)
terminate called throwing an exception
I've found it!
for (AACustomClassLayer *cardLayer in levels) {
cardLayer = [[AACustomClassLayer node] autorelease];
[layers addObject:cardLayer];
}
You should add a conditional check in your for loop that determines if the object you are getting from enumeration is in fact a CCLayer. Your crash log states that the anchorPoint setter was not available on some object, presumably on an object in your layers_ array since that is the code you have posted that deals with anchorPoints.
Enumeration is convenient, but you are casing all objects to CCLayer when it is possible that one of them is not. I don't know where you are adding objects to layers_ but is it possible you are adding an object that is not actually a CCLayer ?

Resources