Does referencing a singleton's sharedInstance create a retain cycle? - ios

Say I have the following singleton:
#interface ABCSingleton: NSObject
#property (nonatomic, copy) NSString *name;
#property (nonatomic, strong) ABCViewController *mainViewController;
#end
#implementation ABCSingleton
+ (ABCSingleton *)sharedInstance {
static ABCSingleton *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [ABCSingleton new];
});
return instance;
}
- (void)doSomething {
}
#end
If doSomething contained this code:
- (void)doSomething {
self.mainViewController.tapBlock = ^() {
self.name = #"abc";
};
}
... it would create a retain cycle since ABCSingleton owns mainViewController, which owns tapBlock, which owns ABCSingleton.
What if instead of using self, I used sharedInstance?
- (void)doSomething {
self.mainViewController.tapBlock = ^() {
[ABCSingleton sharedInstance].name = #"abc";
};
}
Would it still create a retain cycle? (An explanation as to why, or why not, would be appreciated!)

To the specific question, this is a retain loop in the first case, and equivalent to a retain loop in the second case (equivalent in that mainViewController will never be released).
This indicates a deeper design problem. A singleton should never reference a view controller. Singletons are by nature model objects. Model objects should never reference controller objects directly. See Model-View-Controller for an introduction. This is a key design pattern in Cocoa.
Instead, the view controller should know about the model (the singleton in this case). doSomething should modify the model, and the view controller, when it comes on the screen, should read the model to set tapBlock. The view controller can also observe the model (via KVO, notifications, or delegation) while it is onscreen to know when to change its values.

Related

Share singleton from Objective C to Swift

