鍍金池/ 教程/ iOS/ Fetch 請求
與四軸無人機的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動布局工具箱
動畫
為 iOS 7 重新設(shè)計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應(yīng)式 Android 應(yīng)用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項目介紹
Swift 的強大之處
測試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機與照片
音頻 API 一覽
交互式動畫
常見的后臺實踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應(yīng)式視圖
Square Register 中的擴張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機項目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對清單
View Controller 容器
學(xué)無止境
XCTest 測試實戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機工作原理
Build 過程

Fetch 請求

將對象從存儲中取出來的方法之一是使用 NSFetchRequest。但是請注意,一個最常見的錯誤是在你不需要的時候去讀取數(shù)據(jù)。請確保你已經(jīng)閱讀并理解了獲取對象一節(jié)中的內(nèi)容。大多數(shù)時候,遍歷關(guān)系更加有效,而使用 NSFetchRequest 往往成本很高。

通常有兩個原因使用 NSFetchRequest 來執(zhí)行數(shù)據(jù)獲?。?1) 你需要為匹配特定謂詞 (predicate) 的對象搜索整個對象圖;或者 (2) 你想要在比如 table view 這樣的地方顯示所有的對象。其實還有第三種,也是一個較不常見的情況,就是在遍歷關(guān)系的同時卻想要更高效地預(yù)先獲取數(shù)據(jù)。我們也將簡單深入這個問題。不過我們先來看看兩個主要原因,它們更加常見并且每個都具有自己的復(fù)雜性。

基礎(chǔ)

在這里我們不會涉及基礎(chǔ)內(nèi)容,因為一個關(guān)于 Core Data 的名為 Fetching Managed Objects 的 Xcode 文檔已經(jīng)涵蓋了大量基本原理。我們將深入到一些更專業(yè)的方面。

搜索對象圖

在我們的 交通數(shù)據(jù)的例子 中,我們有 12,800 個車站,其中有接近 3,000,000 個停留時間相互關(guān)聯(lián)。對接近北緯 52° 29' 57.30",東經(jīng) +13° 25' 5.40" 的車站,如果我們想要按照發(fā)車時間介于 8:00 和 8:30 之間的條件來進(jìn)行查找,我們不會想要在這個 context 中加載所有的 12,800 個 車站 對象和所有三百萬的 停留時間 對象,然后再對它們進(jìn)行循環(huán)訪問。如果我們這樣做,將不得不花費大量時間以及相當(dāng)大的存儲空間以將所有的對象加載到存儲器中。取而代之,我們想要的是使用 SQLite 來縮減進(jìn)入內(nèi)存的的對象的數(shù)量。

讓我們從小處開始,為位置接近北緯 52° 29' 57.30" 東經(jīng) +13° 25' 5.40" 的車站創(chuàng)建一個 fetch 請求。首先我們創(chuàng)建這個 fetch 請求:

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:[Stop entityName]]

我們使用 Florian 的 data model 文章中 中提到的 +entityName 方法。然后,我們需要將結(jié)果限定為那些接近我們的點的結(jié)果。

我們可以簡單的用一個 (不完全) 正方形區(qū)域圍繞我們的興趣點。實際在數(shù)學(xué)上這有些復(fù)雜,因為地球恰好有點類似于一個橢球。但是如果我們假設(shè)地球是球體,則可以得到這個公式:

D = R * sqrt( (deltaLatitude * deltaLatitude) +
              (cos(meanLatitidue) * deltaLongitude) * (cos(meanLatitidue) * deltaLongitude))

我們最后可以得到以下內(nèi)容 (均為近似值):

static double const R = 6371009000; // 地球半徑(單位:米)
double deltaLatitude = D / R * 180 / M_PI;
double deltaLongitude = D / (R * cos(meanLatitidue)) * 180 / M_PI;

我們的感興趣的點是:

CLLocation *pointOfInterest = [[CLLocation alloc] initWithLatitude:52.4992490 
                                                         longitude:13.4181670];

我們想在 ±263 英尺(80 米)內(nèi)進(jìn)行搜索:

static double const D = 80. * 1.1;
double const R = 6371009.; // 地球半徑(單位:米)
double meanLatitidue = pointOfInterest.latitude * M_PI / 180.;
double deltaLatitude = D / R * 180. / M_PI;
double deltaLongitude = D / (R * cos(meanLatitidue)) * 180. / M_PI;
double minLatitude = pointOfInterest.latitude - deltaLatitude;
double maxLatitude = pointOfInterest.latitude + deltaLatitude;
double minLongitude = pointOfInterest.longitude - deltaLongitude;
double maxLongitude = pointOfInterest.longitude + deltaLongitude;

