iOS 圖片解碼(decode)筆記

為什么圖像在顯示到屏幕上之前要進(jìn)行解碼

一般我們使用的圖像是JPEG/PNG歼指,這些圖像數(shù)據(jù)不是位圖爹土,而是是經(jīng)過(guò)編碼壓縮后的數(shù)據(jù),需要線將它解碼轉(zhuǎn)成位圖數(shù)據(jù)踩身,然后才能把位圖渲染到屏幕上着饥。

當(dāng)你用 UIImage 或 CGImageSource 的那幾個(gè)方法創(chuàng)建圖片時(shí),圖片數(shù)據(jù)并不會(huì)立刻解碼惰赋。圖片設(shè)置到 UIImageView 或者 CALayer.contents 中去宰掉,并且 CALayer 被提交到 GPU 前,CGImage 中的數(shù)據(jù)才會(huì)得到解碼赁濒。這一步是發(fā)生在主線程的轨奄,并且不可避免。

圖片加載的工作流

概括來(lái)說(shuō)拒炎,從磁盤中加載一張圖片挪拟,并將它顯示到屏幕上,中間的主要工作流如下:

  1. 假設(shè)我們使用 +imageWithContentsOfFile: 方法從磁盤中加載一張圖片击你,這個(gè)時(shí)候的圖片并沒(méi)有解壓縮玉组;
  2. 然后將生成的 UIImage 賦值給 UIImageView
  3. 接著一個(gè)隱式的 CATransaction 捕獲到了 UIImageView 圖層樹(shù)的變化丁侄;
  4. 在主線程的下一個(gè) run loop 到來(lái)時(shí)惯雳,Core Animation 提交了這個(gè)隱式的 transaction ,這個(gè)過(guò)程可能會(huì)對(duì)圖片進(jìn)行 copy 操作鸿摇,而受圖片是否字節(jié)對(duì)齊等因素的影響石景,這個(gè) copy 操作可能會(huì)涉及以下部分或全部步驟:
    1. 分配內(nèi)存緩沖區(qū)用于管理文件 IO 和解壓縮操作;
    2. 將文件數(shù)據(jù)從磁盤讀到內(nèi)存中拙吉;
    3. 將壓縮的圖片數(shù)據(jù)解碼成未壓縮的位圖形式潮孽,這是一個(gè)非常耗時(shí)的 CPU 操作;
    4. 最后 Core Animation 使用未壓縮的位圖數(shù)據(jù)渲染 UIImageView 的圖層筷黔。

在上面的步驟中往史,我們提到了圖片的解壓縮是一個(gè)非常耗時(shí)的 CPU 操作,并且它默認(rèn)是在主線程中執(zhí)行的佛舱。那么當(dāng)需要加載的圖片比較多時(shí)椎例,就會(huì)對(duì)我們應(yīng)用的響應(yīng)性造成嚴(yán)重的影響揽乱,尤其是在快速滑動(dòng)的列表上,這個(gè)問(wèn)題會(huì)表現(xiàn)得更加突出粟矿。

圖像的解碼

解碼操作是比較耗時(shí)的,并且沒(méi)有GPU硬解碼损拢,只能通過(guò)CPU陌粹,iOS默認(rèn)會(huì)在主線程對(duì)圖像進(jìn)行解碼。解碼過(guò)程是一個(gè)相當(dāng)復(fù)雜的任務(wù)福压,需要消耗非常長(zhǎng)的時(shí)間掏秩。60FPS ≈ 0.01666s per frame = 16.7ms per frame,這意味著在主線程超過(guò)16.7ms的任務(wù)都會(huì)引起掉幀荆姆。很多庫(kù)都解決了圖像解碼的問(wèn)題蒙幻,不過(guò)由于解碼后的圖像太大,一般不會(huì)緩存到磁盤胆筒,SDWebImage的做法是把解碼操作從主線程移到子線程邮破,讓耗時(shí)的解碼操作不占用主線程的時(shí)間

