+ (void)load {
[super load];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class aClass = [self class];
SEL selector = #selector(setBackgroundColor:);
SEL _selector = #selector(cusSetBackgroundColor:);
Method method = class_getInstanceMethod(aClass, selector);
Method _method = class_getInstanceMethod(aClass, _selector);
BOOL did = class_addMethod(aClass, selector, method_getImplementation(_method), method_getTypeEncoding(_method));
if (did) {
class_replaceMethod(aClass, _selector, method_getImplementation(method), method_getTypeEncoding(method));
} else {
class_addMethod(aClass, _selector, method_getImplementation(_method), method_getTypeEncoding(_method));
method_exchangeImplementations(method, _method);
}
});
}
- (void)cusSetBackgroundColor:(UIColor *)backgroundColor
{
NSLog(#"test swizzling");
[self cusSetBackgroundColor:backgroundColor];
}
when i put it into "UITableView+Swizzling". And choose iPad Pro. it will crash. (iPhone runs well)
the crash log is:
Assertion failure in void PushNextClassForSettingIMP(id, SEL()).
Terminating app due to uncaught exception 'NSInternalInconsistencyException'
don't konw why.....
for ipad.
i run 'p class_getInstanceMethod(aClass, #selector(setBackgroundColor:))'
the console can't output the function address.
but for iPhone. i can get the address.
so i tried 'p class_getInstanceMethod(aClass, #selector(_setBackgroundColor))'
problem solved..
but it's just so strange. if you konw other soluetion.
please let me konw.
The same crash with me, and I resolved it use UIView instead of UITableView.
Related
Basically, I want to print each time a class object is instantiated. The following code shows the intent.
#interface NSObject (ILogger)
+ (void)initialize;
#end
#implementation NSObject (ILogger)
+ (void)initialize
{
NSLog(#"Initializing %s", class_getName([self class]));
}
#end
This does not work because NSObject already has a +initialize method so this approach results in undefined behavior. The compiler also warns about the issue: warning: category is implementing a method which will also be implemented by its primary class
One idea would be to somehow swizzle +[NSObject initialize] and do the logging. How do I do that safely?
EDIT:
Maybe I'm using the wrong terminology but the goal is to know if a class is used at all in the app. If many objects of a class are created, there is no need to log every time, once is sufficient.
After Edit Answer
You are correct about use of +[NSObject initialize] method for tracking the first use of a class. I don't know anything more appropriate for that. The swizzling would look like this:
#import <objc/runtime.h>
#implementation NSObject (InitializeLog)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getClassMethod(self, #selector(initialize));
Method swizzledMethod = class_getClassMethod(self, #selector(tdw_initializeLog));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
+ (void)tdw_initializeLog {
const char *className = class_getName(self);
printf("Initializing %s\n", className);
[self tdw_initializeLog];
}
#end
There are a few things to be advised about:
initialize doesn't fallback to the NSObject implementation (which is swizzled above) if derived classes have this method implemented AND don't have [super initialize]; called. Thus for any custom class inherited from Cocoa classes either don't implement this method OR call [super initialize]; somewhere in your implementation:
+ (void)initialize {
[super initialize];
...
}
Cocoa classes are rarely as straightforward as they look like. Quite a lot of interfaces and classes are hidden under the same name and sometimes the logs will be somewhat misleading (e.g. in place of NSNumber you will get NSValue class reported). Thus, take any logging out of Foundation classes with a grain of salt and always double-check where it comes from (also be ready that those classes won't be reported at all).
First use of NSLog also triggers some classes to initialise themselves and it make them to call +[NSObject initialize]. In order to avoid an infinite loop or bad_access errors I decided to use printf to log the fact of initialisation in my implementation.
Original Answer
The + (void)initialize method has very little to do with objects instantiation, since it gets called for each Objective-C class shortly before it's first time used in your client code. It might be called multiple times if subclasses of a given class don't have this method implemented and never gets called afterward. Thus it's just a bad choice if you want to track objects instantiation.
However there are still a few options you may want to employ to track occasions of objects instantiation.
Swizzling -[NSObject init]
First, I would consider init method of NSObject:
#import <objc/runtime.h>
#implementation NSObject (InitLog)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod(self, #selector(init));
Method swizzledMethod = class_getInstanceMethod(self, #selector(initLog_tdw));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (instancetype)initLog_tdw {
self = [self initLog_tdw];
if (self) {
const char *className = class_getName([self class]);
NSLog(#"Instantiating %s", className);
}
return self;
}
#end
It will work fine as long as instances falls back to the -[NSObject init] method. Unfortunately quite a lot of Cocoa classes don't do that. Consider the following scenario:
NSObject *obj = [NSObject new]; // NSLog prints "Instantiating NSObject"
NSString *hiddenStr = [[NSMutableString alloc] initWithString:#"Test"]; // NSLog is silent
NSURL *url = [[NSURL alloc] initWithString:#"http://www.google.com"]; // NSLog is silent
-[NSURL initWithString:] and -[NSMutableString initWithString:] somehow avoids NSObject's default constructor being called. It will still work for any custom classes which don't have any fancy initialisation:
#implementation TDWObject
- (instancetype)initWithNum:(int)num {
self = [super init];
if (self) {
_myNum = num;
}
return self;
}
#end
TDWObject *customObj = [TDWObject new]; // NSLog prints "Instantiating TDWObject"
TDWObject *customObjWithNum = [[TDWObject alloc] initWithNum:2]; // NSLog prints "Instantiating TDWObject"
Swizzling +[NSObject alloc]
Alternatively you can swizzle the alloc method:
#import <objc/runtime.h>
#implementation NSObject (AllocLog)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getClassMethod(self, #selector(alloc));
Method swizzledMethod = class_getClassMethod(self, #selector(tdw_allocLog));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
+ (instancetype)tdw_allocLog {
id allocatedObject = [self tdw_allocLog];
if (allocatedObject) {
const char *className = class_getName([allocatedObject class]);
NSLog(#"Allocating %s", className);
}
return allocatedObject;
}
#end
It will intercept almost all Cocoa classes instantiation (the exception must be some of the fabric methods, where class-specific optimisation takes place, e.g. +[NSNumber numberWith..] family of methods), but there are other problems to be aware of. The allocated instances returned from alloc method are not always that straightforward. E.g. for NSMutableString example above NSLog will print NSPlaceholderMutableString:
TDWObject *customObj = [TDWObject new]; // NSLog prints "Allocating TDWObject"
TDWObject *customObjWithNum = [[TDWObject alloc] initWithNum:2]; // NSLog prints "Allocating TDWObject"
NSObject *obj = [NSObject new]; // NSLog prints "Allocating NSObject"
NSString *hiddenStr = [[NSMutableString alloc] initWithString:#"Test"]; // NSLog prints "Allocating NSPlaceholderMutableString"
NSURL *url = [[NSURL alloc] initWithString:#"http://www.google.com"]; // NSLog prints "Allocating NSURL"
That's is because Foundation framework uses Class Cluster design pattern heavily and instances returned by alloc are often some kind of abstract factories, which are later leveraged by Cocoa classes to make a concrete instance of the class requested.
Both approaches have their own downsides, but I struggle to come up with anything more concise and reliable.
I think it's possible to do this with breakpoint if only need logging, I've not tested it with initialize, but does works on my case with dealloc, note that it might print a lot more than you actually needed and slow down performance:
In Xcode, go to the Breakpoint navigator (Cmd+8)
At the bottom-left on the screen, tap '+', then select "Symbolic Breakpoint..." from the menu
Fill the form:
Symbol: -[NSObject initialize]
Action: Select "Log Message"
Enter: --- init #(id)[$arg1 description]# #(id)[$arg1 title]#
Select "Log message to console"
Check "Automatically continue after evaluating actions" so Xcode does not stop at the breakpoint
The only possible solution I found was swizzling -[NSObject init] this was tested only in a small test project
I have an article about swizzling that maybe you will find interesting medium article
extension NSObject {
static let swizzleInit: Void = {
DispatchQueue.once(token: "NSObject.initialize.swizzle") {
let originalSelector = Selector("init")
let swizzledSelector = #selector(swizzledInitialize)
guard let originalMethod = class_getInstanceMethod(NSObject.self, originalSelector),
let swizzledMethod = class_getInstanceMethod(NSObject.self, swizzledSelector)
else {
debugPrint("Error while swizzling")
return
}
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}()
#objc
private func swizzledInitialize() -> Self? {
debugPrint("\(Self.self) has been initialized")
return swizzledInitialize()
}
}
DispatchQueue.once implementation in DispatchQueue.once gist
Then in AppDelegate ...
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
NSObject.swizzleInit
// Override point for customization after application launch.
return true
}
I'm trying to debug a mysterious crash I'm seeing in Crashlytics, but haven't been able to reproduce myself.
The error message looks like this:
Fatal Exception: NSInvalidArgumentException
-[NSNull compare:]: unrecognized selector sent to instance 0x1e911bc30
-[NSOrderedSet initWithSet:copyItems:]
Here is the full stacktrack if interested
Because I haven't been able to pinpoint the origin of the crash, I thought I would add a new method to NSNull in order to further debug it via logging.
However I'm not sure how to do it. I think I'd need to add a compare method to NSNull, but I have limited knowledge of objc. I got the idea from this answer. The proposed solution for a similar problem looks like this
BOOL canPerformAction(id withSender) {
return false;
}
- (void)viewDidLoad {
[super viewDidLoad];
Class class = NSClassFromString(#"UIThreadSafeNode");
class_addMethod(class, #selector(canPerformAction:withSender:), (IMP)canPerformAction, "##:");
}
How could I do this in Swift for adding compare to NSNull?
You could add a compare method to NSNull like this:
Objective-C:
#import <objc/runtime.h>
static inline NSComparisonResult compareNulls(id self, SEL _cmd, NSNull *other) {
if([other isKindOfClass:[NSNull class]]) {
return NSOrderedSame; // Nulls are always the same.
}
return NSOrderedDescending;
}
#implementation NSNull (Comparisons)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
const char *encoding = [[NSString stringWithFormat:#"%s#:#", #encode(NSComparisonResult)] UTF8String];
class_addMethod([self class], #selector(compare:), (IMP)compareNulls, encoding);
});
}
#end
Swift:
// Add this code to your AppDelegate.swift file:
import ObjectiveC
fileprivate func compareNulls(_ self: AnyObject, _ _cmd: Selector, _ other: AnyObject) -> ComparisonResult {
if other is NSNull {
return .orderedSame
}
return .orderedDescending
}
fileprivate func addNSNullCompareImplementationIfNecessary() {
let sel = NSSelectorFromString("compareNulls:")
guard class_getMethodImplementation(NSNull.self, sel) == nil else {
return
}
let types = "i#:#"
class_addMethod(NSNull.self, sel, imp_implementationWithBlock(compareNulls), types)
}
// Add this line to your -didFinishLaunching: function:
addNSNullCompareImplementationIfNecessary()
This is only a temporary solution that will stop the crashes.
I would nevertheless encourage you to a) file a bug report, and b) continue investigating why this happened - clearly having an NSNull in this case wasn't expected by Parse...
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.
In a simple subclass of UIViewController I just implemented a selector as a callback to refresh UI
-(void)afterfollowAction:(id)object{
//blabla
}
but finally I found respondsToSelector can not recognise it. Test code:
SEL callback = #selector(afterFollowAction:);
BOOL test1 = [self respondsToSelector:callback]; //NO
BOOL test2 = [self respondsToSelector:#selector(afterFollowAction:)]; //NO
BOOL test3 = [[self class] instanceMethodForSelector: #selector(afterFollowAction:)]; //YES
BOOL test4 = [self respondsToSelector:#selector(viewWillDisappear:)]; //YES
[self performSelector:callback withObject:nil]; //crash
test2 shows not the local variable callback's issue,
test4 shows it knows it is a UIViewController and self is not nil
test3 shows actually it knows the selector is implemented
but if you invoke it with "performSelector" it throw exception "unrecognized selector"
I searched the web and I did not make error with ":" or nil thing;
I put the declaration of the selector to the header file, but makes no use.
I restarted the Mac and iPhone but it is the same result.
This is a simple class in which I do not re-implement respondsToSelector: selector.
So, Did I make wrong use of respondsToSelector? I just want to make the callback outside this class.
Environment: Mac OS X 10.11.3, Xcode 7.2.1, iOS 9.2.1
Update: find(cmd+F) without match case hide the f/F issue. Thanks to Kiran!
It should be
SEL callback = #selector(afterfollowAction:);
BOOL test1 = [self respondsToSelector:callback]; //NO
BOOL test2 = [self respondsToSelector:#selector(afterfollowAction:)]; //NO
BOOL test3 = [[self class] instanceMethodForSelector: #selector(afterfollowAction:)]; //YES
BOOL test4 = [self respondsToSelector:#selector(viewWillDisappear:)]; //YES
[self performSelector:callback withObject:nil]; //crash
Your are using capital "F"
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];
}
}