(當(dāng)我們接近 180° 經(jīng)線的時候,這個運算不成立。由于我們的交通數(shù)據(jù)源于離 180° 經(jīng)線很遠(yuǎn)很遠(yuǎn)的柏林,所以我們忽略這個問題。)

request.result = [NSPredicate predicateWithFormat:
                  @"(%@ <= longitude) AND (longitude <= %@)"
                  @"AND (%@ <= latitude) AND (latitude <= %@)",
                  @(minLongitude), @(maxLongitude), @(minLatitude), @(maxLatitude)];

指定一種排序描述符毫無意義,因為我們會在內(nèi)存中做第二次遍歷。不過我們將讓 Core Data 在返回對象里填上所有值。

request.returnsObjectsAsFaults = NO;

如果不做這個設(shè)置,Core Data 將把值取入持久化存儲協(xié)調(diào)器的行緩存 (row cache) 中,而不是填充實際對象。通常來說這是沒問題的,不過由于我們將立刻訪問所有對象,所以我們并不希望出現(xiàn)這種行為。

編者注 把屬性值先取入緩存中,在對象需要的時候再進(jìn)行一次訪問,這在 Core Data 中是默認(rèn)行為,這種技術(shù)稱為 Faulting。這么做可以避免降低內(nèi)存開銷,但是如果你確定將訪問結(jié)果對象的具體屬性值時,可以禁用 Faults 以提高獲取性能。

為安全防范考慮,最好加上:

request.fetchLimit = 200;

執(zhí)行這條 fetch 請求

NSError *error = nil;
NSArray *stops = [moc executeFetchRequest:request error:&error];
NSAssert(stops != nil, @"Failed to execute %@: %@", request, error);

獲取操作失敗唯一 (可能) 的原因是儲存器損壞(文件被刪除等等),否則就是 fetch 請求中出現(xiàn)了語法錯誤。所以在這里使用 NSAssert() 是安全的。

我們現(xiàn)在使用 Core Locations,對內(nèi)存中的數(shù)據(jù)做第二次遍歷。

NSPredicate *exactPredicate = [self exactLatitudeAndLongitudePredicateForCoordinate:self.location.coordinate];
stops = [stops filteredArrayUsingPredicate:exactPredicate];

和:

- (NSPredicate *)exactLatitudeAndLongitudePredicateForCoordinate:(CLLocationCoordinate2D)pointOfInterest;
{
    return [NSPredicate predicateWithBlock:^BOOL(Stop *evaluatedStop, NSDictionary *bindings) {
        CLLocation *evaluatedLocation = [[CLLocation alloc] initWithLatitude:evaluatedStop.latitude longitude:evaluatedStop.longitude];
        CLLocationDistance distance = [self.location distanceFromLocation:evaluatedLocation];
        return (distance < self.distance);
    }];
}

至此我們完成了全部設(shè)置。

地理定位性能

使用裝載了 SSD 硬盤的新一代 MacBook Pro 讀取這些數(shù)據(jù)平均約需要 360μs,也就是說,你每秒可以做大約 2800 次請求。iPhone 5 平均約需要 1.67ms,每秒 600 次請求。

如果加上 -com.apple.CoreData.SQLDebug1 作為啟動參數(shù)傳遞給應(yīng)用程序,我們將得到如下輸出:

sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZIDENTIFIER, t0.ZLATITUDE, t0.ZLONGITUDE, t0.ZNAME FROM ZSTOP t0 WHERE (? <=  t0.ZLONGITUDE AND  t0.ZLONGITUDE <= ? AND ? <=  t0.ZLATITUDE AND  t0.ZLATITUDE <= ?)  LIMIT 100
annotation: sql connection fetch time: 0.0008s
annotation: total fetch execution time: 0.0013s for 15 rows.

除開一些 (對于存儲本身的) 統(tǒng)計信息外,實際為讀取數(shù)據(jù)而生成的 SQL 是:

SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZIDENTIFIER, t0.ZLATITUDE, t0.ZLONGITUDE, t0.ZNAME FROM ZSTOP t0
WHERE (? <=  t0.ZLONGITUDE AND  t0.ZLONGITUDE <= ? AND ? <=  t0.ZLATITUDE AND  t0.ZLATITUDE <= ?)
LIMIT 200

