iOS block object without copying? - objective-c

Recently I learn that if I need to keep a block object, I should copy the block, because it's created on the stack.
Then I review some of my code. I found that I was doing something like:
#implementation MyView
...
-(void) addButton:(NSString *)title clickAction:(void ^(void)) action {
[self.buttons addObject:#[ title, action ] ];
}
-(void) createButtons { ... }
...
#end
...
// Somewhere in my project:
-(void) init {
MyView *view = [MyView new];
[view addButton:#"OK" clickAction:^{ NSLog(#"OK"); }];
[view addButton:#"Cancel" clickAction:^{ NSLog(#"Cancel"); }];
}
Basically, I add some buttons info to MyView. At some point, MyView will create the buttons. When the button is click, MyView will find out the corresponding clickAction, and invoke it.
The weird part is that this program works fine, no exception, no error.
So, why the program runs without copying the block object?
Other info :
* iphone app ( 4.3 - 6.0 )
* non-ARC
* XCode 4.5

Behind the scene, block implementation consists of two parts:
The executable code of the block, and
A data structure containing the values of the variables used in the block
Only the second part of the block is placed on the stack and copied to the heap memory; the first part is compiled to the code segment of your program, and does not get copied.
Since your block implementations do not reference any variable from the surrounding scope, the data portion of their blocks is empty. That is why your code works: there is simply nothing to copy! However, you should add block copy anyway, because otherwise a small change to the code of your block will break your application without the compiler noticing anything.

The compiler creates different types of blocks depending on what the block does. If a block doesn't capture any variables then it can be represented by an NSGlobalBlock (NSGlobalBlock is little more than a function pointer). I suspect that's what's happening.

Related

Cocoa class member variable allocated inside function call nil unless forced to init/load

I come from a C/C++ background and am currently learning a bit about Cocoa and Objective-C.
I have a weird behavior involving lazy initialization (unless I'm mistaken) and feel like I'm missing something very basic.
Setup:
Xcode 10.1 (10B61)
macOS High Sierra 10.13.6
started from a scratch Cocoa project
uses Storyboard
add files TestMainView.m/.h
under the View Controller in main.storyboard, set the NSView custom class as TestMainView
tested under debug and release builds
Basically, I create an NSTextView inside a view controller to be able to write some text.
In TestMainView.m, I create the chain of objects programmatically as decribed here
There are two paths:
first one is enabled by setting USE_FUNCTION_CALL to 0, it makes the entire code run inside awakeFromNib().
second path is enabled by setting USE_FUNCTION_CALL to 1. It makes the text container and text view to be allocated from the function call addNewPage() and returns the text container for further usage.
First code path works just as expected: I can write some text.
However second code path just doesn't work because upon return, textContainer.textView is nil (textContainer value itself is totally fine).
What's more troubling though (and this is where I suspect lazy init to be the culprit) is that if I "force" the textContainer.textView value while inside the function call, then everything works just fine. You can try this by setting FORCE_VALUE_LOAD to 1.
It doesn't have to be an if(), it works with NSLog() as well. It even works if you set a breakpoint at the return line and use the debugger to print the value ("p textContainer.textView")
So my questions are:
is this related to lazy initialization ?
is that a bug ? is there a workaround ?
am I thinking about Cocoa/ObjC programming the wrong way ?
I really hope I am missing something here because I cannot be expected to randomly check variables here and there inside Cocoa classes, hoping that they would not turn nil. It even fails silently (no error message, nothing).
TestMainView.m
#import "TestMainView.h"
#define USE_FUNCTION_CALL 1
#define FORCE_VALUE_LOAD 0
#implementation TestMainView
NSTextStorage* m_mainStorage;
- (void)awakeFromNib
{
[super awakeFromNib];
m_mainStorage = [NSTextStorage new];
NSLayoutManager* layoutManager = [[NSLayoutManager alloc] init];
#if USE_FUNCTION_CALL == 1
NSTextContainer* textContainer = [self addNewPage:self.bounds];
#else
NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:NSMakeSize(FLT_MAX, FLT_MAX)];
NSTextView* textView = [[NSTextView alloc] initWithFrame:self.bounds textContainer:textContainer];
#endif
[layoutManager addTextContainer:textContainer];
[m_mainStorage addLayoutManager:layoutManager];
// textContainer.textView is nil unless forced inside function call
[self addSubview:textContainer.textView];
}
#if USE_FUNCTION_CALL == 1
- (NSTextContainer*)addNewPage:(NSRect)containerFrame
{
NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:NSMakeSize(FLT_MAX, FLT_MAX)];
NSTextView* textView = [[NSTextView alloc] initWithFrame:containerFrame textContainer:textContainer];
[textView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
#if FORCE_VALUE_LOAD == 1
// Lazy init ? textContainer.textView is nil unless we force it
if (textContainer.textView)
{
}
#endif
return textContainer;
}
#endif
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
}
#end
TestMainView.h
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
#interface TestMainView : NSView
#end
NS_ASSUME_NONNULL_END
I am not familiar with cocoa that much but I think the problem is ARC (Automatic reference counting).
NSTextView* textView = [[NSTextView alloc] initWithFrame:containerFrame textContainer:textContainer];
In the .h file of NSTextContainer you can see NSTextView is a weak reference type.
So after returning from the function it gets deallocated
But if you make the textView an instance variable of TestMainView it works as expected.
Not really sure why it also works if you force it though. ~~(Maybe compiler optimisation?)~~
It seems forcing i.e calling
if (textContainer.textView) {
is triggering retain/autorelease calls so until the next autorelease drain call, textview is still alive.(I am guessing it does not get drained until awakeFromNib function returns). The reason why it works is that you are adding the textView to the view hierarchy(a strong reference) before autorelease pool releases it.
cekisakurek's answer is correct. Objects are deallocated if there is no owning (/"strong") reference to them. Neither the text container nor the text view have owning references to each other. The container has a weak reference to the view, which means that it's set to nil automatically when the view dies. (The view has an non-nilling reference to the container, which means you will have a dangling pointer in textView.textContainer if the container is deallocated while the view is still alive.)
The text container is kept alive because it's returned from the method and assigned to a variable, which creates an owning reference as long as that variable is in scope. The view's only owning reference was inside the addNewPage: method, so it does not outlive that scope.
The "force load" has nothing to do with lazy initialization; as bbum commented, that it "works" is most likely to be accidental. I strongly suspect it wouldn't in an optimized build.
Let me assure you that you do not need to go around poking properties willy-nilly in Cocoa programming. But you do need to consider ownership relations between your objects. In this case, something else needs to own both container and view. That can be your class here, via an ivar/property, or another object that's appropriate given the NSText{Whatever} API (which is not familiar to me).

Synthesized copy property does not get deallocated with ARC

in my project I'm managing several Drawing objects. I'm trying to add a copy of a SmartPath object to a drawing object. This works. But when the drawing gets deallocated the SmartPath does not. I have put some extra code in the dealloc of the Drawing to explicitly set clear the pointer to the SmartPath. For some reason this works (the retain count was 1). I know I can probably copy the SmartPath and assign that to a strong parameter to fix this leak. But I'm relatively new to IOS and want to know how to use the copy parameters properly in combination with ARC.
Here is the code:
Drawing.h:
#interface Drawing : NSObject{
#private
SmartPath* rawLinePath; //path that is build up from straight lines between input points
SmartPath* smoothLinePath; //smoothened version of rawLinePath
}
#property(atomic,copy)SmartPath* rawLinePath;
#property(atomic,copy)SmartPath* smoothLinePath;
Drawing.m
#implementation Drawing
#synthesize rawLinePath;
#synthesize smoothLinePath;
-(id)init
{
if (self = [super init])
{
[NSThread detachNewThreadSelector:#selector(pointMonitor)
toTarget:self
withObject:nil];
}
return self;
}
-(void)dealloc{
rawLinePath=nil;
}
SmartPath.m
/*
* Init - set all variables in the correct state
*/
-(id)init
{
if (self = [super init])
{
visible=TRUE;
color = [UIColor redColor].CGColor;
width = SMARTPATH_LINE_WIDTH;
path = CGPathCreateMutable();
lock = [[NSLock alloc]init];
}
return self;
}
/*
* dealloc - clean up after self
*/
-(void)dealloc{
CGPathRelease(path);
}
/*
* copy method to be able to pass a SmartPath to a copy property
*/
-(id)copyWithZone:(NSZone *)zone{
SmartPath *pathCopy = [[SmartPath allocWithZone: zone] init];
pathCopy.visible =visible;
pathCopy.color = color;
pathCopy.width = width;
return pathCopy;
}
I hope any of you knows the answer to this problem.
Best regards
Your problem is your call to -detachNewThreadSelector:toTarget:withObject:. This retains target, which is self, and does not release it until pointMonitor exits. I suspect this never happens, so you've effectively created a retain loop.
You should almost never use -detachNewThreadSelector:toTarget:withObject:. It can create an unbounded number of threads. Instead, you should generally use dispatch queues, NSTimer, NSOperation or other async mechanisms. NSThread objects are generally only appropriate for long-lived producer/consumer threads (and usually those are still handled better with the newer tools like dispatch queues).
I'm not certain what pointMonitor does, but is there any reason it needs its own thread at all? You can do a lot of very good Cocoa development and never fork a thread. Could you use an NSTimer here? Note that most of these techniques retain their target until they fire (just like NSThread). If they didn't, you'd crash when they fired.
Without knowing what you're trying to do, I'm not certain which approach to recommend. You may want to put that together as a new question.
By not starting instance variable names with an underscore, you end up with code where you never know whether you are using an accessor method or an instance variable. As a result, you can never be sure whether a copy is made or not.
If you do that in other places, there's a good chance that a reference to your SmartPath object gets stuck somewhere. And what are you doing creating NSLock objects? Do you need to do anything that #synchronized can't do with much less code?
And if you use a newer Xcode version, get rid of all the instance variables and #synthesize statements. Just declare the properties.
And excuse me, but detaching a thread from an init method is just sick.

Why gets this initializer argument destroyed?

I have an initializer with two arguments:
-(id) initWithSourceFrame:(CGRect)sourceViewFrame mappedFrame:(CGRect)mappedViewFrame {
CGRect copy = mappedViewFrame;
self = [super init];
if (self) {
// not able to access mappedViewFrame here..
// copy is ok
// doing initialization here...
}
return self;
}
It seemed to get wrong values from the second argument (mappedViewFrame). When looking for the error, I found out that mappedViewFrame gets destroyed (overridden in memory?).
This can be easily observed in the debugger:
Debugger Screenshot on flickr (I cannot post images yet)
The copy is still holding the original values, so using the copy was a workaround in this case. But of course I want to understand why this could happen.
The class is a direct subclass of NSObject, the whole project is a OS X native app.
The first argument was never destroyed. The problem does not relate to the values passed in. I switched them and it was always the second which was corrupted.
For example, I called the initializer with these example arguments (different from those in the debugger screenshots), and the error occured in the same way:
Mapper *mapper = [[Mapper alloc] initWithSourceFrame:CGRectMake(0, 0, 100, 100) mappedFrame:CGRectMake(0,0, 200,200)];
The method declaration:
-(id) initWithSourceFrame:(CGRect) sourceViewFrame mappedFrame:(CGRect) mappedViewFrame;
I'm somewhat new to Objective-C, so I am sorry if I missed something obvious. However, it looks strange that an argument does not keep valid during method invocation.
I stumbled upon a possible solution while I tried to reproduce the error using NSLog instead of the debugger.
The funny thing was, the logged values were correct. Just before the call to NSLog the debugger shows still the garbage output as in the screenshots. But after accessing the value (by NSLog or another programmatic access) the debugger shows the correct values.
So it looks more like a problem with the debugger. The wrong display of values is more likely to appear if some objects are allocated.
Remains the question: Why did my program not work? I now removed the 'copy' variable (from line 2) and access mappedViewFrame directly and everything works as expected. So the same code which didn't work before now works, I think this can be of two reasons:
There was another error in my implementation, which caused the wrong computation results. When introducing the 'copy' variable (after seeing the weird debugger output), I fixed the other error without noticing. Afterwards, I credited the fix to my new copying solution.
An unknown factor caused the variable to be distorted when I first encountered the problem and does not do so now. Well, to be shure I left the copy there, and check if the value remains unchanged. If this will happen, I will post it here. Until then, the first theory is much more likely.
This thing teached me not to trust the debugger, and instead use logging when in doubt, which is a pain. If anybody knows what causes the strange debugger output or how to fix it I would greatly appreceate it. If I have time I will do further look into when this does appear, if it happens just in xcode or is a problem of the gnu debugger in general.
I have tried this quickly in a few minutes. I've never really tried to create a Mac OS X project, just Foundation and iPhone projects, but the idea seems the same. I created a Cocoa Application project "Stack1". I'm assuming you have done the same, because I don't think a Foundation project would support CGRect structs without manually importing CGGeometry.h or something... Anyway, I set up a new Objective C class called "Mapper" and implemented the initWithSourceFrame:mappedFrame: method you specified.
Mapper.h
#import <Cocoa/Cocoa.h>
#interface Mapper : NSObject {
}
-(id) initWithSourceFrame:(CGRect) sourceViewFrame mappedFrame:(CGRect) mappedViewFrame;
#end
Mapper.m
#import "Mapper.h"
#implementation Mapper
-(id) initWithSourceFrame:(CGRect)sourceViewFrame mappedFrame:(CGRect)mappedViewFrame {
NSLog(#"height: %f, width: %f", mappedViewFrame.size.height, mappedViewFrame.size.width);
self = [super init];
NSLog(#"height: %f, width: %f", mappedViewFrame.size.height, mappedViewFrame.size.width);
if (self) {
NSLog(#"height: %f, width: %f", mappedViewFrame.size.height, mappedViewFrame.size.width);
}
return self;
}
#end
I didn't alter Stack1AppDelegate.h.
Stack1AppDelegate.m
#import "Stack1AppDelegate.h"
#import "Mapper.h"
#implementation Stack1AppDelegate
#synthesize window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
Mapper *mapper = [[Mapper alloc] initWithSourceFrame:CGRectMake(0, 0, 100, 100) mappedFrame:CGRectMake(0,0, 200,200)];
}
#end
All my code did was allocate a Mapper instance as yours did and also use NSLog to print the width and height before [super init], after [super init], and also within the if (self) block.
Here's the log:
2011-01-23 16:09:07.167 Stack1[935:a0f] height: 200.000000, width: 200.000000
2011-01-23 16:09:07.170 Stack1[935:a0f] height: 200.000000, width: 200.000000
2011-01-23 16:09:07.170 Stack1[935:a0f] height: 200.000000, width: 200.000000
I've also uploaded the whole project here:
http://ak.net84.net/files/temp/Stack1.zip

Why can't my singleton class return a value that will stay in scope

Stick with me. I'm visually impaired, have never used this site before, and will probably not post this in precisely the format that you are all used to. I apologize for any unintentional faux pas's herein.
Using Objective-C in an iOS project…
I have a singleton class, set up in what appears to be the usual way for Objective-C. It is, in the main, a series of methods which accept NSString values, interprets them, and return something else. In the code below, I'm simplifying things to the barest minimum, to emphasize the problem I am having.
From the singleton class:
- (NSUInteger) assignControlState:(NSString *)state {
// excerpted for clarity...
return UIControlStateNormal; // an example of what might be returned
}
Now, an instance of another class tries to use this method like so:
- (void) buttonSetup:(UIButton*)button {
[button setTitle:#"something" forState:[[SingletonClass accessToInstance] assignControlState:#"normal"]];
}
This code actually works. HOwever, when the system goes to draw the UI which includes the button whose title was set in this way, an EXC_BAD_ACCESS error occurs.
If the assignControlState method is moved into the same class as the buttonSetup method, no error is generated.
I'm guessing this is something about Apple's memory management that I'm not fully understanding, and how things go in and out of scope, but for the life of me, I can't figure out where I'm going wrong.
HOpe someone can help. Thanks.
The problem is in your accessToInstance method. I'll bet you are under-retaining. The implementation should be more like this:
static SingletonClass *sSingletonClass = nil;
#implementation
+ (id)accessToInstance {
if (sSingletonClass == nil) {
sSingletonClass = [[[self class] alloc] init];
}
return sSingletonClass;
}
#end
Now, if your program is following normal memory management rules, the singleton will stay around. You can check by writing:
- (void)dealloc {
[super dealloc]; // <-- set a breakpoint here.
}
If the debugger ever stops at this breakpoint, you know something in your program has over-released the singleton.
You know that bit you excerpted for clarity? I think you need to show us what it is because there's probably an over release in it somewhere.
Specifically, I think you release an autoreleased object. If you do that and don't use the object again, everything will carry on normally until the autorelease pool gets drained. The autorelease pool gets drained automatically at the end of the event at about the same time as the drawing normally occurs.
That would also explain the delayed crash following the NSLogs.

event scope

Given
#interface Canvas:NSView {
NSNumber * currentToolType;
...
}
declared in my .h file
and in the .m file
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
currentToolType=[[NSNumber alloc]initWithInt:1];
}
return self;
}
and further down
-(void)mouseUp:(NSEvent *)event
{
NSLog(#"tool value in event: %d",[currentToolType intValue]);
//rest of code
}
-(NSBezzierPath *)drawPath:(NSRect)aRect
{
NSLog(#"tool value in draw: %d",[currentToolType intValue]);
//rest of drawPath method code that uses the value of currentToolType in a switch statment
}
-(IBAction)selectToolOne:(id)sender
{
[currentToolType release];
[currentToolType = [[NSNumber alloc]initWithInt:0];
}
-(IBAction)selectToolTwo:(id)sender
{
[currentToolType release];
[currentToolType = [[NSNumber alloc]initWithInt:1];
}
The action methods are the only place where currentToolType is changed. But, for some reason, it seems to be a different instance of currentToolType in the mouseUp. I did not write (or synthesize) accessors for the var as it is used only by itself. I noticed that initWithFrame is called twice - I'm assuming it's for the parent window and the NSView?
What am I missing?THANKS!
This is an XCode generated Document based app using COCOA and Obj-C. I'm new at both.
You mention that initWithFrame: is called twice. Your initWithFrame: should only be called once (unless you happen to have two Canvas views).
Is it possible you have the Canvas view in your nib/xib file and are also creating another in code (with alloc/initWithFrame:)?
In which case you have two Canvas objects. You probably have one hooked up to your controls and the other one is in the window (and thus responding to the mouseUp: and it is giving you the same value every time).
If you have the Canvas view setup in IB, you can fix this problem by removing your code that is creating the second one.
You've probably run in to a special case: NSNumber could have cached instances to represent commonly-used numbers.
Two observations, though:
You're wasting a whole lot of memory using NSNumber when you could be simply using NSIntegers or maybe an old-fashioned enumerated type, completely avoiding the object overhead.
You never actually showed your code for when you look at the instances of NSNumber; without it, there's not really enough information here to answer your question.