I am trying to access an Objective C singleton from Swift, however I only seem to get the initial value created in the init function of the singleton. The flightControllerState object exposed is updated in a delegate function and I can see that the value is properly updated on the Objective C side.
I have followed a few different posts here on SO and also this article on how to call the shared object from Swift. (I should also mention this is running inside a react native project if that may have any impact?)
EDIT updated swift code - I added the wrong line to the init method to grab shared instance - issue is still the same
Objective-C Singleton
#import DJISDK;
#interface RCTBridgeDJIFlightController : RCTEventEmitter<DJIFlightControllerDelegate> {
DJIFlightControllerState *flightControllerState;
}
#property(nonatomic, readonly) DJIFlightControllerState *flightControllerState;
+ (id)sharedFlightController;
#end
#implementation RCTBridgeDJIFlightController
DJIFlightControllerState *flightControllerState;
#synthesize flightControllerState;
+ (id)sharedFlightController {
static RCTBridgeDJIFlightController *sharedFlightControllerInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedFlightControllerInstance = [[self alloc] init];
});
return sharedFlightControllerInstance;
}
- (id)init {
// I also tried this to make sure the shared instance was returned but no luck
//if (sharedFlightControllerInstance != nil) {
// return sharedFlightControllerInstance;
//}
if (self = [super init]) {
flightControllerState = nil;
}
return self;
}
-(void)flightController:(DJIFlightController *)fc didUpdateState:(DJIFlightControllerState *)state {
flightControllerState = state;
}
#end
Swift class calling singleton and accessing values
class VirtualStickController {
var flightControllerSharedInstance: RCTBridgeDJIFlightController
override init() {
self.flightControllerSharedInstance = RCTBridgeDJIFlightController.sharedFlightController()
}
func getFlightControllerState() {
if let state = flightControllerSharedInstance.flightControllerState {
print("FLIGHT CONTROLLER STATE: \(state)") // always null
} else {
print ("NULL")
}
}
DJIFlightControllerState *flightControllerState;
#synthesize flightControllerState;
There is no need to use #synthesize for properties in (modern) Objective-C except in special circumstance.
The property flightControllerState is an instance property and will be synthesised (with or without the #synthesize) using a hidden instance variable for its storage.
The variable flightControllerState is a global variable, it happens to have the same name as the property but has no connection whatsoever with it.
At a guess you are changing the global variable in Objective-C and expecting to see the result in Swift via the property, you won't.
Remove the global variable and then check the rest of your code.
Apart from that your code produces a valid shared instance which can be shared between Objective-C and Swift and changes made in one language will be visible in the other.
HTH
Regarding the titular question about how to access an Objective C singleton from Swift, I would recommend an alternative. Modern convention is to declare your sharedFlightController as a class property and declare init as NS_UNAVAILABLE:
#interface RCTBridgeDJIFlightController : NSObject
...
#property (nonatomic, readonly, class) RCTBridgeDJIFlightController *sharedFlightController;
- (instancetype)init NS_UNAVAILABLE;
#end
The implementation would implement a getter for this class property:
#implementation RCTBridgeDJIFlightController
+ (instancetype)sharedFlightController {
static RCTBridgeDJIFlightController *sharedFlightControllerInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedFlightControllerInstance = [[self alloc] init];
});
return sharedFlightControllerInstance;
}
...
#end
Now, your Swift code can reference RCTBridgeDJIFlightController.shared, as is the convention with Swift singletons.
Regarding why you are receiving a nil for the status, there are one of two possible problems:
You Objective-C code has confusing combination of explicitly defined ivars, manual synthesis, and global variables. (See below.)
I would also suggest that you confirm whether flightController:didUpdateState: is ever getting called at all. (I don't see you ever setting the delegate of the flight controller.) Add a breakpoint or NSLog statement in that method and confirm.
On the first issue, above, I would suggest:
You should not use those commented lines in your init method. If you want to make sure that your singleton object is used, then declare init as NS_UNAVAILABLE.
Given that all your init method is doing is updating flightControllerState to nil, you can remove it entirely. In ARC, properties are initialized to nil for you.
You should not declare explicit ivar in your #interface. Let the compiler synthesize this automatically for you.
You should not #synthesize the ivar in your #implementation. The compiler will now automatically synthesize for you (and will use an appropriate name for the ivar, adding an underscore to the property name.
You should not declare that global in your #implementation.
If you want to use this sharedFlightController from Swift, you should define it to be a class property, not a class method. I know that that article suggested using a class method, but that really is not best practice.
Thus:
// RCTBridgeDJIFlightController.h
#import <Foundation/Foundation.h>
// dji imports here
NS_ASSUME_NONNULL_BEGIN
#interface RCTBridgeDJIFlightController : NSObject
#property (nonatomic, readonly, nullable) DJIFlightControllerState *flightControllerState;
#property (nonatomic, readonly, class) RCTBridgeDJIFlightController *sharedFlightController;
- (instancetype)init NS_UNAVAILABLE;
#end
NS_ASSUME_NONNULL_END
And
// RCTBridgeDJIFlightController.m
#import "RCTBridgeDJIFlightController.h"
#interface RCTBridgeDJIFlightController ()
#property (nonatomic, nullable) DJIFlightControllerState *flightControllerState;
#end
#implementation RCTBridgeDJIFlightController
+ (instancetype)sharedFlightController {
static RCTBridgeDJIFlightController *sharedFlightControllerInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedFlightControllerInstance = [[self alloc] init];
});
return sharedFlightControllerInstance;
}
- (void)flightController:(DJIFlightController *)fc didUpdateState:(DJIFlightControllerState *)state {
NSLog(#"State updated");
self.flightControllerState = state;
}
#end
The end result is that you can now use it like so:
class VirtualStickController {
func getFlightControllerState() {
if let state = RCTBridgeDJIFlightController.shared.flightControllerState {
print("FLIGHT CONTROLLER STATE: \(state)")
} else {
print("NULL")
}
}
}
Note, because the sharedFlightController is now a class property, Swift/ObjC interoperability is smart enough so the Swift code can just reference shared, as shown above.

Why my sharedInstance returns nil?

I have viewController with a global property 'notesArray", and I want to get it from AppDelegate through singleton.
Here is my appViewController.h
#import <UIKit/UIKit.h>
#interface AppViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
{
NSMutableArray *notesArray;
}
#property (nonatomic, strong) NSMutableArray *notesArray;
+ (AppViewController*)sharedManager;
#end
Here is my AppViewController.m
#implementation AppViewController
#synthesize notesArray;
+ (AppViewController*)sharedManager {
static AppViewController *sharedManager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[self alloc] init];
});
return sharedManager;
}
...
#end
My app works fine, but I need to save it's state in NSUserDefaults, so in AppDelegate/applicationDidEnterBackground: I make
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[NSUserDefaults standardUserDefaults] setObject:[AppViewController sharedManager].notesArray forKey:#"savedNotes"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
But the problem is that [AppViewController sharedManager].notesArray = nil.
I tried to set breakpoint to applicationDidEnterBackground: and all data from [AppViewController sharedManager] = nil.
Few observations:
notesArray is not a global property. It is a property on instances of AppViewController. Now in #2 we see that you intend it to be a singleton, but since ObjC doesn't really have a good way to prevent someone else from creating a second object of this type, are you sure you're not creating a second copy e.g. by creating an object of that type in a XIB? This is probably not your problem (see #3), but I just wanted to cover all the bases.
Your sharedManager method implies that you're seeing AppViewController as a singleton. That's an unusual thing to do with a view controller. Usually you would have several view controllers, and the singleton would be part of the model they share. Even better, you'd just give each view controller a pointer to the model object that has this array, and if you ever need a second list, everything will just work. Also, why is it called "manager"? It's a view controller.
Have you stepped through your code in the debugger to see what is going on? What value does notesArray have while you're trying to use it in applicationDidEnterBackground:? I do not see anywhere in your code that you initialize notesArray. So you just have an empty pointer to an object (i.e. notesArray is likely nil). You probably meant to add an init method to your singleton's class that creates an NSMutableArray instance and remembers its address in notesArray?

Objective C - object allocating good practice