這正是我們所期望的。如果我們想要對這個性能進(jìn)行調(diào)查研究,我們可以使用 SQL EXPLAIN 命令。為此,我們可以像下面這樣使用命令行 sqlite3 來打開數(shù)據(jù)庫:

% cd TrafficSearch
% sqlite3 transit-data.sqlite
SQLite version 3.7.13 2012-07-17 17:46:21
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> EXPLAIN QUERY PLAN SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZIDENTIFIER, t0.ZLATITUDE, t0.ZLONGITUDE, t0.ZNAME FROM ZSTOP t0
...> WHERE (13.30845219672199 <=  t0.ZLONGITUDE AND  t0.ZLONGITUDE <= 13.33441458422844 AND 52.42769566863058 <=  t0.ZLATITUDE AND  t0.ZLATITUDE <= 52.44352370653525)
...> LIMIT 100;
0|0|0|SEARCH TABLE ZSTOP AS t0 USING INDEX ZSTOP_ZLONGITUDE_INDEX (ZLONGITUDE>? AND ZLONGITUDE<?) (~6944 rows)

這告訴我們 SQLite 為 (ZLONGITUDE>? AND ZLONGITUDE<?) 條件使用了 ZSTOP_ZLONGITUDE_INDEX。我們像 model 文章 中描述的那樣使用復(fù)合索引則會做的更好。由于我們總是同時搜索經(jīng)度和緯度的組合,這么做會更高效,而且我們可以去掉經(jīng)度和緯度各自的索引。

這將使輸出像下面這樣:

0|0|0|SEARCH TABLE ZSTOP AS t0 USING INDEX ZSTOP_ZLONGITUDE_ZLATITUDE (ZLONGITUDE>? AND ZLONGITUDE<?) (~6944 rows)

在我們的簡單案例中,加上復(fù)合索引幾乎不影響性能。

就像在 SQLite 文檔中的說明一樣,如果你的輸出里含有 SCAN TABLE 的話,你就要提高警惕了,這基本上意味著 SQLite 需要遍歷 所有的 記錄來看看那些是相匹配的。除非你只存儲了很少的幾個對象,否則你都應(yīng)該使用使用 index。

子查詢

假設(shè)我們只想要那些接近我們的且在接下來 20 分鐘之內(nèi)提供服務(wù)的車站。

我們可以像這樣為 StopTimes 的實體創(chuàng)建一個謂詞:

NSPredicate *timePredicate = [NSPredicate predicateWithFormat:@"(%@ <= departureTime) && (departureTime <= %@)",
                              startDate, endDate];

但是如果我們想要的謂詞是可以基于與 StopTimes 停留時間 對象的關(guān)系而過濾出那些 Stop 車站 對象,而不是 停留時間 對象本身的話,我們可以使用一個這樣的 子查詢

NSPredicate *predicate = [NSPredicate predicateWithFormat:
                          @"(SUBQUERY(stopTimes, $x, (%@ <= $x.departureTime) && ($x.departureTime <= %@)).@count != 0)",
                          startDate, endDate];

請注意,如果接近午夜,這個邏輯是稍有瑕疵的,因為我們應(yīng)當(dāng)將謂詞一分為二。不過該邏輯在這個例子中是可行的。

對于限制數(shù)據(jù)在關(guān)系之上的,子查詢非常有用。在 Xcode 文檔 -[NSExpression expressionForSubquery:usingIteratorVariable:predicate:] 中有更多信息。

我們可以簡單的使用 and 或者 && 來組合兩個謂詞,例如:

[NSPredicate predicateWithFormat:@"(%@ <= departureTime) && (SUBQUERY(stopTimes ....

或者在代碼中使用 +[NSCompoundPredicate andPredicateWithSubpredicates:]。

我們用一個像這樣的謂詞來作為結(jié)束:

(lldb) po predicate
(13.39657778010461 <= longitude AND longitude <= 13.42266155792719
    AND 52.63249629924865 <= latitude AND latitude <= 52.64832433715332)
    AND SUBQUERY(
        stopTimes, $x, CAST(-978250148.000000, "NSDate") <= $x.departureTime 
        AND $x.departureTime <= CAST(-978306000.000000, "NSDate")
    ).@count != 0

子查詢性能

如果我們看一下生成的 SQL,它會像下面這樣:

sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZIDENTIFIER, t0.ZLATITUDE, t0.ZLONGITUDE, t0.ZNAME FROM ZSTOP t0
     WHERE ((? <=  t0.ZLONGITUDE AND  t0.ZLONGITUDE <= ? AND ? <=  t0.ZLATITUDE AND  t0.ZLATITUDE <= ?)
            AND (SELECT COUNT(t1.Z_PK) FROM ZSTOPTIME t1 WHERE (t0.Z_PK = t1.ZSTOP AND ((? <=  t1.ZDEPARTURETIME AND  t1.ZDEPARTURETIME <= ?))) ) <> ?)
     LIMIT 200

這個 fetch 請求在新一代 MacBook Pro 上運行大約需要 12.3 ms。在 iPhone 5 上,大約需要 110 ms。請注意,我們有 300 萬 個停留時間 和將近 13,000 個車站。

explan 這個查詢,結(jié)果如下:

sqlite> EXPLAIN QUERY PLAN SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZIDENTIFIER, t0.ZLATITUDE, t0.ZLONGITUDE, t0.ZNAME FROM ZSTOP t0
   ...> WHERE ((13.37190946378911 <=  t0.ZLONGITUDE AND  t0.ZLONGITUDE <= 13.3978625285315 AND 52.41186440524024 <=  t0.ZLATITUDE AND  t0.ZLATITUDE <= 52.42769244314491) AND
   ...> (SELECT COUNT(t1.Z_PK) FROM ZSTOPTIME t1 WHERE (t0.Z_PK = t1.ZSTOP AND ((-978291733.000000 <=  t1.ZDEPARTURETIME AND  t1.ZDEPARTURETIME <= -978290533.000000))) ) <> ?)
   ...> LIMIT 200;
0|0|0|SEARCH TABLE ZSTOP AS t0 USING INDEX ZSTOP_ZLONGITUDE_ZLATITUDE (ZLONGITUDE>? AND ZLONGITUDE<?) (~3472 rows)
0|0|0|EXECUTE CORRELATED SCALAR SUBQUERY 1
1|0|0|SEARCH TABLE ZSTOPTIME AS t1 USING INDEX ZSTOPTIME_ZSTOP_INDEX (ZSTOP=?) (~2 rows)

請注意,我們?nèi)绾螌χ^詞排序非常重要。我們希望把經(jīng)緯度放在前面,因為代價低,而子查詢由于代價高則放在語句最后。

文本搜索

搜索文本是一種常見的情況。在我們的例子中,來看看使用名稱來搜索 車站 實體。

柏林有個被稱為 "U G?rlitzer Bahnhof (Berlin)" 的車站。一種很傻很天真的搜索該站的方法如下:

NSString *searchString = @"U G?rli";
predicate = [NSPredicate predicateWithFormat:@"name BEGINSWITH %@", searchString];

如果你想按照如下所示做的話,事情會變得更糟 (比如進(jìn)行一項大小寫和(或)音調(diào)不敏感的查詢。):

name BEGINSWITH[cd] 'u gorli'

事實上,事情并不是那么簡單。Unicode 非常復(fù)雜,并且有很多陷阱。首要的是很多字符可以通過多種方式來表示。 U+00F6U+006F都代表 U+0308 都可以表示 "?."。如果你身處 ASCII 碼的世界之外時,像大寫 / 小寫這樣的概念就會非常復(fù)雜。

SQLite 會為你減輕負(fù)擔(dān),但它是要付出代價的。雖然它看起來很直接,但事實并非如此。對于字符串搜索,我們想做的是在我們有一個規(guī)范化的版本可以在其中進(jìn)行搜索。我們將消除音調(diào)符號,把字符串變成小寫字母,然后將其放入一個 normalizedName 字段中。然后我們將對用于搜索的字符串做同樣的事情。然后 SQLite 就不必考慮音調(diào)和大小寫,在大小寫和音調(diào)不敏感的情況下,搜索就仍會很快。但是我們必須先完成一系列繁重的任務(wù)。

在新一代 MacBook Pro 上,使用示例代碼使用 BEGINSWITH[cd] 和示例的字符串搜索需要 7.6ms (130 次搜索 / 秒),在 iPhone 5 上這個數(shù)字是每次搜索 47ms,每秒進(jìn)行 21 次搜索。

為了將字符串轉(zhuǎn)換為小寫并移除其音調(diào),我們可以使用 CFStringTransform()

