Accessing private methods

Is there a way to access a method with 4 parameters which is not declared in header file?

Accepted Reply

1. Will Apple reject if

NSInvocation
is used?
NSInvocation
is a public API, and using it is not grounds for rejection in and of itself. However, using it to call private methods in Apple frameworks is definitely grounds for rejection. And even if you’re not shipping via the App Store, using private methods can lead to binary compatibility problems in the future. Don’t do this.

WARNING Do not do any of this for Apple classes in product code.

2. I tried Managing Functions and Function Pointers but I couldn't use as one of the parameter is a block for call back.

It’s hard to say what’s going on here without more context. Pasted in below is a full program that implements my take on your code:

@import Foundation;
@import ObjectiveC;

@interface Main : NSObject

@end

@implementation Main

typedef void (^Handler)(NSDictionary *info, NSError *error);

- (void)run {
    typedef void (*MethodType)(id, SEL, NSString *, NSString *, Handler);
    MethodType methodToCall = (MethodType) objc_msgSend;
    methodToCall(
        self,
        @selector(testWithString1:string2:handler:),
        @"s1",
        @"s2",
        ^(NSDictionary *info, NSError *error) {
            NSLog(@"handler called, info: %@, error: %@", info, error);
        }
    );
}

- (void)testWithString1:(NSString *)string1 string2:(NSString *)string2 handler:(Handler)handler {
    handler(@{ @"a": string1, @"b": string2 }, nil);
}

@end

int main(int argc, char **argv) {
    #pragma unused(argc)
    #pragma unused(argv)
    @autoreleasepool {
        [[[Main alloc] init] run];
    }
    return EXIT_SUCCESS;
}

Running it here (macOS 10.14.6, Xcode 10.3) it prints:

2019-08-23 09:48:30.561572+0100 xxot[86465:7744413] handler called, info: {
    a = s1;
    b = s2;
}, error: (null)

3. I tried category as well. It didn't work as the category method is not declared in header file.

I’m not sure what you mean by this. The purpose of the category is to declare methods, so if it doesn’t declare a method then you can solve that by adding a method to the category.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

The "normal" way of handling cases like these (when, for example, there's no performSelector variant to help you) is to use NSInvocation to construct an object you can use to invoke the method:


https://developer.apple.com/documentation/foundation/nsinvocation


Using NSInvocation is not especially easy. You need a pretty good understanding of the Obj-C runtime (general principles, at least), and a lot of patience to chip away at the problem to get it right. With luck you might find a tutorial out there on the internetz somewhere.


Or, you could, you know, declare the method in a header file.


What's the use-case here?

The "normal" way of handling cases like these … is to use

NSInvocation

Personally, I try to avoid

NSInvocation
wherever possible. Instead I do this by defining a function type for the method’s signature (including its two hidden parameters) and then casting
objc_msgSend
to that function type and calling that. There’s an example of this in the Cast Objective-C Messages in Proper Form section of Managing Functions and Function Pointers.

There is a gotcha here, namely ‘abnormal’ return types. If the method returns a structure or a floating point value, you have to use the appropriate

objc_msgSend
variant (
objc_msgSend_stret
,
objc_msgSend_fpret
).

Oh, wait, if you’re trying to call a limited number of method selectors, you can just define those methods in a category on the class and call them directly.

IMPORTANT Do not do any of this for Apple classes in product code. That puts you on the path to binary compatibility problems in the future.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

1. Will Apple reject if NSInvocation is used?

2. I tried Managing Functions and Function Pointers but I couldn't use as one of the parameter is a block for call back.


typedef void (^Handler)(NSDictionary *info, NSError *error);

id target = self;

SEL selector = @selector(test:string2:string3:);

typedef void (*MethodType)(id, SEL, id, id, id);

Handler handler;

MethodType methodToCall = (MethodType)[target methodForSelector:selector];

methodToCall(target, selector, @(1), @(2), handler);



- (void)test:(NSString*)string1 string2:(NSString*)string2 handler:(Handler)handler{

handler(nil, nil);

}


3) I tried category as well. It didn't work as the category method is not declared in header file.

1. Will Apple reject if

NSInvocation
is used?
NSInvocation
is a public API, and using it is not grounds for rejection in and of itself. However, using it to call private methods in Apple frameworks is definitely grounds for rejection. And even if you’re not shipping via the App Store, using private methods can lead to binary compatibility problems in the future. Don’t do this.

WARNING Do not do any of this for Apple classes in product code.

2. I tried Managing Functions and Function Pointers but I couldn't use as one of the parameter is a block for call back.

It’s hard to say what’s going on here without more context. Pasted in below is a full program that implements my take on your code:

@import Foundation;
@import ObjectiveC;

@interface Main : NSObject

@end

@implementation Main

typedef void (^Handler)(NSDictionary *info, NSError *error);

- (void)run {
    typedef void (*MethodType)(id, SEL, NSString *, NSString *, Handler);
    MethodType methodToCall = (MethodType) objc_msgSend;
    methodToCall(
        self,
        @selector(testWithString1:string2:handler:),
        @"s1",
        @"s2",
        ^(NSDictionary *info, NSError *error) {
            NSLog(@"handler called, info: %@, error: %@", info, error);
        }
    );
}

- (void)testWithString1:(NSString *)string1 string2:(NSString *)string2 handler:(Handler)handler {
    handler(@{ @"a": string1, @"b": string2 }, nil);
}

@end

int main(int argc, char **argv) {
    #pragma unused(argc)
    #pragma unused(argv)
    @autoreleasepool {
        [[[Main alloc] init] run];
    }
    return EXIT_SUCCESS;
}

Running it here (macOS 10.14.6, Xcode 10.3) it prints:

2019-08-23 09:48:30.561572+0100 xxot[86465:7744413] handler called, info: {
    a = s1;
    b = s2;
}, error: (null)

3. I tried category as well. It didn't work as the category method is not declared in header file.

I’m not sure what you mean by this. The purpose of the category is to declare methods, so if it doesn’t declare a method then you can solve that by adding a method to the category.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Defining a function type for the method’s signature solved the problem. Thanks! In regards to adding a method to the category, it didn't work as I didn't delcare the method as public (in .h file) i.e method testWithString1:string2:handler: is not declared in .h of the implementation class(.m), so I couldn't call the private methed declared in Category Class.