PageRenderTime 42ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/SPHChatBubble/AsyncImageView.m

https://gitlab.com/lisit1003/SPHChatBubble
Objective C | 709 lines | 556 code | 90 blank | 63 comment | 84 complexity | c94c474ca6e41663f838fb2f19f7fce6 MD5 | raw file
  1. //
  2. // AsyncImageView.m
  3. //
  4. // Version 1.4
  5. //
  6. // Created by Nick Lockwood on 03/04/2011.
  7. // Copyright (c) 2011 Charcoal Design
  8. //
  9. // Distributed under the permissive zlib License
  10. // Get the latest version from either of these locations:
  11. //
  12. // http://charcoaldesign.co.uk/source/cocoa#asyncimageview
  13. // https://github.com/nicklockwood/AsyncImageView
  14. //
  15. // This software is provided 'as-is', without any express or implied
  16. // warranty. In no event will the authors be held liable for any damages
  17. // arising from the use of this software.
  18. //
  19. // Permission is granted to anyone to use this software for any purpose,
  20. // including commercial applications, and to alter it and redistribute it
  21. // freely, subject to the following restrictions:
  22. //
  23. // 1. The origin of this software must not be misrepresented; you must not
  24. // claim that you wrote the original software. If you use this software
  25. // in a product, an acknowledgment in the product documentation would be
  26. // appreciated but is not required.
  27. //
  28. // 2. Altered source versions must be plainly marked as such, and must not be
  29. // misrepresented as being the original software.
  30. //
  31. // 3. This notice may not be removed or altered from any source distribution.
  32. //
  33. #import "AsyncImageView.h"
  34. #import <objc/message.h>
  35. NSString *const AsyncImageLoadDidFinish = @"AsyncImageLoadDidFinish";
  36. NSString *const AsyncImageLoadDidFail = @"AsyncImageLoadDidFail";
  37. NSString *const AsyncImageTargetReleased = @"AsyncImageTargetReleased";
  38. NSString *const AsyncImageImageKey = @"image";
  39. NSString *const AsyncImageURLKey = @"URL";
  40. NSString *const AsyncImageCacheKey = @"cache";
  41. NSString *const AsyncImageErrorKey = @"error";
  42. @interface AsyncImageConnection : NSObject
  43. @property (nonatomic, strong) NSURLConnection *connection;
  44. @property (nonatomic, strong) NSMutableData *data;
  45. @property (nonatomic, strong) NSURL *URL;
  46. @property (nonatomic, strong) NSCache *cache;
  47. @property (nonatomic, strong) id target;
  48. @property (nonatomic, assign) SEL success;
  49. @property (nonatomic, assign) SEL failure;
  50. @property (nonatomic, readonly, getter = isLoading) BOOL loading;
  51. @property (nonatomic, readonly) BOOL cancelled;
  52. - (AsyncImageConnection *)initWithURL:(NSURL *)URL
  53. cache:(NSCache *)cache
  54. target:(id)target
  55. success:(SEL)success
  56. failure:(SEL)failure;
  57. - (void)start;
  58. - (void)cancel;
  59. - (BOOL)isInCache;
  60. @end
  61. @implementation AsyncImageConnection
  62. @synthesize connection = _connection;
  63. @synthesize data = _data;
  64. @synthesize URL = _URL;
  65. @synthesize cache = _cache;
  66. @synthesize target = _target;
  67. @synthesize success = _success;
  68. @synthesize failure = _failure;
  69. @synthesize loading = _loading;
  70. @synthesize cancelled = _cancelled;
  71. - (AsyncImageConnection *)initWithURL:(NSURL *)URL
  72. cache:(NSCache *)cache
  73. target:(id)target
  74. success:(SEL)success
  75. failure:(SEL)failure
  76. {
  77. if ((self = [self init]))
  78. {
  79. self.URL = URL;
  80. self.cache = cache;
  81. self.target = target;
  82. self.success = success;
  83. self.failure = failure;
  84. }
  85. return self;
  86. }
  87. - (UIImage *)cachedImage
  88. {
  89. if ([_URL isFileURL])
  90. {
  91. NSString *path = [[_URL absoluteURL] path];
  92. NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
  93. if ([path hasPrefix:resourcePath])
  94. {
  95. return [UIImage imageNamed:[path substringFromIndex:[resourcePath length]]];
  96. }
  97. }
  98. return [_cache objectForKey:_URL];
  99. }
  100. - (BOOL)isInCache
  101. {
  102. return [self cachedImage] != nil;
  103. }
  104. - (void)loadFailedWithError:(NSError *)error
  105. {
  106. _loading = NO;
  107. _cancelled = NO;
  108. [[NSNotificationCenter defaultCenter] postNotificationName:AsyncImageLoadDidFail
  109. object:_target
  110. userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
  111. _URL, AsyncImageURLKey,
  112. error, AsyncImageErrorKey,
  113. nil]];
  114. }
  115. - (void)cacheImage:(UIImage *)image
  116. {
  117. if (!_cancelled)
  118. {
  119. if (image && _URL)
  120. {
  121. BOOL storeInCache = YES;
  122. if ([_URL isFileURL])
  123. {
  124. if ([[[_URL absoluteURL] path] hasPrefix:[[NSBundle mainBundle] resourcePath]])
  125. {
  126. //do not store in cache
  127. storeInCache = NO;
  128. }
  129. }
  130. if (storeInCache)
  131. {
  132. [_cache setObject:image forKey:_URL];
  133. }
  134. }
  135. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
  136. image, AsyncImageImageKey,
  137. _URL, AsyncImageURLKey,
  138. nil];
  139. if (_cache)
  140. {
  141. [userInfo setObject:_cache forKey:AsyncImageCacheKey];
  142. }
  143. _loading = NO;
  144. [[NSNotificationCenter defaultCenter] postNotificationName:AsyncImageLoadDidFinish
  145. object:_target
  146. userInfo:[[userInfo copy] autorelease]];
  147. }
  148. else
  149. {
  150. _loading = NO;
  151. _cancelled = NO;
  152. }
  153. }
  154. - (void)processDataInBackground:(NSData *)data
  155. {
  156. @synchronized ([self class])
  157. {
  158. if (!_cancelled)
  159. {
  160. UIImage *image = [[UIImage alloc] initWithData:data];
  161. if (image)
  162. {
  163. //add to cache (may be cached already but it doesn't matter)
  164. [self performSelectorOnMainThread:@selector(cacheImage:)
  165. withObject:image
  166. waitUntilDone:YES];
  167. [image release];
  168. }
  169. else
  170. {
  171. @autoreleasepool
  172. {
  173. NSError *error = [NSError errorWithDomain:@"AsyncImageLoader" code:0 userInfo:[NSDictionary dictionaryWithObject:@"Invalid image data" forKey:NSLocalizedDescriptionKey]];
  174. [self performSelectorOnMainThread:@selector(loadFailedWithError:) withObject:error waitUntilDone:YES];
  175. }
  176. }
  177. }
  178. else
  179. {
  180. //clean up
  181. [self performSelectorOnMainThread:@selector(cacheImage:)
  182. withObject:nil
  183. waitUntilDone:YES];
  184. }
  185. }
  186. }
  187. - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
  188. {
  189. self.data = [NSMutableData data];
  190. }
  191. - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
  192. {
  193. //add data
  194. [_data appendData:data];
  195. }
  196. - (void)connectionDidFinishLoading:(NSURLConnection *)connection
  197. {
  198. [self performSelectorInBackground:@selector(processDataInBackground:) withObject:_data];
  199. self.connection = nil;
  200. self.data = nil;
  201. }
  202. - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
  203. {
  204. self.connection = nil;
  205. self.data = nil;
  206. [self loadFailedWithError:error];
  207. }
  208. - (void)start
  209. {
  210. if (_loading && !_cancelled)
  211. {
  212. return;
  213. }
  214. //begin loading
  215. _loading = YES;
  216. _cancelled = NO;
  217. //check for nil URL
  218. if (_URL == nil)
  219. {
  220. [self cacheImage:nil];
  221. return;
  222. }
  223. //check for cached image
  224. UIImage *image = [self cachedImage];
  225. if (image)
  226. {
  227. //add to cache (cached already but it doesn't matter)
  228. [self performSelectorOnMainThread:@selector(cacheImage:)
  229. withObject:image
  230. waitUntilDone:NO];
  231. return;
  232. }
  233. //begin load
  234. NSURLRequest *request = [NSURLRequest requestWithURL:_URL
  235. cachePolicy:NSURLCacheStorageNotAllowed
  236. timeoutInterval:[AsyncImageLoader sharedLoader].loadingTimeout];
  237. _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
  238. [_connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
  239. [_connection start];
  240. }
  241. - (void)cancel
  242. {
  243. _cancelled = YES;
  244. [_connection cancel];
  245. self.connection = nil;
  246. self.data = nil;
  247. }
  248. - (void)dealloc
  249. {
  250. [_connection release];
  251. [_data release];
  252. [_URL release];
  253. [_target release];
  254. [super ah_dealloc];
  255. }
  256. @end
  257. @interface AsyncImageLoader ()
  258. @property (nonatomic, strong) NSMutableArray *connections;
  259. @end
  260. @implementation AsyncImageLoader
  261. @synthesize cache = _cache;
  262. @synthesize connections = _connections;
  263. @synthesize concurrentLoads = _concurrentLoads;
  264. @synthesize loadingTimeout = _loadingTimeout;
  265. + (AsyncImageLoader *)sharedLoader
  266. {
  267. static AsyncImageLoader *sharedInstance = nil;
  268. if (sharedInstance == nil)
  269. {
  270. sharedInstance = [[self alloc] init];
  271. }
  272. return sharedInstance;
  273. }
  274. + (NSCache *)defaultCache
  275. {
  276. static NSCache *sharedInstance = nil;
  277. if (sharedInstance == nil)
  278. {
  279. sharedInstance = [[NSCache alloc] init];
  280. }
  281. return sharedInstance;
  282. }
  283. - (AsyncImageLoader *)init
  284. {
  285. if ((self = [super init]))
  286. {
  287. self.cache = [[self class] defaultCache];
  288. _concurrentLoads = 2;
  289. _loadingTimeout = 60.0;
  290. _connections = [[NSMutableArray alloc] init];
  291. [[NSNotificationCenter defaultCenter] addObserver:self
  292. selector:@selector(imageLoaded:)
  293. name:AsyncImageLoadDidFinish
  294. object:nil];
  295. [[NSNotificationCenter defaultCenter] addObserver:self
  296. selector:@selector(imageFailed:)
  297. name:AsyncImageLoadDidFail
  298. object:nil];
  299. [[NSNotificationCenter defaultCenter] addObserver:self
  300. selector:@selector(targetReleased:)
  301. name:AsyncImageTargetReleased
  302. object:nil];
  303. }
  304. return self;
  305. }
  306. - (void)updateQueue
  307. {
  308. //start connections
  309. NSUInteger count = 0;
  310. for (AsyncImageConnection *connection in _connections)
  311. {
  312. if (![connection isLoading])
  313. {
  314. if ([connection isInCache])
  315. {
  316. [connection start];
  317. }
  318. else if (count < _concurrentLoads)
  319. {
  320. count ++;
  321. [connection start];
  322. }
  323. }
  324. }
  325. }
  326. - (void)imageLoaded:(NSNotification *)notification
  327. {
  328. //complete connections for URL
  329. NSURL *URL = [notification.userInfo objectForKey:AsyncImageURLKey];
  330. for (int i = (int)[_connections count] - 1; i >= 0; i--)
  331. {
  332. AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
  333. if (connection.URL == URL || [connection.URL isEqual:URL])
  334. {
  335. //cancel earlier connections for same target/action
  336. for (int j = i - 1; j >= 0; j--)
  337. {
  338. AsyncImageConnection *earlier = [_connections objectAtIndex:(NSUInteger)j];
  339. if (earlier.target == connection.target &&
  340. earlier.success == connection.success)
  341. {
  342. [earlier cancel];
  343. [_connections removeObjectAtIndex:(NSUInteger)j];
  344. i--;
  345. }
  346. }
  347. //cancel connection (in case it's a duplicate)
  348. [connection cancel];
  349. //perform action
  350. UIImage *image = [notification.userInfo objectForKey:AsyncImageImageKey];
  351. objc_msgSend(connection.target, connection.success, image, connection.URL);
  352. //remove from queue
  353. [_connections removeObjectAtIndex:(NSUInteger)i];
  354. }
  355. }
  356. //update the queue
  357. [self updateQueue];
  358. }
  359. - (void)imageFailed:(NSNotification *)notification
  360. {
  361. //remove connections for URL
  362. NSURL *URL = [notification.userInfo objectForKey:AsyncImageURLKey];
  363. for (int i = (int)[_connections count] - 1; i >= 0; i--)
  364. {
  365. AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
  366. if ([connection.URL isEqual:URL])
  367. {
  368. //cancel connection (in case it's a duplicate)
  369. [connection cancel];
  370. //perform failure action
  371. if (connection.failure)
  372. {
  373. NSError *error = [notification.userInfo objectForKey:AsyncImageErrorKey];
  374. objc_msgSend(connection.target, connection.failure, error, URL);
  375. }
  376. //remove from queue
  377. [_connections removeObjectAtIndex:(NSUInteger)i];
  378. }
  379. }
  380. //update the queue
  381. [self updateQueue];
  382. }
  383. - (void)targetReleased:(NSNotification *)notification
  384. {
  385. //remove connections for URL
  386. id target = [notification object];
  387. for (int i = (int)[_connections count] - 1; i >= 0; i--)
  388. {
  389. AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
  390. if (connection.target == target)
  391. {
  392. //cancel connection
  393. [connection cancel];
  394. [_connections removeObjectAtIndex:(NSUInteger)i];
  395. }
  396. }
  397. //update the queue
  398. [self updateQueue];
  399. }
  400. - (void)loadImageWithURL:(NSURL *)URL target:(id)target success:(SEL)success failure:(SEL)failure
  401. {
  402. //check cache
  403. UIImage *image = [_cache objectForKey:URL];
  404. if (image)
  405. {
  406. [self cancelLoadingImagesForTarget:self action:success];
  407. if (success) [target performSelectorOnMainThread:success withObject:image waitUntilDone:NO];
  408. return;
  409. }
  410. //create new connection
  411. AsyncImageConnection *connection = [[AsyncImageConnection alloc] initWithURL:URL
  412. cache:_cache
  413. target:target
  414. success:success
  415. failure:failure];
  416. BOOL added = NO;
  417. for (NSUInteger i = 0; i < [_connections count]; i++)
  418. {
  419. AsyncImageConnection *existingConnection = [_connections objectAtIndex:i];
  420. if (!existingConnection.loading)
  421. {
  422. [_connections insertObject:connection atIndex:i];
  423. added = YES;
  424. break;
  425. }
  426. }
  427. if (!added)
  428. {
  429. [_connections addObject:connection];
  430. }
  431. [connection release];
  432. [self updateQueue];
  433. }
  434. - (void)loadImageWithURL:(NSURL *)URL target:(id)target action:(SEL)action
  435. {
  436. [self loadImageWithURL:URL target:target success:action failure:NULL];
  437. }
  438. - (void)loadImageWithURL:(NSURL *)URL
  439. {
  440. [self loadImageWithURL:URL target:nil success:NULL failure:NULL];
  441. }
  442. - (void)cancelLoadingURL:(NSURL *)URL target:(id)target action:(SEL)action
  443. {
  444. for (int i = (int)[_connections count] - 1; i >= 0; i--)
  445. {
  446. AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
  447. if ([connection.URL isEqual:URL] && connection.target == target && connection.success == action)
  448. {
  449. [connection cancel];
  450. [_connections removeObjectAtIndex:(NSUInteger)i];
  451. }
  452. }
  453. }
  454. - (void)cancelLoadingURL:(NSURL *)URL target:(id)target
  455. {
  456. for (int i = (int)[_connections count] - 1; i >= 0; i--)
  457. {
  458. AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
  459. if ([connection.URL isEqual:URL] && connection.target == target)
  460. {
  461. [connection cancel];
  462. [_connections removeObjectAtIndex:(NSUInteger)i];
  463. }
  464. }
  465. }
  466. - (void)cancelLoadingURL:(NSURL *)URL
  467. {
  468. for (int i = (int)[_connections count] - 1; i >= 0; i--)
  469. {
  470. AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
  471. if ([connection.URL isEqual:URL])
  472. {
  473. [connection cancel];
  474. [_connections removeObjectAtIndex:(NSUInteger)i];
  475. }
  476. }
  477. }
  478. - (void)cancelLoadingImagesForTarget:(id)target action:(SEL)action
  479. {
  480. for (int i = (int)[_connections count] - 1; i >= 0; i--)
  481. {
  482. AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
  483. if (connection.target == target && connection.success == action)
  484. {
  485. [connection cancel];
  486. }
  487. }
  488. }
  489. - (void)cancelLoadingImagesForTarget:(id)target
  490. {
  491. for (int i = (int)[_connections count] - 1; i >= 0; i--)
  492. {
  493. AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
  494. if (connection.target == target)
  495. {
  496. [connection cancel];
  497. }
  498. }
  499. }
  500. - (NSURL *)URLForTarget:(id)target action:(SEL)action
  501. {
  502. //return the most recent image URL assigned to the target for the given action
  503. //this is not neccesarily the next image that will be assigned
  504. for (int i = (int)[_connections count] - 1; i >= 0; i--)
  505. {
  506. AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
  507. if (connection.target == target && connection.success == action)
  508. {
  509. return [[connection.URL ah_retain] autorelease];
  510. }
  511. }
  512. return nil;
  513. }
  514. - (NSURL *)URLForTarget:(id)target
  515. {
  516. //return the most recent image URL assigned to the target
  517. //this is not neccesarily the next image that will be assigned
  518. for (int i = (int)[_connections count] - 1; i >= 0; i--)
  519. {
  520. AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
  521. if (connection.target == target)
  522. {
  523. return [[connection.URL ah_retain] autorelease];
  524. }
  525. }
  526. return nil;
  527. }
  528. - (void)dealloc
  529. {
  530. [[NSNotificationCenter defaultCenter] removeObserver:self];
  531. [_cache release];
  532. [_connections release];
  533. [super ah_dealloc];
  534. }
  535. @end
  536. @implementation UIImageView(AsyncImageView)
  537. - (void)setImageURL:(NSURL *)imageURL
  538. {
  539. [[AsyncImageLoader sharedLoader] loadImageWithURL:imageURL target:self action:@selector(setImage:)];
  540. }
  541. - (NSURL *)imageURL
  542. {
  543. return [[AsyncImageLoader sharedLoader] URLForTarget:self action:@selector(setImage:)];
  544. }
  545. @end
  546. @interface AsyncImageView ()
  547. @property (nonatomic, strong) UIActivityIndicatorView *activityView;
  548. @end
  549. @implementation AsyncImageView
  550. @synthesize showActivityIndicator = _showActivityIndicator;
  551. @synthesize activityIndicatorStyle = _activityIndicatorStyle;
  552. @synthesize crossfadeImages = _crossfadeImages;
  553. @synthesize crossfadeDuration = _crossfadeDuration;
  554. @synthesize activityView = _activityView;
  555. - (void)setUp
  556. {
  557. _showActivityIndicator = (self.image == nil);
  558. _activityIndicatorStyle = UIActivityIndicatorViewStyleGray;
  559. _crossfadeImages = YES;
  560. _crossfadeDuration = 0.4;
  561. }
  562. - (id)initWithFrame:(CGRect)frame
  563. {
  564. if ((self = [super initWithFrame:frame]))
  565. {
  566. [self setUp];
  567. }
  568. return self;
  569. }
  570. - (id)initWithCoder:(NSCoder *)aDecoder
  571. {
  572. if ((self = [super initWithCoder:aDecoder]))
  573. {
  574. [self setUp];
  575. }
  576. return self;
  577. }
  578. - (void)setImageURL:(NSURL *)imageURL
  579. {
  580. super.imageURL = imageURL;
  581. if (_showActivityIndicator && !self.image && imageURL)
  582. {
  583. if (_activityView == nil)
  584. {
  585. _activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:_activityIndicatorStyle];
  586. _activityView.hidesWhenStopped = YES;
  587. _activityView.center = CGPointMake(self.bounds.size.width / 2.0f, self.bounds.size.height / 2.0f);
  588. _activityView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
  589. [self addSubview:_activityView];
  590. }
  591. [_activityView startAnimating];
  592. }
  593. }
  594. - (void)setActivityIndicatorStyle:(UIActivityIndicatorViewStyle)style
  595. {
  596. _activityIndicatorStyle = style;
  597. [_activityView removeFromSuperview];
  598. self.activityView = nil;
  599. }
  600. - (void)setImage:(UIImage *)image
  601. {
  602. if (_crossfadeImages)
  603. {
  604. //implement crossfade transition without needing to import QuartzCore
  605. id animation = objc_msgSend(NSClassFromString(@"CATransition"), @selector(animation));
  606. objc_msgSend(animation, @selector(setType:), @"kCATransitionFade");
  607. objc_msgSend(animation, @selector(setDuration:), _crossfadeDuration);
  608. objc_msgSend(self.layer, @selector(addAnimation:forKey:), animation, nil);
  609. }
  610. super.image = image;
  611. [_activityView stopAnimating];
  612. }
  613. - (void)dealloc
  614. {
  615. [[AsyncImageLoader sharedLoader] cancelLoadingURL:self.imageURL target:self];
  616. [_activityView release];
  617. [super ah_dealloc];
  618. }
  619. @end