I'm trying to build MVC Application with Objective-C, I'm trying to allocate and init my model once in my superclass witch is UIViewController, my idea is to do it once in superclass and to have access from every subclass of my superclass.
superclass.h
#property (nonatomic, strong) Lecturer *lecturer;
superclass.m
- (void) viewDidLoad {
[super viewDidLoad];
}
#pragma mark - GET
- (Lecturer *)lecturer {
if (!_lecturer) {
_lecturer = [Lecturer alloc]init];
}
return _lecturer;
}
My idea is to call self.lecturer from all subclasses and set/get the lecturer class property's but every time when i call self.lecturer its creating a new instance, i know i can use SINGLETONE but is there any way i can do it differently without singletone design pattern?
Thanks for attention.
How about using static variable?
- (Lecturer *)lecturer {
static dispatch_once_t onceToken;
static Lecturer *o;
dispatch_once(&onceToken, ^{
o = [[Lecturer alloc] init];
});
return o;
}
In addition dispatch_once helps to do it thread-safely.

ARC: Object - Delegate relationship

I am developing an ARC enabled project. From a view controller I am pushing MyClass,
- (void)pushMyClass {
MyClass *myClass = [[MyClass alloc] init];
[self.navigationController pushViewController:myClass animated:YES];
}
After doing some operations I am popping MyClass. The problem here is that MyClass is not getting deallocated. Following is how the classes look.
/* MyHelperClassDelegate */
#protocol MyHelperClassDelegate <NSObject>
- (void)helperDidFinishHelping:(MyHelperClass *)helper;
#end
/* MyHelperClass Interface */
#interface MyHelperClass : NSObject {
__weak id <MyHelperDelegate> delegate;
}
#property(nonatomic, weak) id<MyHelperDelegate> delegate;
- (void)startHelping;
#end
/* MyHelperClass Implementation */
#implementation MyHelperClass
#synthesize delegate;
- (void)dealloc {
delegate = nil;
}
/* MyClass */
#interface MyClass : UIViewController <MyHelperClassDelegate> {
MyHelperClass *helper;
}
#implementation MyClass {
- (void)dealloc {
helper.delegate = nil;
}
- (void)getHelp {
helper = [MyHelperClass new];
helper.delegate = self;
[helper startHelping];
}
- (void)helperDidFinishHelping:(MyHelperClass *)helper {
}
}
MyHelperClass calls a web service using NSMutalbleURLRequest & NSURLConnection to fetch some data and saves it to user defaults.
One thing to notice here is, if I comment the line helper.delegate = self;, then MyClass gets deallocated.
What to do to make MyClass get deallocated when it is popped out of navigation controller?
Thanks.
Your delegate code looks correct (except your use of an ivar, you don't show a #synthesize so you may have _delegate and delegate both). Its quite likely that something else is retaining MyClass. What I suggest you do is add a NSLog to your MyClass dealloc. Then push it, and immediately hit the back button and see if its dealloc'd or not. If not, then take a hard look at what you do in viewDidLoad et al and start commenting out sections of that code until you can get the dealloc.
Also, I assume you don't keep a strong reference in the class that pushes the MyClass object.
I agree with Chuck that one cannot say much from the code provided. But one reason why the MyClass object is not deallocated might be that it is retained by your helper object since delegate is declared as strong, and the MyClass object has the property helper also declared as strong. In this case you had a retain cycle, and none of them can be released.
The trick could possibly lie within the fact that you use NSURLConnection. It is not specified how you use this class with the code that you've provided, but please note the special considerations referenced in the NSURLConnection class reference:
Special Considerations: During the download the connection maintains a
strong reference to the delegate. It releases that strong reference
when the connection finishes loading, fails, or is canceled.

iOS keeping objects in scope that perform task in background

How do I keep an object in scope, which performs a task in background, that was instantiated from a method of another object?
See the following example. The display method of object A instantiates object B and calls its display method. When the B object's display method, starts an asynchronous task, control goes back to A::display method, which returns, and bObject gets deallocated. When bObject's data/method are accessed in the background thread, this will throw exception.
What is the best way of maintaining the scope for object B? I can declare the object B as a member variable for A, but this will increase the scope for the entire lifespan of object A.
#implementation A
-(void) display
{
B* bObject = [[B alloc] init];
[bObject display];
}
#end
#implementation B
-(void) display
{
dispatch_async((dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0),{
self.data = 5;
---------
});
#end
Previously in iOS you would be able to do this and then assuming your object created returned with some delegate callback you could release it. Now you will have to make "bObject" a member variable for your class and either set it to nil when you are done with it or wait for A to be destroyed which will put bObject out of scope. I'm assuming this is with ARC and as soon as "display" finishes your local variable is out of scope and is deallocated because of ARC.
you can add this to A:
#property (nonatomic, strong)B *bObject;
You can create an iVar B *bObject in A's interface. Just make sure you are not releasing bObject anywhere. Also, you may want to not reference self from the block in bObject.
#interface A : NSObject
#property (strong, nonatomic) B *bObject;
#end
#implementation A
- (void)display {
self.bObject = [[B alloc] init];
[self.bObject display];
}
#end
#implementation A
- (void)display {
B *safeSelf = __block self;
dispatch_async((dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0),{
safeSelf.data = 5;
----
});
}
#end

Resources