ios swizzle better understanding - ios

I have a UIViewController with this code:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"CLASIC");
}
And then I have a framework with a UIViewController category that does swizzling in this manner:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL viewWillAppearSelector = #selector(viewDidAppear:);
SEL viewWillAppearLoggerSelector = #selector(logged_viewDidAppear:);
Method originalMethod = class_getInstanceMethod(self, viewWillAppearSelector);
Method extendedMethod = class_getInstanceMethod(self, viewWillAppearLoggerSelector);
method_exchangeImplementations(originalMethod, extendedMethod);
});
}
- (void)logged_viewDidAppear:(BOOL)animated
{
[self logged_viewDidAppear:animated];
NSLog(#"SWIZZLED");
}
The output is SWIZZLED and then CLASIC.
Now my question is: if in my viewcontroller I comment the [super viewDidAppear:animated]; then the swizzled method does not get called anymore; why is that? I understood most of the aspects but it seems this one somehow slipped.
- (void)viewDidAppear:(BOOL)animated
{
// we comment this and this will trigger the swizzled method not being called anymore
//[super viewDidAppear:animated];
NSLog(#"CLASIC");
}
// ========================
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL viewWillAppearSelector = #selector(viewDidAppear:);
SEL viewWillAppearLoggerSelector = #selector(logged_viewDidAppear:);
Method originalMethod = class_getInstanceMethod(self, viewWillAppearSelector);
Method extendedMethod = class_getInstanceMethod(self, viewWillAppearLoggerSelector);
method_exchangeImplementations(originalMethod, extendedMethod);
});
}
- (void)logged_viewDidAppear:(BOOL)animated
{
[self logged_viewDidAppear:animated];
NSLog(#"SWIZZLED");
}

Method swizzling is used for overriding original methods with the custom one at runtime. So you can exchange almost any method, (including the private apple implemented ones) with the custom one you wrote.
So imagine there is class named Parent with a method named A and you exchange it with B somewhere before it been called like inside load method. From now on every sub class off 'Parent' will use B except the original 'A' method. But what if you override A in a child class? As Inheritance definition, objects will call their own methods and if they haven't implement it, they use their suprclass's method. So what if you want the parent implementation? Thats where super comes in.
Conclusion
If you override a method, the super class (or the custom exchanged method in superclass) method will not getting called
If you want the parent implementation, you have to use super keyword to access it
And in this questions case:
Overriding a method in sub class without calling super means you just override swizzled method and it will not getting called.
Hope it helps

Related

Swizzling +[NSObject initialize] method to log each class instantiation

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
}

Reset singleton instance to nil after each test case

