Add assertion against externally setting .image in specific ASImageNode subclasses

This commit is contained in:
Michael Schneider
2016-12-04 07:15:28 -08:00
parent 42c7bb291e
commit 9adb6554fc
8 changed files with 75 additions and 16 deletions

View File

@@ -204,6 +204,7 @@
6907C2591DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m */; };
6907C25A1DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m */; };
69127CFE1DD2B387004BF6E2 /* ASEventLog.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 696F01EA1DD2AF450049FBD5 /* ASEventLog.h */; };
69309D461DF3B1B50089FA48 /* ASImageNode+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 69309D451DF3B1B50089FA48 /* ASImageNode+Private.h */; };
693117CE1DC7C72700DE4784 /* ASDisplayNode+Deprecated.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 683489271D70DE3400327501 /* ASDisplayNode+Deprecated.h */; };
69527B121DC84292004785FB /* ASLayoutElementStylePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 69527B111DC84292004785FB /* ASLayoutElementStylePrivate.h */; };
6959433E1D70815300B0EE1F /* ASDisplayNodeLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */; };
@@ -1002,6 +1003,7 @@
68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASVisibilityProtocols.m; sourceTree = "<group>"; };
6907C2561DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASObjectDescriptionHelpers.h; sourceTree = "<group>"; };
6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASObjectDescriptionHelpers.m; sourceTree = "<group>"; };
69309D451DF3B1B50089FA48 /* ASImageNode+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+Private.h"; sourceTree = "<group>"; };
69527B111DC84292004785FB /* ASLayoutElementStylePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutElementStylePrivate.h; path = AsyncDisplayKit/Layout/ASLayoutElementStylePrivate.h; sourceTree = SOURCE_ROOT; };
6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeLayout.mm; sourceTree = "<group>"; };
6959433D1D70815300B0EE1F /* ASDisplayNodeLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeLayout.h; sourceTree = "<group>"; };
@@ -1609,6 +1611,7 @@
68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */,
058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */,
058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */,
69309D451DF3B1B50089FA48 /* ASImageNode+Private.h */,
ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */,
ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.m */,
69C4CAF51DA3147000B1EC9B /* ASLayoutElementStylePrivate.h */,
@@ -1857,6 +1860,7 @@
34EFC7631B701CBF00AD841F /* ASCenterLayoutSpec.h in Headers */,
9C70F20C1CDBE9B6007D6C76 /* ASCollectionDataController.h in Headers */,
18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */,
69309D461DF3B1B50089FA48 /* ASImageNode+Private.h in Headers */,
9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */,
B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */,
ACE87A2C1D73696800D7FF06 /* ASSectionContext.h in Headers */,

View File