@implementation NSString (SearchNormalization)

- (NSString *)normalizedSearchString;
{
    // 參考 <http://userguide.icu-project.org/transforms>
    NSString *mutableName = [self mutableCopy];
    CFStringTransform((__bridge CFMutableStringRef) mutableName, NULL, 
                      (__bridge CFStringRef)@"NFD; [:Nonspacing Mark:] Remove; Lower(); NFC", NO);
    return mutableName;
}

@end

我們將更新 Stop 類來自動更新 normalizedName

@interface Stop (CoreDataForward)

@property (nonatomic, strong) NSString *primitiveName;
@property (nonatomic, strong) NSString *primitiveNormalizedName;

@end

@implementation Stop

@dynamic name;
- (void)setName:(NSString *)name;
{
    [self willAccessValueForKey:@"name"];
    [self willAccessValueForKey:@"normalizedName"];
    self.primitiveName = name;
    self.primitiveNormalizedName = [name normalizedSearchString];
    [self didAccessValueForKey:@"normalizedName"];
    [self didAccessValueForKey:@"name"];
}

// ...

@end

有了這些,我們就可以用 BEGINSWITH 代替 BEGINSWITH[cd] 來搜索了:

predicate = [NSPredicate predicateWithFormat:@"normalizedName BEGINSWITH %@", [searchString normalizedSearchString]];

在新一代 MacBook Pro 上,使用示例代碼中的示例字符串搜索 BEGINSWITH 需要 6.2ms(160 次搜索 / 秒),在 iPhone 5 大約上需要 40ms,25 次搜索 / 秒。

自由文本搜索

我們的搜索還只能在字符串的開頭和搜索字符串相匹配的情況下有效。要解決這個問題就要創(chuàng)建另一個用來搜索的實體。我們稱這個實體為 SearchTerm,給其一個 normalizedWord 屬性,以及一個和 Stop 的關(guān)系。對于每個車站 我們將規(guī)范它們的名稱,并將其拆分成一個個詞。例如:

    "Gedenkst?tte Dt. Widerstand (Berlin)"
->  "gedenkstatte dt. widerstand (berlin)"
->  "gedenkstatte", "dt", "widerstand", "berlin"

對于每個詞。我們創(chuàng)建一個 SearchTerm 和一個從 Stop 到它的所有 SearchTerm 對象的關(guān)系。當(dāng)用戶輸入一個字符串,我們用以下代碼在 SearchTerm 對象的 normalizedWord 上搜索:

predicate = [NSPredicate predicateWithFormat:@"normalizedWord BEGINSWITH %@", [searchString normalizedSearchString]]

這也可以在 Stop 對象中直接用子查詢完成。

獲取所有對象

如果我們的獲取請求中沒有設(shè)置謂詞,我們將為獲取到給定 實體 的所有對象。如果我們對 StopTimes 實體這樣做的話,我們將會牽涉 300 萬個對象。這將會變得緩慢,以及占用大量內(nèi)存。然而有時候,我們就是需要獲取所有對象。常見的例子是我們想要在一個 table view 中顯示所有對象。

在這種情況中,我們要做的是設(shè)置批處理量:

request.fetchBatchSize = 50;

當(dāng)我們設(shè)置了批處理量運行 -[NSManagedObjectContext executeFetchRequest:error:] 的時候,我們?nèi)匀粫玫揭粋€返回的數(shù)組。我們可以查詢它的元素數(shù)量(對于 StopTimes 實體而言,這將接近 300 萬),不過 Core Data 將只會隨著我們對數(shù)組的循環(huán)訪問將對象填充進(jìn)去。如果這些對象不再被訪問,Core Data 則會再次清理對象。簡單來說,數(shù)組的批處理量為 50(在這個例子中)。Core Data 將一次獲取 50 個對象。一旦有超過一定數(shù)量的批量對象,Core Data 將釋放最舊一批對象。于是,你就可以在這樣的數(shù)組中循環(huán)訪問所有對象,而無需在存儲器中同時存所有 300 萬個對象。

在 iOS 中,如果你使用 NSFetchedResultsController 且有很多對象,請確保你的 fetch 請求中設(shè)置了 fetchBatchSize。你需要實際實驗以確定多少的處理量更適合你。一般來說,將其設(shè)置為你要顯示的數(shù)目的兩倍,會是一個不錯的開始。

上一篇:依賴注入下一篇:底層并發(fā) API