Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Set delegate to nil when deallocating #134

Closed
wants to merge 5 commits into from

5 participants

Tomáš Znamenáček Jonathan Sterling Kolin Krewinkel Zachary Drayer Simon Blommegård
Tomáš Znamenáček

Otherwise the code crashes when the KKGridView instance gets deallocated. It appears that [UIScrollView dealloc] releases the delegate (which gets deallocated) and then there’s a call to [KKGridView setDelegate:] with a nil argument. The call goes through the setter, which references the already freed _delegate and EXC_BAD_ACCESS ensues.

Zachary Drayer

I agree, awesome change! :D

However, why access the delegate through a property now, instead of direct ivar access like elsewhere?

And, I think that it would make sense to keep the gridDelegate property around (where -setGridDelegate: does nothing but call -setDelegate: on self) and marked as deprecated (with __attribute__((deprecated))), instead of getting rid of it completely. This way, people who update KKGridView won't suddenly find their build broken.

Collaborator

I'm with @jonsterling on this one, it's still in an early development phrase.

The phase of development doesn't matter much, if people are using it in the wild.

Collaborator
Tomáš Znamenáček zoul Set delegate to nil when deallocating.
Otherwise the code crashes when the KKGridView instance gets
deallocated. It appears that [UIScrollView dealloc] releases the
delegate (which gets deallocated) and then there’s a call to [KKGridView
setDelegate:] with a nil argument. The call goes through the setter,
which references the already freed _delegate and EXC_BAD_ACCESS ensues.
343fde5
Tomáš Znamenáček

This appears to be a bit more complex. After some more debugging I realized that the code still crashes even when setting the delegate to nil in dealloc. It looks like the property is not weak, even though my target supports weak properties. I’d say it’s because of the UIScrollView rewrite. The delegate property is now defined like this:

@property (nonatomic, kk_weak) IBOutlet id <KKGridViewDelegate> delegate;

…but since we piggy-back on the delegate property of UIScrollView, there is just a @dynamic delegate in the implementation and therefore the property modifiers from the the KKGridView header are not used. The property is synthesized according to the declaration in UIScrollView.h:

@property(nonatomic, assign) id<UIScrollViewDelegate> delegate;

In other words, the delegate is not a weak property, even though the KKGridView headers make it seem so. The patch in the pull request (343fde5) is useless.

Tomáš Znamenáček zoul Quick and dirty fix for the weak delegate problem (#134).
The declaration of the delegate property was changed to make it obvious
that the property isn’t weak, and the delegate is (safely) set to nil
during deallocation to cover at least some use cases where the calling
code expects the delegate to be a weak property.
4d02d22
Jonathan Sterling
Collaborator

So, UIScrollView almost certainly does not release its delegate; as you can see, the property is assign, which means that UIScrollView claims to not exert ownership over its delegate's memory. So, there's got to be something else weird that's going on here.

Tomáš Znamenáček

You can discard the first comment, the rest explains the issue better. What UIScrollView probably does is setting the delegate to nil ([self setDelegate:nil]) during dealloc. This call goes through the accessor in KKGridView where the code touches self.delegate. If the delegate is already released by then (and not set to nil), we get the segfault. This is only problem for people who rely on the delegate property being weak, because others set the delegate to nil when the delegate is getting deallocated. I hope this makes sense, it’s a bit complicated.

Jonathan Sterling
Collaborator

Ah, now I follow. Thanks!

Kolin Krewinkel

Should I close this?

Tomáš Znamenáček

The main problem still persists. In your master the delegate property is marked weak, but is never weak, even when compiled with toolchain that supports weak properties. This is a nasty bug that can easily crash even in very simple scenarios. I did not think about a proper fix much yet, but I'd suggest to have a separate weak gridDelegate property with its own instance variable (ie. not backed up by UIScrollView's delegate).

Jonathan Sterling
Collaborator

Yeah, I'm thinking you're right. Overriding a superclass's delegate seems rather unsafe at this point. Perhaps for now we should mark delegate as assign (to avoid setting incorrect expectations), until we fix this.

Tomáš Znamenáček

This is exactly what my 4d02d22 does, along with one more simple precaution for people who already depend on the delegate being a weak property. Other commits have crept into the pull request again because I'm a Git loser :-), but you can cherry-pick.

Jonathan Sterling
Collaborator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 11, 2012
  1. Jonathan Sterling

    Minor OCD stuff

    jonsterling authored