@@ -199,6 +199,11 @@ struct ASImageNodeDrawParameters {
#pragma mark - Setter / Getter
- (void)setImage:(UIImage *)image
{
[self __setImage:image];
}
- (void)__setImage:(UIImage *)image
{
ASDN::MutexLocker l(__instanceLock__);
if (!ASObjectIsEqual(_image, image)) {

View File

@@ -11,16 +11,17 @@
#if TARGET_OS_IOS
#import "ASMultiplexImageNode.h"
#import "ASImageNode+Private.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import "ASAvailability.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASDisplayNodeExtras.h"
#import "ASLog.h"
#import "ASPhotosFrameworkImageRequest.h"
#import "ASEqualityHelpers.h"
#import "ASInternalHelpers.h"
#import "ASDisplayNodeExtras.h"
#if !AS_IOS8_SDK_OR_LATER
#error ASMultiplexImageNode can be used on iOS 7, but must be linked against the iOS 8 SDK.
@@ -233,7 +234,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
// setting this to nil makes the node fetch images the next time its display starts
_loadedImageIdentifier = nil;
self.image = nil;
[self __setImage:nil];
}
- (void)didEnterPreloadState
@@ -325,6 +326,12 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
#pragma mark - Core
- (void)setImage:(UIImage *)image
{
ASDisplayNodeAssert(NO, @"Setting the image directly to an ASMultiplexImageNode is not allowed.");
[self __setImage:image];
}
- (void)setDelegate:(id <ASMultiplexImageNodeDelegate>)delegate
{
if (_delegate == delegate)
@@ -520,7 +527,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) {
return;
}
strongSelf.image = progressImage;
[self __setImage:progressImage];
};
}
[_downloader setProgressImageBlock:progress callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier];
@@ -538,7 +545,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
if (shouldReleaseImageOnBackgroundThread) {
ASPerformBackgroundDeallocation(image);
}
self.image = nil;
[self __setImage:nil];
}
#pragma mark -
@@ -867,7 +874,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
UIImage *previousImage = self.image;
self.loadedImageIdentifier = imageIdentifier;
self.image = image;
[self __setImage:image];
if (_delegateFlags.updatedImage) {
[_delegate multiplexImageNode:self didUpdateImage:image withIdentifier:imageIdentifier fromImage:previousImage withIdentifier:previousIdentifier];

View File

@@ -9,15 +9,16 @@
//
#import "ASNetworkImageNode.h"
#import "ASImageNode+Private.h"
#import "ASBasicImageDownloader.h"
#import "ASDisplayNodeInternal.h"
#import "ASDisplayNodeExtras.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASEqualityHelpers.h"
#import "ASInternalHelpers.h"
#import "ASImageContainerProtocolCategories.h"
#import "ASDisplayNodeExtras.h"
#if PIN_REMOTE_IMAGE
#import "ASPINRemoteImageDownloader.h"
@@ -67,6 +68,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
unsigned int cacheSupportsSynchronousFetch:1;
} _cacheFlags;
}
@end
@implementation ASNetworkImageNode
@@ -116,6 +118,12 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
#pragma mark - Public methods -- must lock
- (void)setImage:(UIImage *)image
{
ASDisplayNodeAssert(NO, @"Setting the image directly to an ASNetworkImageNode is not allowed. Please either use the defaultImage property or move to an ASImageNode");
[self __setImage:image];
}
- (void)setURL:(NSURL *)URL
{
[self setURL:URL resetToDefault:YES];
@@ -136,7 +144,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
BOOL hasURL = _URL == nil;
if (reset || hasURL) {
self.image = _defaultImage;
[self __setImage:_defaultImage];
/* We want to maintain the order that currentImageQuality is set regardless of the calling thread,
so always use a dispatch_async to ensure that we queue the operations in the correct order.
(see comment in displayDidFinish) */
@@ -171,7 +179,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
dispatch_async(dispatch_get_main_queue(), ^{
self.currentImageQuality = hasURL ? 0.0 : 1.0;
});
self.image = defaultImage;
[self __setImage:defaultImage];
}
}
@@ -256,7 +264,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
if (_imageLoaded == NO && _URL && _downloadIdentifier == nil) {
UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:_URL] asdk_image];
if (result) {
self.image = result;
[self __setImage:result];
_imageLoaded = YES;
dispatch_async(dispatch_get_main_queue(), ^{
_currentImageQuality = 1.0;
@@ -340,7 +348,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) {
return;
}
self.image = progressImage;
[self __setImage:progressImage];
dispatch_async(dispatch_get_main_queue(), ^{
// See comment in -displayDidFinish for why this must be dispatched to main
self.currentImageQuality = progress;
@@ -396,7 +404,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
ASPerformBackgroundDeallocation(image);
}
self.animatedImage = nil;
self.image = _defaultImage;
[self __setImage:_defaultImage];
_imageLoaded = NO;
// See comment in -displayDidFinish for why this must be dispatched to main
dispatch_async(dispatch_get_main_queue(), ^{
@@ -456,7 +464,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
dispatch_async(dispatch_get_main_queue(), ^{
if (self.shouldCacheImage) {
self.image = [UIImage imageNamed:_URL.path.lastPathComponent];
[self __setImage:[UIImage imageNamed:_URL.path.lastPathComponent]];
} else {
// First try to load the path directly, for efficiency assuming a developer who
// doesn't want caching is trying to be as minimal as possible.
@@ -486,7 +494,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
if (animatedImage != nil) {
self.animatedImage = animatedImage;
} else {
self.image = nonAnimatedImage;
[self __setImage:nonAnimatedImage];
}
}
@@ -522,7 +530,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
if ([imageContainer asdk_animatedImageData] && _downloaderFlags.downloaderImplementsAnimatedImage) {
strongSelf.animatedImage = [_downloader animatedImageWithData:[imageContainer asdk_animatedImageData]];
} else {
strongSelf.image = [imageContainer asdk_image];
[strongSelf __setImage:[imageContainer asdk_image]];
}
dispatch_async(dispatch_get_main_queue(), ^{
strongSelf->_currentImageQuality = 1.0;

View File

@@ -16,6 +16,7 @@
#import "ASEqualityHelpers.h"
#import "ASInternalHelpers.h"
#import "ASDisplayNodeExtras.h"
#import "ASImageNode+Private.h"
static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) {
return ASObjectIsEqual(asset1, asset2)
@@ -300,7 +301,7 @@ static NSString * const kRate = @"rate";
if (image != nil) {
self.contentMode = ASContentModeFromVideoGravity(_gravity);
}
self.image = image;
[self __setImage:image];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

View File

@@ -0,0 +1,22 @@
//
// ASImageNode+Private.h
// AsyncDisplayKit
//
// Created by Michael Schneider on 12/3/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#pragma mark - ASImageNode
#import "ASImageNode.h"
@interface ASImageNode (Private)
/*
* Set the image property of the ASImageNode. Subclasses like ASNetworkImageNode do not allow setting the
* image property directly and throw an assertion. There still needs to be a way for subclasses of
* ASNetworkImageNode to set the image.
*/
- (void)__setImage:(UIImage *)image;
@end

View File

@@ -302,4 +302,10 @@
[mockDelegate verify];
}
@end
- (void)testThatSettingAnImageExternallyWillThrow
{
ASMultiplexImageNode *multiplexImageNode = [[ASMultiplexImageNode alloc] init];
XCTAssertThrows(multiplexImageNode.image = [UIImage imageNamed:@""]);
}
@end

View File

@@ -71,6 +71,12 @@
[downloader verifyWithDelay:5];
}
- (void)testThatSettingAnImageExternallyWillThrow
{
ASNetworkImageNode *networkImageNode = [[ASNetworkImageNode alloc] init];
XCTAssertThrows(networkImageNode.image = [UIImage imageNamed:@""]);
}
@end
@implementation ASTestImageCache