How to call block defined as property in another class? - ios

I am calling the block from second class which has been declared and maintained in first class.
In ViewController.h
#property (copy) void (^simpleBlock)(NSString*);
In View Controller.m
- (void)viewDidLoad {
[super viewDidLoad];
self.simpleBlock = ^(NSString *str)
{
NSLog(#"Hello My Name is: %#",str);
};
}
In SecondViewController.m
In ViewDidload
ViewController *VC = [[ViewController alloc]init];
VC.simpleBlock(#"Harjot");//bad execution error
Please suggest me some solutions because the code is giving me bad execution error.
How can i call the block in any another way?

It's the correct way of run the block. However if you try to run a block that is nil you'll have a crash - so you should always check that it's not nil before calling it:
ViewController *vc = [[ViewController alloc] init];
if (vc.simpleClock) {
vc.simpleBlock(#"Harjot");//this will not get called
}
The reason why in your case the block is nil is because you set it in viewDidLoad - however viewDidLoad is not called until its view is ready to go on screen. For testing purposes try to move the assignment from viewDidLoad to init and this should work:
- (instancetype)init
{
self [super init];
if (self) {
_simpleBlock = ^(NSString *str)
{
NSLog(#"Hello My Name is: %#",str);
};
}
return self;
}

Related

Is it bad design to set self.delegate = self

I have a UIViewController subclass (say MyViewController).
MyViewController.h
#protocol TargetChangedDelegate
-(void) targetChanged;
#end
#interface MyViewController
#property (weak) id<TargetChangedDelegate> targetChangedDelegate;
-(void) doSomethingOnYourOwn;
#end
MyViewController.m
#implementation MyViewController <TargetChangedDelegate>
-(void) doSomethingOnYourOwn
{
// DO some stuff here
// IS THIS BAD ??
self.targetChangedDelegate = self;
}
-(IBAction) targetSelectionChanged
{
[self.targetChangedDelegate targetChanged];
}
-(void) targetChanged
{
// Do some stuff here
}
#end
Based on certain conditions a class that instantiates an instance of MyViewController may decide to set itself as the delegate or not.
Foo.m
#property(strong) MyViewController *myVC;
-(void) configureViews
{
self.myVC = [[MyViewController alloc] init];
[self.view addSubview:self.myVC];
if (someCondition)
{
self.myVC.targetChangedDelegate = self;
}
else
{
[self.myVC doSomethingOnYourOwn]
//MyViewController sets itself as the targetChangedDelegate
}
}
With reference to the code snippet above, I have the following question:
Is it a violation of MVC/delegation design pattern (or just a bad design) to say:
self.delegate = self;
There's absolutely no problem with setting the delegate to self. In fact it is a good way to provide default delegate functionality if a delegate is not set by somebody else.
Obviously, the delegate property has to be declared weak otherwise you get a reference cycle.
To expand a bit, having read the wrong answer and wrong comments above, if you allow an object to be its own delegate, your code is cleaner because you do not have to surround absolutely every single delegate call with
if ([self delegate] != nil)
{
[[self delegate] someMethod];
}
else
{
[self someMethod];
}
Its not proper way to assign self.delegate = self.
for your functionality, you can do this:
-(void) doSomethingOnYourOwn
{
// DO some stuff here
self.targetChangedDelegate = nil;
}
and when using delegate:
if(self.targetChangedDelegate != nil && [self.targetChangedDelegate respondsToSelector:#selector(targetChanged)]
{
[self.targetChangedDelegate targetChanged];
}
else
{
[self targetChanged];
}
It is bad design to set self.delegate = self; it should be another object. Delegation via protocols are an alternative design to subclassing and you can read more about delegation here:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html
And here is more on protocols:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Protocol.html

Singleton class return nil value for its NSString property

I have a singleton class, and I have a property declared in it:
#property (nonatomic, strong) NSString *currentTableName;
+ (SuperNoteManager*)sharedInstance;
.m file:
+ (SuperNoteManager*)sharedInstance
{
static SuperNoteManager *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[SuperNoteManager alloc] init];
});
return _sharedInstance;
}
When I run my app for the first time, there is no data in the data base,so it shows the EmptyViewController.
#property (nonatomic, strong) SuperNoteManager *myManager;
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
_myManager=[SuperNoteManager sharedInstance];
}
-(void)changeRootView{
UIStoryboard *storyboard=[UIStoryboard storyboardWithName:#"Main" bundle:nil];
HomeViewController *hVC=[storyboard instantiateViewControllerWithIdentifier:#"HomeViewController"];
UINavigationController *mNavVC=[storyboard instantiateViewControllerWithIdentifier:#"MainNavigationController"];
mNavVC.viewControllers=#[hVC];
[[UIApplication sharedApplication].keyWindow setRootViewController:mNavVC];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if ( [_myManager checkForDataInAllTables]) {
NSLog(#"All tables are empty");
}else{
//a note is saved, show home view controller
if (![_myManager isDatabaseEmpty]) {
[self changeRootView];
}
}
}
There is + button on NavigationBar on EmptyNotesViewController, and on tap '+',
NotesViewController is pushed from EmptyNotesViewController.
In the NotesViewController, after I write some notes, I save the notes in database:
NotesViewController:
#property (nonatomic,strong) SuperNoteManager *myManager;
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
_myManager.currentTableName=#"WorkTable";
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController) {
NSLog(#"going back");
[self insertTextintoDatabase]; //Text is inserted . I double checked
}
}
And then When I go back to my EmpytNotesViewController, I check for data, and if data is present, I change the rootViewController as it is not EmptyNotesView anymore.
So When I go back to my EmptyNotesViewController:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if ( [_myManager checkForDataInAllTables]) {
NSLog(#"All tables are empty");
}else{
//a note is saved, show home view controller
//Put a breakpoint here
if (![_myManager isDatabaseEmpty]) {
[self changeRootView];
}
}
}
Here at the breakpoint _myManager.currentTableName is nil. why?
I set it in the NotesController, and it became nil when it come back to the EmptyNotesController.
I thought once a value is set in singleton, it will persist as long as the app is closed/killed.
Note: I have declared the property of my Singleton class as strong and also all the properties in the singleton are declared as strong.
It appears like you never get a reference to the SuperNoteManager singleton in NotesViewController, like you did in your EmptyNotesController.
Therefore the currentTableName property never gets set in the first place.
You want to insert:
_myManager = [SuperNoteManager sharedInstance];
in your -viewDidAppear: before you set the currentTableName property.

self always returns nil inside viewController's methods

For some reason I am experiencing an issue where, inside of a viewController's code (which runs well after the viewDidLoad method is called), self always returns nil. (Running on iPad Air with iOS 9.2.1, app built with 9.2 as the target).
In the initialization code we have a run-of-the-mill initter:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
};
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) { }
return self;
}
The viewController gets instantiated like this:
myVC * myVC = [[myVC alloc] initWithNibName:nil bundle:nil];
The names of the myVC class and .xib file are the same.
There's a property called:
#property (weak, nonatomic) IBOutlet UILabel *ipAddressLabel;
In a method inside myVC, I have a method like this:
- (void) networkPropertiesDidChange:(NSDictionary *)properties {
void (^completionBlock)(UIAlertAction *) = ^(UIAlertAction *action) {
[self.serialNumberField setEnabled:false];
};
if([properties[#"result" isEqualToString #"error"]) {
[Utility showBasicAlertControllerWithText:#"Device not registered." completionBlock:completionBlock sender:self];
return;
}
else if ([properties[#"result"] isEqualToString #"ipChange"]) {
NSString *newAddy = [[NSString alloc] initWithString:properties[#"ipAddress"]];
self.ipAddressLabel.text = newAddy;
return;
}
else return;
}
However the address label just goes blank.
When I step through the code while it executes, on the line where I initialize newAddy, self is not nil, and ipAddressLabel is not nil. However when the runtime hits self.ipAddressLabel.text, then it goes through initWithNibName, and returns nil for self! Then ipAddressLabel gets set to nil, and then it turns blank on the view.
At this point myVC has already successfully loaded and is on the screen. So I cannot understand why self is returning nil for self, inside this method... it's very odd.
If I delete my override of the initWithNibName method, then everything just works perfectly. If I check if (self != nil) at the beginning of initWithNib name before setting self=[super... ], then the view draws about 1" too low on the screen and gets cut off.
Just trying to understand why this was happening. Thanks.
The problem is here:
void (^completionBlock)(UIAlertAction *) = ^(UIAlertAction *action) {
[self.serialNumberField setEnabled:false];
};
It needs to be weakSelf...
__weak typeof(self) weakSelf = self;
void (^completionBlock)(UIAlertAction *) = ^(UIAlertAction *action) {
[weakSelf.paxSerialNumberField setEnabled:false];
};
Don't ask me why, my brain exploded.

Why is a UINavigationController failing to take ownership of a block callback parameter using ARC

Given the following code example (iOS 7, Xcode 5):
/**
* SampleProvider Class
*/
typedef void(^RequestCallback)(UIViewController *result);
static NSString * const cControllerRequestNotification = #"controllerRequestNotification";
static NSString * const cRequestClassNameKey = #"className";
static NSString * const cRequestCallbackKey = #"callback";
#interface SampleProvider : NSObject
+ (void)requestControllerForClassName:(NSString *)className completion:(RequestCallback)callback;
#end
#interface SampleProvider ()
- (UIViewController *)controllerForClassName:(NSString *)className;
- (void)didReceiveControllerRequest:(NSNotification *)n;
#end
#implementation SampleProvider
#pragma mark - Overrides
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (id)init {
self = [super init];
if( self ) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didReceiveControllerRequest:) name:cControllerRequestNotification object:nil];
}
return self;
}
#pragma mark - Public API
+ (void)requestControllerForClassName:(NSString *)className completion:(RequestCallback)callback{
NSDictionary *requestInfo = #{ cRequestClassNameKey : className, cRequestCallbackKey : [callback copy] };
[[NSNotificationCenter defaultCenter] postNotificationName:cControllerRequestNotification object:requestInfo];
}
#pragma mark - Private API
- (UIViewController *)controllerForClassName:(NSString *)className {
UIViewController *result = nil;
Class controllerClass = NSClassFromString(className);
if( (nil != controllerClass) && ([controllerClass isSubclassOfClass:[UIViewController class]]) ) {
result = [[controllerClass alloc] init];
}
return result;
}
- (void)didReceiveControllerRequest:(NSNotification *)n {
NSDictionary *requestInfo = [n object];
NSString *className = requestInfo[cRequestClassNameKey];
RequestCallback callback = requestInfo[cRequestCallbackKey];
UIViewController *result = [self controllerForClassName:className];
if( nil != callback ) {
callback(result);
}
}
#end
/**
* SampleViewController Class
*/
#interface SampleViewController : UIViewController
#end
#implementation SampleViewController
#pragma mark - Overrides
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
NSString *className = #"ClassName";
[SampleProvider requestControllerForClassName:className completion:^(UIViewController *result) {
if( nil != result ) {
// Result is valid pointer, not a zombie.
[self.navigationController pushViewController:result animated:YES];
// Result is released, not nil.
} else {
NSLog(#"Unable to load controller with class name: %#", className);
}
}];
}
#end
Why would my UINavigationController fail to take ownership of the callback controller, received by SampleProvider's public class method, even after showing the view?
I'm seeing the following behavior:
The new controller class is properly allocated and returned via the callback method. Upon entering the callback the result parameter is pointing to valid memory.
The new controller is pushed to my UINavigationController's navigation stack.
The newly pushed controller's "viewDidLoad" method is called.
When inspecting the UINavigationController's "viewControllers" property, the newly pushed controller is referenced in the array.
The newly push controller is is deallocated while UINavigationController pushViewController:animated: is still executing.
The new controller is now a zombie.
Thank you for any assistance.
I don't have a clearcut answer because the answer may be in code you haven't posted -- the code you have posted looks valid apart from two observations (which could lead you to an answer):
Should that isKindOfClass be isSubclassOfClass? -isKindOfClass: is an
instance method on NSObject, not a class method.
Calling pushViewController: synchronously during viewDidLoad seems
dangerous. It's quite possible that the state of the view hierarchy
is not stable at that time. That push should happen in response to
some other discrete event, I'd think. Try making that push (or the
entire requestControllerForClassName:) asynchronous via
dispatch_async, as a test, and see if that solves your problem.

getInstance before initWithNibName:

I have a class named IGMapViewController
In that I have
static IGMapViewController *instance =nil;
+(IGMapViewController *)getInstance {
#synchronized(self) {
if (instance==nil) {
instance= [IGMapViewController new];
}
}
return instance;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// more code
instance = self;
}
return self;
}
If use the object in more then 1 class but only use initWithNibName in one class.
In a class named IGRouteController in the init method i use _mapViewController = [IGMapViewController getInstance]; this happens before the initWithNibName gets executed in another class.
In IGRouteController I have a method updateRouteList in that method I use:
[_mapViewController drawSuggestedRoute:suggestedRoute];
It all does run but I can't see the result.
If i use:
IGMapViewController *wtf = [IGMapViewController getInstance];
[wtf drawSuggestedRoute:suggestedRoute];
Then it does work great.
So is it possible to get a instance and init later with a nib?
I believe I see what you are trying to accomplish. You want to initialize a singleton instance of your class from a nib. Correct?
When you initialize your instance, you are using [IGMapViewController new] which is presumably not the intended behavior. How about this (untested...)?
+ (id)sharedController
{
static dispatch_once_t pred;
static IGMapViewController *cSharedInstance = nil;
dispatch_once(&pred, ^{
cSharedInstance = [[self alloc] initWithNibName:#"YourNibName" bundle:nil];
});
return cSharedInstance;
}
clankill3r,
You should avoid creating singleton UIViewControllers (see comments in this discussion UIViewController as a singleton). This has been also highlighted by #CarlVeazey.
IMHO, you should create a UIViewController each time you need it. In this case your view controller would be a reusable component. When you create a new instance of your controller, just inject (though a property or in the initializer the data you are interested in, suggestedRoute in this case).
A simple example could be the following:
// YourViewController.h
- (id)initWithSuggestedRoute:(id)theSuggestedRoute;
// YourViewController.m
- (id)initWithSuggestedRoute:(id)theSuggestedRoute
{
self = [super initWithNibName:#"YourViewController" bundle:nil];
if (self) {
// set the internal suggested route, e.g.
_suggestedRoute = theSuggestedRoute; // without ARC enabled _suggestedRoute = [theSuggestedRoute retain];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self drawSuggestedRoute:[self suggestedRoute]];
}
For further info about UIViewControllers, I really advice to read two interesting post by #Ole Begemann.
Passing Data Between View Controllers
initWithNibName:bundle: Breaks Encapsulation
Hope that helps.

Resources