對(duì)于PNG圖片來(lái)說(shuō)仆救,因?yàn)槲募赡芨笫愫停约虞d會(huì)比JPEG更長(zhǎng),但是解碼會(huì)相對(duì)較快彤蔽,而且Xcode會(huì)把PNG圖片進(jìn)行解碼優(yōu)化之后引入工程摧莽。JPEG圖片更小,加載更快顿痪,但是解壓的步驟要消耗更長(zhǎng)的時(shí)間镊辕,因?yàn)镴PEG解壓算法比基于zip的PNG算法更加復(fù)雜。
當(dāng)加載圖片的時(shí)候蚁袭,iOS通常會(huì)延遲解壓圖片的時(shí)間征懈,直到加載到內(nèi)存之后。因?yàn)樾枰诶L制之前進(jìn)行解壓揩悄,這就會(huì)在準(zhǔn)備繪制圖片的時(shí)候影響性能受裹。
iOS通常會(huì)延時(shí)解壓圖片,等到圖片在屏幕上顯示的時(shí)候解壓圖片虏束。解壓圖片是非常耗時(shí)的操作棉饶。

圖像解碼的核心方法CGBitmapContextCreate

CGContextRef CGBitmapContextCreate(
void * data, 
size_t width, 
size_t height,
size_t bitsPerComponent, 
size_t bytesPerRow,
CGColorSpaceRef  _Nullable space, 
uint32_t bitmapInfo)

上面的第一個(gè)參數(shù)是一個(gè)只想一塊內(nèi)存的指針,這塊內(nèi)存用于存儲(chǔ)被繪制的圖形镇匀,這塊內(nèi)存的size最小不能小于bytesPerRow*height(圖形每行的字節(jié)數(shù)乘以圖形的高度)照藻,傳遞NULL意味著由這個(gè)函數(shù)來(lái)管理圖形的內(nèi)存,這可以減少內(nèi)存泄漏的問(wèn)題汗侵;

第二個(gè)參數(shù)with是圖形的width幸缕;

第三個(gè)參數(shù)高度是圖形的高度群发;

第四個(gè)參數(shù)是像素中每一個(gè)顏色分量的像素位數(shù);

pixel formatter

Pixel Format

我們前面已經(jīng)提到了发乔,位圖其實(shí)就是一個(gè)像素?cái)?shù)組熟妓,而像素格式則是用來(lái)描述每個(gè)像素的組成格式,它包括以下信息:

  • Bits per component :一個(gè)像素中每個(gè)獨(dú)立的顏色分量使用的 bit 數(shù)栏尚;
  • Bits per pixel :一個(gè)像素使用的總 bit 數(shù)起愈;
  • Bytes per row :位圖中的每一行使用的字節(jié)數(shù)。

有一點(diǎn)需要注意的是译仗,對(duì)于位圖來(lái)說(shuō)抬虽,像素格式并不是隨意組合的,目前只支持以下有限的 17 種特定組合

Supported Pixel Formats

Color Spaces

在 Quartz 中纵菌,一個(gè)顏色是由一組值來(lái)表示的阐污,比如 0, 0, 1 。而顏色空間則是用來(lái)說(shuō)明如何解析這些值的咱圆,離開(kāi)了顏色空間笛辟,它們將變得毫無(wú)意義。比如序苏,下面的值都表示藍(lán)色:


blue_color

如果不知道顏色空間隘膘,那么我們根本無(wú)法知道這些值所代表的顏色。比如 0, 0, 1 在 RGB 下代表藍(lán)色杠览,而在 BGR 下則代表的是紅色弯菊。在 RGB 和 BGR 兩種顏色空間下,綠色是相同的踱阿,而紅色和藍(lán)色則相互對(duì)調(diào)了管钳。因此,對(duì)于同一張圖片软舌,使用 RGB 和 BGR 兩種顏色空間可能會(huì)得到兩種不一樣的效果:


color_profiles.png

BitmapInfo

Color Spaces and Bitmap Layout

我們前面已經(jīng)知道了才漆,像素格式是用來(lái)描述每個(gè)像素的組成格式的,比如每個(gè)像素使用的總 bit 數(shù)和每個(gè)獨(dú)立的顏色分量使用的 bit 數(shù)佛点。而要想確保 Quartz 能夠正確地解析這些 bit 所代表的含義醇滥,我們還需要提供位圖的布局信息 CGBitmapInfo

typedef CF_OPTIONS(uint32_t, CGBitmapInfo) {
    kCGBitmapAlphaInfoMask = 0x1F,

    kCGBitmapFloatInfoMask = 0xF00,
    kCGBitmapFloatComponents = (1 << 8),

    kCGBitmapByteOrderMask     = kCGImageByteOrderMask,
    kCGBitmapByteOrderDefault  = (0 << 12),
    kCGBitmapByteOrder16Little = kCGImageByteOrder16Little,
    kCGBitmapByteOrder32Little = kCGImageByteOrder32Little,
    kCGBitmapByteOrder16Big    = kCGImageByteOrder16Big,
    kCGBitmapByteOrder32Big    = kCGImageByteOrder32Big
} CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