I am using OCMock 3 to unit test my iOS project.
I use dispatch_once() created a singleton class MyManager :
#implementation MyManager
+ (id)sharedInstance {
static MyManager *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
I have a method in School class which uses the above singleton:
#implementation School
...
- (void) createLecture {
MyManager *mgr = [MyManager sharedInstance];
[mgr checkLectures];
...
}
#end
Now, I want to unit test this method, I use a partial mock of MyManager:
- (void) testCreateLecture {
// create a partially mocked instance of MyManager
id partialMockMgr = [OCMockObject partialMockForObject:[MyManager sharedInstance]];
// run method to test
[schoolToTest createLecture];
...
}
- (void)tearDown {
// I want to set the singleton instance to nil, how to?
[super tearDown];
}
In tearDown phase, I want to set the singleton instance to nil so that the following test case could start from clean state.
I know on internet, some people suggest to move the static MyManager *sharedMyManager outside the +(id)sharedInstance method. But I would like to ask, is there any way to set the instance to nil without moving it outside +(id)sharedInstance method? (Any solution like java reflection?)
You can't achieve what you want with a local static variable. Block-scoped statics are only visible inside their lexical context.
We do this by making the singleton instance a static variable scoped to the class implementation and adding a mutator to override it. Generally that mutator is only called by tests.
#implementation MyManager
static MyManager *_sharedInstance = nil;
static dispatch_once_t once_token = 0;
+(instancetype)sharedInstance {
dispatch_once(&once_token, ^{
if (_sharedInstance == nil) {
_sharedInstance = [[MyManager alloc] init];
}
});
return _sharedInstance;
}
+(void)setSharedInstance:(MyManager *)instance {
once_token = 0; // resets the once_token so dispatch_once will run again
_sharedInstance = instance;
}
#end
Then in your unit test:
// we can replace it with a mock object
id mockManager = [OCMockObject mockForClass:[MyManager class]];
[MyManager setSharedInstance:mockManager];
// we can reset it so that it returns the actual MyManager
[MyManager setSharedInstance:nil];
This also works with partial mocks, as in your example:
id mockMyManager = [OCMockObject partialMockForObject:[MyManager sharedInstance]];
[[mockMyManager expect] checkLectures];
[MyManager setSharedInstance:mockMyManager];
[schoolToTest createLecture];
[mockMyManager verify];
[mockMyManager stopMocking];
// reset it so that it returns the actual MyManager
[MyManager setSharedInstance:nil];
Here's a full breakdown of the approach.
The answer is no, because you use dispatch_once(&onceToken, ^{ so even if you added another method which could reset the variable to nil you'd never be able to initialise it again.
So you already have one solution and the best solution is to not access the singleton directly (use dependency injection instead).
It is an easier way to solute your issue.
Your class have a singleton. you can add a method that is destroy this class instance. So when you call shareManager method again , it will create a new instance.
Such as:
static MyManager *sharedMyManager = nil;
+ (void)destroy
{
sharedMyManager = nil;
}
As others have stated, what you should really do is refactor your code to use dependency injection. This means that if the School class needs a MyManager instance to operate, then it should have an initWithManager:(MyManager *)manager method which should be the designated initializer. Or if the MyManager is only needed in this particular method, it should be a method parameter, e.g. createLectureWithManager:(MyManager *)manager.
Then in your tests, you could just do School *schoolToTest = [[School alloc] initWithManager:[[MyManager alloc] init]], and each test would have a new MyManager instance. You could drop the singleton pattern entirely, removing the sharedInstance method on MyManager and your application's logic would be responsible to ensure that there is only one instance that you pass around.
But sometimes, you have to work with legacy code that you can't just refactor. In these cases, you need to stub the class method. That is, you need to replace the implementation of -[MyManager sharedInstance] with an implementation that returns [[MyManager alloc] init]. This can be accomplished using the runtime to swizzle the class method, which would be the equivalent of Java reflection that you are looking for. See this for an example of how to use the runtime.
You can also do it with OCMock, which uses the runtime behind the scenes, just like mocking frameworks in Java are based on the reflection API :
MyManager *testManager = [[MyManager alloc] init];
id mock = [[OCMockObject mockForClass:[MyManager class]];
[[[mock stub] andReturn:testManager] sharedInstance];
If you don't want to refactor your code for easier unit testing then there is another solution (not perfect but works):
Create a local property of MyManager type
In setUp instantiate the property from above and swizzle the sharedInstance method with your local method (e.g. swizzle_sharedInstance)
Inside the swizzle_sharedInstance return the local property
In tearDown swizzle back to original sharedInstance and nullify the local property
I suggest a little bit different approach. You can create a mock of your sharedInstance using OCMock:
id myManagerMock = OCMClassMock([MyManager class]);
OCMStub([myManagerMock sharedManager]).andReturn(myManagerMock);
Now School implementation will use myManagerMock object, and you can stub this object to return anything you want under you test case. For example:
OCMStub([myManagerMock someMethodThatReturnsBoolean]).andReturn(YES);
It's important that after your tests, you will perform cleaning of your mock object by calling (at the end of your test method or in -tearDown):
[myManagerMock stopMocking];

Safe way to create singleton with init method in Objective-C

I would like to take the GCD approach of using shared instances to the next step so I created the following code:
#implementation MyClass
static id sharedInstance;
#pragma mark Initialization
+ (instancetype)sharedInstance {
static dispatch_once_t once;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
if (sharedInstance) {
return sharedInstance;
}
#synchronized(self) {
self = [super init];
if (self) {
sharedInstance = self;
}
return self;
}
}
#end
I assume the sharedInstance method seems to be ok but I am unsure about the init method. The reason for creating this is that I don't want people using my SDK, to use the init method, and if they do ... make it bullet proof.
Instead of transparently redirecting calls to init to the singleton implementation which can cause very confusing behaviour for the users of your SDK, I suggest not allowing to call init at all:
+ (instancetype)sharedInstance {
static dispatch_once_t once;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] initPrivate];
});
return sharedInstance;
}
- (instancetype)init {
#throw [NSException exceptionWithName:NSInternalInconsistencyException reason:#"..." userInfo:nil];
}
- (instancetype)initPrivate {
if (self = [super init]) {
...
}
return self;
}
I would like to suggest new ways of solving your problem.
You can use NS_UNAVAILABLE in the header file just like this:
//Header file
#interface MyClass : NSObject
+ (instancetype)sharedInstance
- (instancetype)init NS_UNAVAILABLE;
//...
#end
In this case init function will not be available from outside, will not be suggested for autocompletion, and you'll be able to normally use the init method inside implementation file.
As you are making a singleton class I would suggest you to make new method unavailable too by adding this line to the header file:
+ (instancetype)new NS_UNAVAILABLE;
There is also an old way of making methods unavailable (which can be used in header too):
- (instancetype) init __attribute__((unavailable("Use 'sharedInstance' instead of 'init' as this class is singleton.")));
This can be used if you want to prompt some message about unavailability.
The general opinion is that trying to protect your singleton against that kind of bug is pointless. Whoever calls [[LUIMain alloc] init] and creates a singleton gets what they deserved.
And the code that you wrote isn't thread safe anyway. If I call [[LUIMain alloc] init] while someone else calls sharedInstance, sharedInstance will return a different object than on the next call. (#synchronized (self) in the init method is pointless, because a second caller will have a different self).

