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

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).

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
}

retain cycle with target-selector

I am new to retain cycle which I am confused if my situation falls into.
I have a singleton class
Singleton.h
#interface Singleton : NSObject
+ (Singleton *)sharedInstance;
- (void)doSomethingWithData:(NSDictionary *)data untilDoneReturnToTarget:(id)target selector:(SEL)selector;
#end
Singleton.m
#implementation RequestSingleton
static Singleton *shared = nil;
#pragma mark - System
- (id)init {
self = [super init];
if (self) {
}
return self;
}
#pragma mark - Interface
+ (Singleton *)sharedInstance {
static dispatch_once_t pred;
dispatch_once(&pred, ^{
shared = [[Singleton alloc] init];
});
return shared;
}
- (void)doSomethingWithData:(NSDictionary *)data untilDoneReturnToTarget:(id)target selector:(SEL)selector {
[someClass doSomething:data
completionHandler: ^{
if ([target respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[target performSelector:selector
withObject:someObject]; // is this a retain-cycle?
#pragma clang diagnostic pop
}
}];
}
Now in a view controller, I call doSomethingWithData:untilDoneReturnToTarget:selector: defined in Singleton class:
- (void)function {
Singleton *singleton = [Singleton sharedInstance]
[singleton doSomethingWithData:someData
target:self
selector:#selector(processResult:)];
}
- (void)processResult:(id)data {
…
}
I'm not sure if my implementation in doSomethingWithData:untilDoneReturnToTarget:selector: has fallen into retain-cycle? Should I use weakTarget instead of target in the block implementation? Please help me clarify.
Thanks in advance.
There is no retain cycle in your code as far as I can see. But you are extending target's lifecycle until you finish processing data.
If you want to keep the same behaviour but issue a callback only if target is still around, use weak reference
Here is a good article that might help http://digitalleaves.com/blog/2015/05/demystifying-retain-cycles-in-arc/
Update: one of the most common cases for retain cycle is mutual strong reference: object → block and block → object. Sometimes you can even introduce it not knowingly by using API that would internally create a retain cycle if you are not careful, e.g. ReactiveCocoa (that's why they even have macros to fight it called weakify and strongify).
If you have a singleton, it won't get deallocated anyway, even if there's no retain cycle.

Singleton UIViewController

I'm trying to add some data from a random class to my viewController,
So to keep always the same data, i did a singleton on my UIViewController, but it doesnt work i never get the data on my tableview.
this what i added to my UIViewController :
+(id)sharedMBVC {
static MBViewController *sharedMBVC ;
#synchronized(self) {
if (!sharedMBVC)
sharedMBVC = [[MBViewController alloc] init];
return sharedMBVC;
}
}
and from my class i call it by doing this :
MBViewController *vc = [MBViewController sharedMBVC];
Do i have to set somewhere the content of my NSArrays that they are declared in my viewDidLoad of the viewcontroller ? or there is something else to do.
PS : i was doing in my class before vc = (MBViewController *)[[[[UIApplication sharedApplication] delegate] window] rootViewController]; but now my uiviewcontroller its not a rootview anymore, thats why im trying to find other way to access to it, and i guess the best solution is to do a singleton
Can u help me guys
OK, so the problem you have is that the viewController that displays the arrays also "owns" the arrays. This means that (with your current setup) to be able to change the arrays you need to get hold of the viewController to be able to access the arrays.
You need to change this by removing the arrays from that viewController.
You can still do this with a singleton (if you prefer) but create a brand new class called something like ArrayManager.
This will contain the arrays and ALL the methods for updating the arrays.
So for instance if your viewController has a method called - (void)addObjectToArray:(id)object; then move this method to the ArrayManager singleton class.
Now in your displaying viewController you can do...
[[ArrayManager sharedInstance] getSomeDataFromTheArray];
And in the place that has to update the array you can do...
[[ArrayManager sharedInstance] addObjectToArray:someObject];
Now you don't need to worry about passing the viewController around at all.
This can be improved with various things. For instance, you maybe don't need a singleton at all and can just take this ArrayManager class and inject it into the places that need it by setting a property etc...
Also, you could possibly use CoreData to store the information.
Also, your singleton method is not correct. The way recommended by Apple is to use...
+ (ArrayManager *)sharedInstance
{
static dispatch_once_t once;
static ArrayManager *arrayManager;
dispatch_once(&once, ^{
arrayManager = [[ArrayManager alloc] init];
});
return arrayManager;
}
Rewriting your singleton...
.h file
#interface PTVData : NSObject
+ (PTVData *)sharedInstance;
- (void)addSensor:(NSString *)sensorName;
- (NSInteger)numberOfSensors;
- (NSString *)sensorAtIndex:(NSUInteger)index;
#end
.m file
#interface PTVData ()
#property (nonatomic, strong) NSMutableArray *sensors;
#end
#implementation PTVData
+ (PTVData)sharedInstance
{
static PTVData *sharedPTVData;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedPTVData = [[PTVData alloc] init];
});
return sharedPTVData;
}
- (id)init
{
if (self = [super init]) {
_sensors = [[NSMutableArray alloc] initWithObject:#"None"];
}
return self;
}
- (void)addSensor:(NSString *)sensorName
{
if (sensorName
&& ![self.sensors containsObject:sensorName]) {
[self.sensors addObject:sensorName];
}
}
- (NSInteger)numberOfSensors
{
return self.sensors.count;
}
- (NSString *)sensorAtIndex:(NSUInteger)index
{
return self.sensors[index];
}
#end
By doing this you hide the actual array of sensors. It is only directly accessible through the PTVData class.
Now in your tableview methods you can do...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[PTVData sharedInstance] numberOfSensors];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = ...
cell.textLabel.text = [[PTVData sharedInstance] sensorAtIndex:indexPath.row];
return cell;
}
Well, I think, these arrays don't belong to the MBViewController from architectural point of view. I would separate them to a data layer (DataSource class of some sort, for instance) and keep a reference to the DataSource everywhere you need. Have a look at the Second iOS App tutorial by Apple. It contains a simple example of data layer implementation.
UPDATE:
Also, check out Fogmeister's answer. He explains a possible implementation of such object rather well :)
As for why singleton didn't work as you expected in this case, I believe, the reason could be the following:
If you get to the MBViewController via a segue (which, I think, you are), then a new instance of MBViewController is created every time. If you access your arrays from MBViewController using self.myArray, then you access this new MBViewController's myArray. While sharedMBVC keeps a reference to the shared instance, it's just ignored by the segue.
in my ViewController.h
#property PTVData *ptvdata;
ViewController.m
ViewDidload
ptvdata = [PTVData sharedPTVData];
_sensorsCollection = ptvdata.sensorsCollection;
then i have a method in my ViewController.m
- (void) addSensorToCollection:( NSString *)sensorName{
[[PTVData sharedPTVData] addSensorToCollection:sensorName];
_sensorsCollection = ptvdata.sensorsCollection;
[ self.tableView reloadData];
}
}
My PTVData.h
#property (nonatomic,retain) NSMutableArray *sensorsCollection;
+(id)sharedPTVData;
-(id) init;
- (void) addSensorToCollection:( NSString *)sensorName;
#end
my PTVData.m
#synthesize sensorsCollection = _sensorsCollection;
+ (id)sharedPTVData {
static PTVData *sharedPTVData = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedPTVData = [[self alloc] init];
});
return sharedPTVData;
}
- (id)init {
if (self = [super init]) {
_sensorsCollection = [[NSMutableArray alloc]initWithObjects:#"None", nil];
}
return self;
}
- (void) addSensorToCollection:( NSString *)sensorName{
if (![_sensorsCollection containsObject:sensorName]&& sensorName!= nil) {
[_sensorsCollection addObject:sensorName];
}
}
Instead of initializing your arrays in viewDidLoad, do it in sharedMBVC function. This will ensure that arrays are not re-initialized every time the view loads.

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)"

iOS Storing a pointer

In my ios application i want to store an object's reference. This object can be an instance from interface A,B,C or D. I know that it's always going to be one from these four, but never know which one. How can i represent this object in my code?
Sincerely, Zoli
Represent it as type id.
id ptr;
Also, be aware of the possibility that you can specialise type id to some protocol.
id <SomeProtocol>;
what i understand that you should make a shared instance of that object.
example:-
make a new class named SavedReference.
and write in implementation file this code
Code:
static SavedReference *sharedInstance = nil; //if using iOS5 or above no need to nil it.
+(SavedReference*)sharedInstance
{
#synchronized(self)
{
if(!sharedInstance)
{
sharedInstance = [[self alloc]init];
return sharedInstance;
}
}
return nil;
}
-(id)init
{
self = [super init];
if(self)
{
//initialize variables
}
return self;
}
And Call this class as [[SavedReference sharedInstance] write ur method]

Resources