Commits on Apr 13, 2012
  1. Simon Blommegård
  2. Merge pull request #132 from blommegard/master

    authored
    KKGridViewDelegate inheritances from UIScrollViewDelegate
Commits on Apr 18, 2012
  1. Tomáš Znamenáček

    Set delegate to nil when deallocating.

    zoul authored
    Otherwise the code crashes when the KKGridView instance gets
    deallocated. It appears that [UIScrollView dealloc] releases the
    delegate (which gets deallocated) and then there’s a call to [KKGridView
    setDelegate:] with a nil argument. The call goes through the setter,
    which references the already freed _delegate and EXC_BAD_ACCESS ensues.
  2. Tomáš Znamenáček

    Quick and dirty fix for the weak delegate problem (#134).

    zoul authored
    The declaration of the delegate property was changed to make it obvious
    that the property isn’t weak, and the delegate is (safely) set to nil
    during deallocation to cover at least some use cases where the calling
    code expects the delegate to be a weak property.
This page is out of date. Refresh to see the latest.
58 KKGridView/KKGridView.h
View
@@ -29,8 +29,33 @@ typedef enum {
KKGridViewAnimationNone
} KKGridViewAnimation;
-@protocol KKGridViewDataSource;
-@protocol KKGridViewDelegate;
+@class KKGridView;
+
+@protocol KKGridViewDataSource <NSObject>
+@required
+- (NSUInteger)gridView:(KKGridView *)gridView numberOfItemsInSection:(NSUInteger)section;
+- (KKGridViewCell *)gridView:(KKGridView *)gridView cellForItemAtIndexPath:(KKIndexPath *)indexPath;
+@optional
+- (NSUInteger)numberOfSectionsInGridView:(KKGridView *)gridView;
+- (NSString *)gridView:(KKGridView *)gridView titleForHeaderInSection:(NSUInteger)section;
+- (NSString *)gridView:(KKGridView *)gridView titleForFooterInSection:(NSUInteger)section;
+- (CGFloat)gridView:(KKGridView *)gridView heightForHeaderInSection:(NSUInteger)section;
+- (CGFloat)gridView:(KKGridView *)gridView heightForFooterInSection:(NSUInteger)section;
+- (UIView *)gridView:(KKGridView *)gridView viewForHeaderInSection:(NSUInteger)section;
+- (UIView *)gridView:(KKGridView *)gridView viewForFooterInSection:(NSUInteger)section;
+- (UIView *)gridView:(KKGridView *)gridView viewForRow:(NSUInteger)row inSection:(NSUInteger)section; // a row is compromised of however many cells fit in a column of a given section
+- (NSArray *)sectionIndexTitlesForGridView:(KKGridView *)gridView;
+- (NSInteger)gridView:(KKGridView *)gridView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index;
+@end
+
+@protocol KKGridViewDelegate <NSObject, UIScrollViewDelegate>
+@optional
+- (void)gridView:(KKGridView *)gridView didSelectItemAtIndexPath:(KKIndexPath *)indexPath;
+- (void)gridView:(KKGridView *)gridView didDeselectItemAtIndexPath:(KKIndexPath *)indexPath;
+- (KKIndexPath *)gridView:(KKGridView *)gridView willSelectItemAtIndexPath:(KKIndexPath *)indexPath;
+- (KKIndexPath *)gridView:(KKGridView *)gridView willDeselectItemAtIndexPath:(KKIndexPath *)indexPath;
+- (void)gridView:(KKGridView *)gridView willDisplayCell:(KKGridViewCell *)cell atIndexPath:(KKIndexPath *)indexPath;
+@end
@interface KKGridView : UIScrollView
@@ -47,7 +72,7 @@ typedef enum {
#pragma mark - Data Source and Delegate
@property (nonatomic, kk_weak) IBOutlet id <KKGridViewDataSource> dataSource;
-@property (nonatomic, kk_weak) IBOutlet id <KKGridViewDelegate> gridDelegate;
+@property (nonatomic, assign) IBOutlet id <KKGridViewDelegate> delegate;
#pragma mark - Getters
@@ -90,30 +115,3 @@ typedef enum {
- (NSArray *)indexPathsForSelectedCells;
@end
-
-
-@protocol KKGridViewDataSource <NSObject>
-@required
-- (NSUInteger)gridView:(KKGridView *)gridView numberOfItemsInSection:(NSUInteger)section;
-- (KKGridViewCell *)gridView:(KKGridView *)gridView cellForItemAtIndexPath:(KKIndexPath *)indexPath;
-@optional
-- (NSUInteger)numberOfSectionsInGridView:(KKGridView *)gridView;
-- (NSString *)gridView:(KKGridView *)gridView titleForHeaderInSection:(NSUInteger)section;
-- (NSString *)gridView:(KKGridView *)gridView titleForFooterInSection:(NSUInteger)section;
-- (CGFloat)gridView:(KKGridView *)gridView heightForHeaderInSection:(NSUInteger)section;
-- (CGFloat)gridView:(KKGridView *)gridView heightForFooterInSection:(NSUInteger)section;
-- (UIView *)gridView:(KKGridView *)gridView viewForHeaderInSection:(NSUInteger)section;
-- (UIView *)gridView:(KKGridView *)gridView viewForFooterInSection:(NSUInteger)section;
-- (UIView *)gridView:(KKGridView *)gridView viewForRow:(NSUInteger)row inSection:(NSUInteger)section; // a row is compromised of however many cells fit in a column of a given section
-- (NSArray *)sectionIndexTitlesForGridView:(KKGridView *)gridView;
-- (NSInteger)gridView:(KKGridView *)gridView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index;
-@end
-
-@protocol KKGridViewDelegate <NSObject>
-@optional
-- (void)gridView:(KKGridView *)gridView didSelectItemAtIndexPath:(KKIndexPath *)indexPath;
-- (void)gridView:(KKGridView *)gridView didDeselectItemAtIndexPath:(KKIndexPath *)indexPath;
-- (KKIndexPath *)gridView:(KKGridView *)gridView willSelectItemAtIndexPath:(KKIndexPath *)indexPath;
-- (KKIndexPath *)gridView:(KKGridView *)gridView willDeselectItemAtIndexPath:(KKIndexPath *)indexPath;
-- (void)gridView:(KKGridView *)gridView willDisplayCell:(KKGridViewCell *)cell atIndexPath:(KKIndexPath *)indexPath;
-@end
41 KKGridView/KKGridView.m
View
@@ -22,7 +22,7 @@
NSUInteger itemCount;
};
-@interface KKGridView () <UIGestureRecognizerDelegate,UIScrollViewDelegate> {
+@interface KKGridView () <UIGestureRecognizerDelegate> {
// View-wrapper containers
NSMutableArray *_footerViews;
NSMutableArray *_rowViews;
@@ -135,7 +135,7 @@ + (void)animateIf:(BOOL)animated delay:(NSTimeInterval)delay options:(UIViewAnim
@implementation KKGridView
@synthesize dataSource = _dataSource;
-@synthesize gridDelegate = _gridDelegate;
+@dynamic delegate;
@synthesize allowsMultipleSelection = _allowsMultipleSelection;
@synthesize cellPadding = _cellPadding;
@synthesize cellSize = _cellSize;
@@ -206,6 +206,7 @@ - (void)_sharedInitialization
- (void)dealloc
{
+ [super setDelegate:nil];
[self removeObserver:self forKeyPath:@"contentOffset"];
[self removeObserver:self forKeyPath:@"tracking"];
[self removeGestureRecognizer:_selectionRecognizer];
@@ -242,12 +243,12 @@ - (void)setDataSource:(id<KKGridViewDataSource>)dataSource
}
}
-- (void)setGridDelegate:(id<KKGridViewDelegate>)gridDelegate
+- (void)setDelegate:(id<KKGridViewDelegate>)delegate
{
- if (gridDelegate != _gridDelegate)
+ if (delegate != self.delegate)
{
- _gridDelegate = gridDelegate;
-#define RESPONDS_TO(sel) [_gridDelegate respondsToSelector:@selector(sel)]
+ [super setDelegate:delegate];
+#define RESPONDS_TO(sel) [self.delegate respondsToSelector:@selector(sel)]
_delegateRespondsTo.didSelectItem = RESPONDS_TO(gridView:didSelectItemAtIndexPath:);
_delegateRespondsTo.willSelectItem = RESPONDS_TO(gridView:willSelectItemAtIndexPath:);
_delegateRespondsTo.didDeselectItem = RESPONDS_TO(gridView:didDeselectItemAtIndexPath:);
@@ -678,7 +679,7 @@ - (CGFloat)_sectionHeightsCombinedUpToRow:(NSUInteger)row inSection:(NSUInteger)
- (void)_displayCell:(KKGridViewCell *)cell atIndexPath:(KKIndexPath *)indexPath withAnimation:(KKGridViewAnimation)animation
{
if (_delegateRespondsTo.willDisplayCell) {
- [_gridDelegate gridView:self willDisplayCell:cell atIndexPath:indexPath];
+ [self.delegate gridView:self willDisplayCell:cell atIndexPath:indexPath];
}
if ([_updateStack hasUpdateForIndexPath:indexPath]) {
@@ -1347,22 +1348,20 @@ - (void)deselectItemsAtIndexPaths:(NSArray *)indexPaths animated:(BOOL)animated
}];
}
-- (void)deselectAll: (BOOL)animated
+- (void)deselectAll:(BOOL)animated
{
[KKGridView animateIf:animated delay:0.f options:0 block:^{
[self _deselectAll];
}];
}
-- (KKIndexPath*)indexPathForSelectedCell {
- if (!_allowsMultipleSelection) {
- return [_selectedIndexPaths anyObject];
- } else {
- return nil;
- }
+- (KKIndexPath*)indexPathForSelectedCell
+{
+ return !_allowsMultipleSelection ? _selectedIndexPaths.anyObject : nil;
}
-- (NSArray *)indexPathsForSelectedCells {
+- (NSArray *)indexPathsForSelectedCells
+{
return [_selectedIndexPaths allObjects];
}
@@ -1412,7 +1411,7 @@ - (void)_selectItemAtIndexPath:(KKIndexPath *)indexPath
}
if (_delegateRespondsTo.didSelectItem) {
- [_gridDelegate gridView:self didSelectItemAtIndexPath:indexPath];
+ [self.delegate gridView:self didSelectItemAtIndexPath:indexPath];
}
}
@@ -1426,7 +1425,7 @@ - (void)_deselectAll
if(_delegateRespondsTo.willDeselectItem)
{
- [_gridDelegate gridView:self willDeselectItemAtIndexPath:indexPath];
+ [self.delegate gridView:self willDeselectItemAtIndexPath:indexPath];
}
}
@@ -1436,9 +1435,9 @@ - (void)_deselectAll
- (void)_deselectItemAtIndexPath:(KKIndexPath *)indexPath
{
if (_selectedIndexPaths.count > 0 && _delegateRespondsTo.willDeselectItem && indexPath.index != NSNotFound && indexPath.section != NSNotFound) {
- KKIndexPath *redirectedPath = [_gridDelegate gridView:self willDeselectItemAtIndexPath:indexPath];
+ KKIndexPath *redirectedPath = [self.delegate gridView:self willDeselectItemAtIndexPath:indexPath];
if (redirectedPath != nil && ![redirectedPath isEqual:indexPath]) {
- indexPath = redirectedPath ? redirectedPath : indexPath;
+ indexPath = redirectedPath;
}
}
@@ -1449,7 +1448,7 @@ - (void)_deselectItemAtIndexPath:(KKIndexPath *)indexPath
}
if (_delegateRespondsTo.didDeselectItem) {
- [_gridDelegate gridView:self didDeselectItemAtIndexPath:indexPath];
+ [self.delegate gridView:self didDeselectItemAtIndexPath:indexPath];
}
}
@@ -1489,7 +1488,7 @@ - (void)_handleSelection:(UILongPressGestureRecognizer *)recognizer
KKIndexPath *indexPath = [self indexPathForItemAtPoint:locationInSelf];
if (state == UIGestureRecognizerStateEnded && _delegateRespondsTo.willSelectItem)
- indexPath = [_gridDelegate gridView:self willSelectItemAtIndexPath:indexPath];
+ indexPath = [self.delegate gridView:self willSelectItemAtIndexPath:indexPath];
if (!indexPath || indexPath.index == NSNotFound || indexPath.section == NSNotFound) {
[self _cancelHighlighting];
2  KKGridView/KKGridViewController.m
View
@@ -19,7 +19,7 @@ - (void)loadView
_gridView = [[KKGridView alloc] initWithFrame:self.view.bounds];
_gridView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
_gridView.dataSource = self;
- _gridView.gridDelegate = self;
+ _gridView.delegate = self;
self.view = _gridView;
}
Something went wrong with that request. Please try again.