它主要提供了三個(gè)方面的布局信息:

  • alpha 的信息;
  • 顏色分量是否為浮點(diǎn)數(shù)超营;
  • 像素格式的字節(jié)順序鸳玩。

其中,alpha 的信息由枚舉值 CGImageAlphaInfo來(lái)表示:

typedef CF_ENUM(uint32_t, CGImageAlphaInfo) {
    kCGImageAlphaNone,               /* For example, RGB. */
    kCGImageAlphaPremultipliedLast,  /* For example, premultiplied RGBA */
    kCGImageAlphaPremultipliedFirst, /* For example, premultiplied ARGB */
    kCGImageAlphaLast,               /* For example, non-premultiplied RGBA */
    kCGImageAlphaFirst,              /* For example, non-premultiplied ARGB */
    kCGImageAlphaNoneSkipLast,       /* For example, RBGX. */
    kCGImageAlphaNoneSkipFirst,      /* For example, XRGB. */
    kCGImageAlphaOnly                /* No color data, alpha data only */
};

上面的注釋其實(shí)已經(jīng)比較清楚了演闭,它同樣也提供了三個(gè)方面的 alpha 信息:

  • 是否包含 alpha 不跟;
  • 如果包含 alpha ,那么 alpha 信息所處的位置米碰,在像素的最低有效位窝革,比如 RGBA 购城,還是最高有效位,比如 ARGB 虐译;
  • 如果包含 alpha 瘪板,那么每個(gè)顏色分量是否已經(jīng)乘以 alpha 的值,這種做法可以加速圖片的渲染時(shí)間漆诽,因?yàn)樗苊饬虽秩緯r(shí)的額外乘法運(yùn)算侮攀。比如,對(duì)于 RGB 顏色空間拴泌,用已經(jīng)乘以 alpha 的數(shù)據(jù)來(lái)渲染圖片,每個(gè)像素都可以避免 3 次乘法運(yùn)算惊橱,紅色乘以 alpha 蚪腐,綠色乘以 alpha 和藍(lán)色乘以 alpha 。

那么我們?cè)诮鈮嚎s圖片的時(shí)候應(yīng)該使用哪個(gè)值呢税朴?根據(jù) Which CGImageAlphaInfo should we use 和官方文檔中對(duì) UIGraphicsBeginImageContextWithOptions 函數(shù)的討論:

You use this function to configure the drawing environment for rendering into a bitmap. The format for the bitmap is a ARGB 32-bit integer pixel format using host-byte order. If the opaque parameter is YES, the alpha channel is ignored and the bitmap is treated as fully opaque (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host). Otherwise, each pixel uses a premultipled ARGB format (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host).

我們可以知道回季,當(dāng)圖片不包含 alpha 的時(shí)候使用 kCGImageAlphaNoneSkipFirst ,否則使用 kCGImageAlphaPremultipliedFirst 正林。另外泡一,這里也提到了字節(jié)順序應(yīng)該使用 32 位的主機(jī)字節(jié)順序 kCGBitmapByteOrder32Host ,而這個(gè)值具體是什么觅廓,我們后面再討論鼻忠。

至于顏色分量是否為浮點(diǎn)數(shù),這個(gè)就比較簡(jiǎn)單了杈绸,直接邏輯或 kCGBitmapFloatComponents 就可以了帖蔓。更詳細(xì)的內(nèi)容就不展開(kāi)了,因?yàn)槲覀円话阌貌簧线@個(gè)值瞳脓。

接下來(lái)塑娇,我們來(lái)簡(jiǎn)單地了解下像素格式的字節(jié)順序,它是由枚舉值 CGImageByteOrderInfo 來(lái)表示的:

typedef CF_ENUM(uint32_t, CGImageByteOrderInfo) {
    kCGImageByteOrderMask     = 0x7000,
    kCGImageByteOrder16Little = (1 << 12),
    kCGImageByteOrder32Little = (2 << 12),
    kCGImageByteOrder16Big    = (3 << 12),
    kCGImageByteOrder32Big    = (4 << 12)
} CG_AVAILABLE_STARTING(__MAC_10_12, __IPHONE_10_0);

它主要提供了兩個(gè)方面的字節(jié)順序信息:

對(duì)于 iPhone 來(lái)說(shuō),采用的是小端模式烧栋,但是為了保證應(yīng)用的向后兼容性写妥,我們可以使用系統(tǒng)提供的宏,來(lái)避免 Hardcoding

#ifdef __BIG_ENDIAN__
    #define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Big
    #define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Big
#else /* Little endian. */
    #define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Little
    #define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Little
#endif

根據(jù)前面的討論审姓,我們知道字節(jié)順序的值應(yīng)該使用的是 32 位的主機(jī)字節(jié)順序 kCGBitmapByteOrder32Host 耳标,這樣的話不管當(dāng)前設(shè)備采用的是小端模式還是大端模式,字節(jié)順序始終與其保持一致邑跪。

下面次坡,我們來(lái)看一張圖呼猪,它非常形象地展示了在使用 16 或 32 位像素格式的 CMYK 和 RGB 顏色空間下,一個(gè)像素是如何被表示的:


pixel formats

我們從圖中可以看出砸琅,在 32 位像素格式下宋距,每個(gè)顏色分量使用 8 位;而在 16 位像素格式下症脂,每個(gè)顏色分量則使用 5 位谚赎。

好了,了解完這些相關(guān)知識(shí)后诱篷,我們?cè)倩剡^(guò)頭來(lái)看看 CGBitmapContextCreate 函數(shù)中每個(gè)參數(shù)所代表的具體含義:

  • data :如果不為 NULL 壶唤,那么它應(yīng)該指向一塊大小至少為 bytesPerRow * height 字節(jié)的內(nèi)存;如果 為 NULL 棕所,那么系統(tǒng)就會(huì)為我們自動(dòng)分配和釋放所需的內(nèi)存闸盔,所以一般指定 NULL 即可;
  • widthheight :位圖的寬度和高度琳省,分別賦值為圖片的像素寬度和像素高度即可迎吵;
  • bitsPerComponent :像素的每個(gè)顏色分量使用的 bit 數(shù),在 RGB 顏色空間下指定 8 即可针贬;
  • bytesPerRow :位圖的每一行使用的字節(jié)數(shù)击费,大小至少為 width * bytes per pixel 字節(jié)。有意思的是桦他,當(dāng)我們指定 0 時(shí)蔫巩,系統(tǒng)不僅會(huì)為我們自動(dòng)計(jì)算,而且還會(huì)進(jìn)行 cache line alignment 的優(yōu)化快压,更多信息可以查看 what is byte alignment (cache line alignment) for Core Animation? Why it matters?Why is my image’s Bytes per Row more than its Bytes per Pixel times its Width? 批幌,親測(cè)可用;
  • space :就是我們前面提到的顏色空間嗓节,一般使用 RGB 即可荧缘;
  • bitmapInfo :就是我們前面提到的位圖的布局信息。

+imageNamed:

通過(guò) imageNamed 創(chuàng)建 UIImage 時(shí)拦宣,當(dāng) UIImage 第一次顯示到屏幕上時(shí)截粗,其內(nèi)部的解碼方法才會(huì)被調(diào)用,并且內(nèi)存中自動(dòng)緩存解壓后的圖片鸵隧。當(dāng)APP第一次退到后臺(tái)和收到內(nèi)存警告時(shí)绸罗,緩存才會(huì)被自動(dòng)清空。

+imageWithContentsOfFile:

這個(gè)方法不會(huì)緩存解壓后的圖片豆瘫,也就是說(shuō)每次調(diào)用時(shí)都會(huì)對(duì)文件進(jìn)行加載和解壓珊蟀。iOS通常會(huì)延遲解壓圖片,為了提升性能,在屏幕繪制前可以強(qiáng)制解壓育灸。
有兩種方法可以強(qiáng)制解壓:

  • 將圖片的一個(gè)像素繪制成一個(gè)像素大小的CGContext腻窒。這樣仍然會(huì)解壓整張圖片,但是繪制本身并沒(méi)有消耗任何時(shí)間磅崭。這樣的好處在于加載的圖片并不會(huì)在特定的設(shè)備上為繪制做優(yōu)化儿子,所以可以在任何時(shí)間點(diǎn)繪制出來(lái)。同樣iOS也就可以丟棄解壓后的圖片來(lái)節(jié)省內(nèi)存了砸喻。