Accessing method on self from init?

I am using a singleton that I have setup that I am using to both preload and access my audio files (both sfx and music) I started out running [[FGAudio sharedInstance] preload]; from the AppDelegate but was concerned about having to remember to call preload before using the singleton. My question is to make things automatic, can I access a method on the singleton to do my setup and preload in init, or is it not wise to access a method init as things are still starting up?
+ (FGAudio *)sharedInstance {
static FGAudio *sharedAudio = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedAudio = [[FGAudio alloc]init];
});
return sharedAudio;
}
.
- (id)init {
self = [super init];
if(self) {
[self preload];
}
return self;
}
+ (FGAudio *)sharedInstance {
static FGAudio *sharedAudio = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedAudio = [[FGAudio alloc]init];
[sharedAudio preload];
});
return sharedAudio;
}
Referencing the method from init isn't bad if you can guarantee that subclasses won't mess with it or the objects it relies upon and in the future you won't mess up the preloading period by requiring objects that get inited after you call preload. If you use the above snippet you don't even have to worry about any of that and you also get a preloaded singleton.
Calling a method from the init method?
"Yes. Just be very careful (your object may not have been fully initialised, it shouldn't use accessor methods so as to comply with the previous restriction, et cetera)"

How to swizzle AudioSessionAddPropertyListener

There is a library in my project that is adding audio property listeners. I really need to be able to block it from doing so, but I don't have the source code.
I've done a swizzle before for the addObserver method in NSNotificationCenter. Could you help me do the same for AudioSessionAddPropertyListener? If the method trying to be added in the call does not match my whitelist, I want to block it. Otherwise, I'll call the original method.
I don't know what class for which I should overload the load function. I'm looking inside of
Audio.h. I'm adding some pseudo/real/badlyNamed code so you can see what I'm trying to do.
#import <AudioToolbox/AudioToolbox.h>
#interface AuidoClassUmm (SOMETHING)
#end
+ (void) load
{
Method original, swizzled;
original = class_getInstanceMethod(self, #selector(AudioSessionAddPropertyListener:selector:name:object:));
swizzled = class_getInstanceMethod(self, #selector(swizzled_AudioSessionAddPropertyListener:selector:name:object:));
method_exchangeImplementations(original, swizzled);
}
-(void) swizzled_AudioSessionAddPropertyListener:selector:name:object:
{
if(//funciton object I don't like)
{
return;
}
else
{
// Calls the original addObserver function
[self swizzled_AudioSessionAddPropertyListener::notificationObserver selector:notificationSelector name:notificationName object:notificationSender];
}
}
AudioSessionAddPropertyListener() is a C function, not an Objective-C method. You can't swizzle it.
(Even if you could swizzle it, that would almost certainly be a bad idea.)

Resources