//解壓縮這個(gè)image柔逼,即時(shí)它只有一個(gè)像素。
- (void)decompressImage:(UIImage *)image
{
UIGraphicsBeginImageContext(CGSizeMake(1, 1));
[image drawAtPoint:CGPointZero];
UIGraphicsEndImageContext();
}
  • 將整張圖片繪制到CGContext中割岛,丟棄原始的圖片愉适,并且用一個(gè)從上下文內(nèi)容中新的圖片來(lái)代替。這樣比繪制單一像素那樣需要更加復(fù)雜的計(jì)算癣漆,但是因此產(chǎn)生的圖片將會(huì)為繪制做優(yōu)化维咸,而且由于原始?jí)嚎s圖片被拋棄了,iOS就不能夠隨時(shí)丟棄任何解壓后的圖片來(lái)節(jié)省內(nèi)存了扑媚。
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
                  cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    //dequeue cell
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
    ...
    //切換到子線程
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        //load image
        NSInteger index = indexPath.row;
        NSString *imagePath = self.imagePaths[index];
        UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
        //redraw image using device context
        UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, YES, 0);
        [image drawInRect:imageView.bounds];
        image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        //set image on main thread, but only if index still matches up
        dispatch_async(dispatch_get_main_queue(), ^{
            if (index == cell.tag) {
                imageView.image = image;
            }
        });
    });
    return cell;
}

Large Image Downsizing

SDWebImage解碼的方法在SDWebImageDecoder這個(gè)類里腰湾。這個(gè)類里有兩個(gè)方法雷恃,decodedImageWithImage是直接對(duì)圖片解碼疆股,decodedAndScaledDownImageWithImage這個(gè)方法里會(huì)先判斷圖片的要解壓縮的圖片大小是否超過(guò)60M,沒(méi)超過(guò)的話會(huì)調(diào)用decodedImageWithImage這個(gè)方法直接解碼圖片倒槐,否則會(huì)對(duì)原圖片進(jìn)行縮放以減少占用內(nèi)存空間旬痹,并且解碼圖片時(shí)會(huì)把原始的圖片數(shù)據(jù)分成多個(gè)tail進(jìn)行解碼。這個(gè)過(guò)程應(yīng)該是參考了apple的 Large Image Downsizing
在這個(gè)demo里讨越,有一張large_leaves_70mp.jpg的圖片两残,它在磁盤上的大小是8.3M,但它的像素是7033x10110的把跨,也就是說(shuō)圖片解碼后顯示在屏幕上時(shí)所占的內(nèi)存是7033x10110x4byte(一個(gè)像素是4byte)人弓,也就是271MB,這樣一張圖片着逐,通過(guò)通常方法(imageView.image=image)是無(wú)法正常顯示的崔赌。

demo里原始圖片進(jìn)行了縮放,并且對(duì)把原始圖片分成多個(gè)tail進(jìn)行解碼耸别。
縮放的主要代碼:
原始圖片的在像素上的寬高:

sourceResolution.width = CGImageGetWidth(sourceImage.CGImage);
sourceResolution.height = CGImageGetHeight(sourceImage.CGImage);

計(jì)算縮放比例

 imageScale = destTotalPixels / sourceTotalPixels;

計(jì)算縮放目標(biāo)圖片的寬高

 destResolution.width = (int)( sourceResolution.width * imageScale );
 destResolution.height = (int)( sourceResolution.height * imageScale );

創(chuàng)建縮放目標(biāo)圖片的context:

// create the output bitmap context
destContext = CGBitmapContextCreate( destBitmapData, destResolution.width, destResolution.height, 8, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast );

雖然我們對(duì)超大圖片進(jìn)行了縮放健芭,但是依然較大,特別是在繪制的時(shí)候秀姐,非常耗性能慈迈。所以Sample中的方法是將該圖的繪制分成多個(gè)Tile來(lái)進(jìn)行,在該Sample中這張圖片被分成了14個(gè)Tile省有。sourceTile表示從原圖上截取的Tile尺寸痒留,destTile表示最終繪制到界面上的Tile尺寸谴麦。
demo里通過(guò)宏tileTotalPixels定義了一個(gè)tile的總的尺寸,然后計(jì)算了原圖片一個(gè)tile的高度

sourceTile.size.width = sourceResolution.width;
    // the source tile height is dynamic. Since we specified the size
    // of the source tile in MB, see how many rows of pixels high it
    // can be given the input image width.
    sourceTile.size.height = (int)( tileTotalPixels / sourceTile.size.width );   

然后根據(jù)比例計(jì)算目標(biāo)tile的高度:

destTile.size.height = sourceTile.size.height * imageScale;      

計(jì)算原圖片tile的個(gè)數(shù):

int iterations = (int)( sourceResolution.height / sourceTile.size.height );
int remainder = (int)sourceResolution.height % (int)sourceTile.size.height; if( remainder ) iterations++;

根據(jù)tile的個(gè)數(shù)進(jìn)行循環(huán)狭瞎,把每一個(gè)原始圖片的tile繪制到context中细移,代碼里的注釋提到了,CGContextDrawImage調(diào)用時(shí)數(shù)據(jù)會(huì)被解碼熊锭。

for( int y = 0; y < iterations; ++y ) {
        // create an autorelease pool to catch calls to -autorelease made within the downsize loop.
        NSAutoreleasePool* pool2 = [[NSAutoreleasePool alloc] init];
        NSLog(@"iteration %d of %d",y+1,iterations);
        sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap; 
        destTile.origin.y = ( destResolution.height ) - ( ( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + destSeemOverlap ); 
        // create a reference to the source image with its context clipped to the argument rect.
        sourceTileImageRef = CGImageCreateWithImageInRect(sourceImage.CGImage, sourceTile);
        // if this is the last tile, it's size may be smaller than the source tile height.
        // adjust the dest tile size to account for that difference.
        if( y == iterations - 1 && remainder ) {
            float dify = destTile.size.height;
            destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
            dify -= destTile.size.height;
            destTile.origin.y += dify;
        }
        // read and write a tile sized portion of pixels from the input image to the output image. 
        CGContextDrawImage( destContext, destTile, sourceTileImageRef );
        /* release the source tile portion pixel data. note,
         releasing the sourceTileImageRef doesn't actually release the tile portion pixel
         data that we just drew, but the call afterward does. */
        CGImageRelease(sourceTileImageRef);
        /* while CGImageCreateWithImageInRect lazily loads just the image data defined by the argument rect, 
         that data is finally decoded from disk to mem when CGContextDrawImage is called. sourceTileImageRef 
         maintains internally a reference to the original image, and that original image both, houses and 
         caches that portion of decoded mem. Thus the following call to release the source image. */
        [sourceImage release];
        // free all objects that were sent -autorelease within the scope of this loop.
        [pool2 drain];     
        // we reallocate the source image after the pool is drained since UIImage -imageNamed
        // returns us an autoreleased object.         
        if( y < iterations - 1 ) {            
            sourceImage = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:kImageFilename ofType:nil]];
            [self performSelectorOnMainThread:@selector(updateScrollView:) withObject:nil waitUntilDone:YES];
        }
    }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弧轧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碗殷,更是在濱河造成了極大的恐慌精绎,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锌妻,死亡現(xiàn)場(chǎng)離奇詭異代乃,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)仿粹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門搁吓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人吭历,你說(shuō)我怎么就攤上這事堕仔。” “怎么了晌区?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵摩骨,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我朗若,道長(zhǎng)恼五,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任哭懈,我火速辦了婚禮灾馒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遣总。我一直安慰自己睬罗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布彤避。 她就那樣靜靜地躺著傅物,像睡著了一般。 火紅的嫁衣襯著肌膚如雪琉预。 梳的紋絲不亂的頭發(fā)上董饰,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼卒暂。 笑死啄栓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的也祠。 我是一名探鬼主播昙楚,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼诈嘿!你這毒婦竟也來(lái)了堪旧?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奖亚,失蹤者是張志新(化名)和其女友劉穎淳梦,沒(méi)想到半個(gè)月后鸿吆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體琳要,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡古胆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年榆俺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诗充。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡爷怀,死狀恐怖剧辐,靈堂內(nèi)的尸體忽然破棺而出夹攒,到底是詐尸還是另有隱情蜘醋,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布芹助,位于F島的核電站堂湖,受9級(jí)特大地震影響闲先,放射性物質(zhì)發(fā)生泄漏状土。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一伺糠、第九天 我趴在偏房一處隱蔽的房頂上張望蒙谓。 院中可真熱鬧,春花似錦训桶、人聲如沸累驮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)谤专。三九已至,卻和暖如春午绳,著一層夾襖步出監(jiān)牢的瞬間置侍,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜡坊,地道東北人杠输。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像秕衙,于是被迫代替她去往敵國(guó)和親蠢甲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容