NSDateFormatter為什么耗性能

一. 問題背景

很多人都知道NSDateFormatter頻繁創(chuàng)建和使用是一件耗性能的事构舟,很容易引起卡頓問題呛凶,因此建議盡量全局使用一個(gè)NSDateFormatter對象病往。

也有一些文章說NSDateFormatter最耗性能的stringFromDatedateFromString這兩個(gè)日期和字符串的轉(zhuǎn)換举塔。

那究竟NSDateFormatter的性能是損耗在哪里?為什么會(huì)引起性能損耗呢奉狈?找了一圈也沒找到一個(gè)比較說服力的解釋,因此這里就自己來調(diào)研下涩惑。

二. NSDateFormatter的性能是損耗在哪里

1. 首先我們設(shè)置三組對照組來算出NSDateFormatter在循環(huán)10000次下的損耗時(shí)間:

第一組:NSDateFormatter只生成一次仁期,其他屬性每次都設(shè)置

- (void)testDateFormatterOne {
    double startTime = CFAbsoluteTimeGetCurrent();
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    for (int i = 0; i < 100000; i++) {
        formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
        formatter.dateFormat = @"yyyy-MM-dd-HH:mm:ss-zzz";
        [formatter stringFromDate:[NSDate date]];
    }
    [self costTimeWithStartTime:startTime tipStr:@"NSDateFormatter只生成一次,其他屬性每次都設(shè)置"];
}

第二組: NSDateFormatter只生成一次,其他屬性只設(shè)置一次

- (void)testDateFormatterSecond {
    double startTime = CFAbsoluteTimeGetCurrent();
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
    formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
    formatter.dateFormat = @"yyyy-MM-dd-HH:mm:ss-zzz";
    for (int i = 0; i < 100000; i++) {
        [formatter stringFromDate:[NSDate date]];
    }
    [self costTimeWithStartTime:startTime tipStr:@"NSDateFormatter只生成一次跛蛋,其他屬性只設(shè)置一次"];
}

第三組: NSDateFormatter每次都生成

- (void)testDateFormatterThree {
    double startTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < 100000; i++) {
        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
        formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
        formatter.dateFormat = @"yyyy-MM-dd-HH:mm:ss-zzz";
        [formatter stringFromDate:[NSDate date]];
    }
    [self costTimeWithStartTime:startTime tipStr:@"NSDateFormatter每次都生成"];
}

然后執(zhí)行看所消耗時(shí)間:

image.png
2022-07-03 21:57:43.467481+0800 FJFBlogProjectDemo[96246:933082] ----------------提示語:NSDateFormatter只生成一次熬的,其他屬性每次都設(shè)置,且每次進(jìn)行日期轉(zhuǎn)字符串, costTime: 639.056921 ms
2022-07-03 21:57:43.882939+0800 FJFBlogProjectDemo[96246:933082] ----------------提示語:NSDateFormatter只生成一次,其他屬性只設(shè)置一次赊级,但每次都進(jìn)行日期轉(zhuǎn)字符串, costTime: 415.156960 ms
2022-07-03 21:58:07.942717+0800 FJFBlogProjectDemo[96246:933082] ----------------提示語:NSDateFormatter每次都生成,同時(shí)設(shè)置實(shí)例變量屬性押框,并進(jìn)行日期轉(zhuǎn)換為字符串, costTime: 24059.465051 ms

我們可以看到NSDateFormatter每次都生成,10000次調(diào)用理逊,消耗了24s左右橡伞,而NSDateFormatter只生成一次,只消耗1s不到晋被,很顯然每次都生成確實(shí)有很大的損耗兑徘,那為什么每次都生成NSDateFormatter變量會(huì)有這么大的時(shí)間消耗呢?是NSDateFormatter生成實(shí)例變量消耗時(shí)間了墨微?還是每次生成NSDateFormatter的實(shí)例變量去加載或者轉(zhuǎn)換消耗了時(shí)間呢道媚?

2. 接著我們再次設(shè)置三組實(shí)現(xiàn)對照組

第三組:NSDateFormatter每次都生成,同時(shí)設(shè)置實(shí)例變量屬性,并進(jìn)行日期轉(zhuǎn)換為字符串

- (void)testDateFormatterThree {
    double startTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < 100000; i++) {
        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
        formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
        formatter.dateFormat = @"yyyy-MM-dd-HH:mm:ss-zzz";
        [formatter stringFromDate:[NSDate date]];
    }
    [self costTimeWithStartTime:startTime tipStr:@"NSDateFormatter每次都生成,同時(shí)設(shè)置實(shí)例變量屬性翘县,并進(jìn)行日期轉(zhuǎn)換為字符串"];
}

第四組: NSDateFormatter每次都生成,但只生成NSDateFormatter實(shí)例變量

- (void)testDateFormatterFour {
    double startTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < 100000; i++) {
        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    }
    [self costTimeWithStartTime:startTime tipStr:@"NSDateFormatter每次都生成,但只生成NSDateFormatter實(shí)例變量"];
}

第五組:NSDateFormatter每次都生成最域,同設(shè)置實(shí)例變量屬性,但不進(jìn)行日期轉(zhuǎn)換

- (void)testDateFormatterFive {
    double startTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < 100000; i++) {
        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
        formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
        formatter.dateFormat = @"yyyy-MM-dd-HH:mm:ss-zzz";
    }
    [self costTimeWithStartTime:startTime tipStr:@"NSDateFormatter每次都生成锈麸,同設(shè)置實(shí)例變量屬性镀脂,但不進(jìn)行日期轉(zhuǎn)換"];
}
image.png
2022-07-03 22:01:18.779598+0800 FJFBlogProjectDemo[96318:935488] ----------------提示語:NSDateFormatter每次都生成,同時(shí)設(shè)置實(shí)例變量屬性,并進(jìn)行日期轉(zhuǎn)換為字符串, costTime: 22409.832001 ms
2022-07-03 22:01:18.835837+0800 FJFBlogProjectDemo[96318:935488] ----------------提示語:NSDateFormatter每次都生成,但只生成NSDateFormatter實(shí)例變量, costTime: 55.896997 ms
2022-07-03 22:01:19.075527+0800 FJFBlogProjectDemo[96318:935488] ----------------提示語:NSDateFormatter每次都生成忘伞,同設(shè)置實(shí)例變量屬性薄翅,但不進(jìn)行日期轉(zhuǎn)換, costTime: 239.380956 ms

從這里可以看出NSDateFormatter生成100000個(gè)實(shí)例變量只消耗了55ms左右,所以NSDateFormatter生成實(shí)例變量是不會(huì)造成性能損耗的氓奈。

從這三個(gè)對照組時(shí)間消耗對比中翘魄,我們可以看出好像損耗性能最多的就是stringFromDate這個(gè)NSDate轉(zhuǎn)NSString的函數(shù),但如果真實(shí)這樣的話舀奶,第一組和第二組實(shí)驗(yàn)暑竟,也調(diào)用了stringFromDate函數(shù)100000消耗的時(shí)間也才幾百毫秒。

因此這里可以推斷stringFromDate函數(shù)是否損耗性能育勺,還與NSDateFormatter變量是否為重新生成有關(guān)但荤,是否意味著重新生成的NSDateFormatter實(shí)例變量,就代表stringFromDate每次都要執(zhí)行耗時(shí)加載操作涧至,而如果是緩存的NSDateFormatter實(shí)例變量腹躁,則可以調(diào)用緩存,不需要執(zhí)行耗時(shí)的加載操作南蓬。

3. 基于這個(gè)推測纺非,我們再做接下來的三組對照組哑了。

第六組: NSDateFormatter只生成一次,但每次都變化locale铐炫,其他屬性保持一致垒手,進(jìn)行日期轉(zhuǎn)換

- (void)testDateFormatterSix {
    double startTime = CFAbsoluteTimeGetCurrent();
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    for (int i = 0; i < 100000; i++) {
        if (i % 3 == 0) {
            formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        }
        else if (i % 3 == 1) {
            formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"];
        }
        else if (i % 3 == 2) {
            formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"];
        }
        formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
        formatter.dateFormat = @"yyyy-MM-dd-HH:mm:ss-zzz";
        [formatter stringFromDate:[NSDate date]];
    }
    [self costTimeWithStartTime:startTime tipStr:@"NSDateFormatter只生成一次,但每次都變化locale倒信,其他屬性保持一致科贬,進(jìn)行日期轉(zhuǎn)換"];
}

第七組:NSDateFormatter只生成一次,但每次都變化timeZone鳖悠,其他屬性保持一致,進(jìn)行日期轉(zhuǎn)換

- (void)testDateFormatterSeven {
    double startTime = CFAbsoluteTimeGetCurrent();
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    for (int i = 0; i < 100000; i++) {
        if (i % 3 == 0) {
            formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
        }
        else if (i % 3 == 1) {
            formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:1];
        }
        else if (i % 3 == 2) {
            formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:2];
        }
        formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        formatter.dateFormat = @"yyyy-MM-dd-HH:mm:ss-zzz";
        [formatter stringFromDate:[NSDate date]];
    }
    [self costTimeWithStartTime:startTime tipStr:@"NSDateFormatter只生成一次榜掌,但每次都變化timeZone,其他屬性保持一致,進(jìn)行日期轉(zhuǎn)換"];
}

第八組: NSDateFormatter只生成一次乘综,但每次都變化dateFormat憎账,其他屬性保持一致,進(jìn)行日期轉(zhuǎn)換

- (void)testDateFormatterEight {
    double startTime = CFAbsoluteTimeGetCurrent();
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    for (int i = 0; i < 100000; i++) {
        if (i % 3 == 0) {
            formatter.dateFormat = @"yyyy-MM-dd-HH:mm:ss-zzz";
        }
        else if (i % 3 == 1) {
            formatter.dateFormat = @"yyyy-MM-dd-HH:mm:ss";
        }
        else if (i % 3 == 2) {
            formatter.dateFormat = @"yyyy-MM-dd";
        }
        formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
        [formatter stringFromDate:[NSDate date]];
    }
    [self costTimeWithStartTime:startTime tipStr:@"NSDateFormatter只生成一次,但每次都變化dateFormat卡辰,其他屬性保持一致,進(jìn)行日期轉(zhuǎn)換"];
}
image.png
2022-07-03 22:25:19.390128+0800 FJFBlogProjectDemo[97133:956816] ----------------提示語:NSDateFormatter只生成一次胞皱,但每次都變化locale,其他屬性保持一致九妈,進(jìn)行日期轉(zhuǎn)換, costTime: 19246.747017 ms
2022-07-03 22:25:20.046063+0800 FJFBlogProjectDemo[97133:956816] ----------------提示語:NSDateFormatter只生成一次反砌,但每次都變化timeZone,其他屬性保持一致,進(jìn)行日期轉(zhuǎn)換, costTime: 655.526042 ms
2022-07-03 22:25:20.643665+0800 FJFBlogProjectDemo[97133:956816] ----------------提示語:NSDateFormatter只生成一次萌朱,但每次都變化dateFormat宴树,其他屬性保持一致,進(jìn)行日期轉(zhuǎn)換, costTime: 597.255111 ms

從這三個(gè)對照組消耗時(shí)間,我們可以看出晶疼,同樣都是只生成一次NSDateFormatter酒贬,每次變化locale的這組,消耗時(shí)間基本跟每次都生成NSDateFormatter進(jìn)行日期轉(zhuǎn)換的時(shí)間差不多翠霍。也就是關(guān)鍵的點(diǎn)就是locale變化锭吨,因此我們要進(jìn)一步了解下NSLocale

4. NSLocale

NSLocale是一個(gè)包含某個(gè)地區(qū)語言與文化習(xí)俗的基礎(chǔ)類寒匙。一個(gè)NSLocale的實(shí)例包含了針對這個(gè)地區(qū)特定一群人的所有語言文化基準(zhǔn)耐齐,其中包括語言,鍵盤蒋情,數(shù)字,日期和時(shí)間格式耸携,貨幣棵癣,排序和分類,符號(hào)夺衍、顏色與頭像使用等狈谊。

每一個(gè)NSLocale實(shí)例對應(yīng)著一個(gè)地區(qū)標(biāo)識(shí),例如en_USfr_FR河劝,ja_JPen_GB壁榕,這些標(biāo)識(shí)包含一個(gè)語言碼(例如en代表英語)和一個(gè)地區(qū)碼(例如US代表美國)

從這個(gè)NSLocale,我們可以推測NSDateFormatter的實(shí)例,在進(jìn)行stringFromDate或者dateFromString方法時(shí)赎瞎,會(huì)依據(jù)locale的值牌里,去加載不同地區(qū)標(biāo)識(shí)對應(yīng)的日期格式信息,并緩存务甥,只有當(dāng)NSDateFormatter重新初始化或者locale值做了變更牡辽,才會(huì)重新取加載地區(qū)對應(yīng)的日期格式顯示信息。

基于這個(gè)推測敞临,我們來看下gnustep上面關(guān)于NSDateFormatter的實(shí)現(xiàn)邏輯.

5. gnustep關(guān)于NSDateFormatter的實(shí)現(xiàn)

首先我們看下NSDateFormatter類結(jié)構(gòu)和初始化方法:

image.png
image.png
image.png

NSDateFormatter的初始化方法态辛,我們可以看出這里只是簡單獲取了屬性的默認(rèn)值比如,_behavior挺尿,_locale奏黑,_tz_formatter编矾,所以這里并不會(huì)有耗時(shí)操作熟史。

接著看下stringFromDate方法:

image.png

從這個(gè)方法實(shí)現(xiàn),我推測有可能造成性能損耗的洽沟,應(yīng)該是udat_format方法以故,但該方法沒有展開看不到內(nèi)部實(shí)現(xiàn),因此也只是猜測裆操。

然后我們再看下dateFromString方法的實(shí)現(xiàn):

image.png

從這個(gè)方法實(shí)現(xiàn)怒详,也只能推測真正影響耗時(shí)的應(yīng)該是udat_parse,同樣因?yàn)樵摵瘮?shù)也一樣看不到內(nèi)部展示,因此也只能是推測踪区。

如果大家有更詳細(xì)的官方資料或者其他驗(yàn)證邏輯昆烁,麻煩告知一下。

三. 總結(jié)

NSDateFormatter之所以耗性能是因?yàn)?code>NSDateFormatter的實(shí)例進(jìn)行stringFromDate或者dateFromString進(jìn)行日期與字符串轉(zhuǎn)換時(shí)缎岗,需要依據(jù)NSlocale去加載不同地區(qū)標(biāo)識(shí)相關(guān)的日期格式數(shù)據(jù)静尼,并緩存。

因此有效的對NSDateFormatter進(jìn)行優(yōu)化的方法是依據(jù)項(xiàng)目中用到的高頻的不同的地區(qū)標(biāo)識(shí)传泊,創(chuàng)建多個(gè)全局唯一的NSDateFormatter實(shí)例鼠渺,來進(jìn)行日期格式和字符串的轉(zhuǎn)換。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末眷细,一起剝皮案震驚了整個(gè)濱河市拦盹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌溪椎,老刑警劉巖普舆,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恬口,死亡現(xiàn)場離奇詭異,居然都是意外死亡沼侣,警方通過查閱死者的電腦和手機(jī)祖能,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛾洛,“玉大人养铸,你說我怎么就攤上這事⊙盘叮” “怎么了揭厚?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長扶供。 經(jīng)常有香客問我筛圆,道長,這世上最難降的妖魔是什么椿浓? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任太援,我火速辦了婚禮,結(jié)果婚禮上扳碍,老公的妹妹穿的比我還像新娘提岔。我一直安慰自己,他們只是感情好笋敞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布碱蒙。 她就那樣靜靜地躺著,像睡著了一般夯巷。 火紅的嫁衣襯著肌膚如雪赛惩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天趁餐,我揣著相機(jī)與錄音喷兼,去河邊找鬼。 笑死后雷,一個(gè)胖子當(dāng)著我的面吹牛季惯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播臀突,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼勉抓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了候学?” 一聲冷哼從身側(cè)響起琳状,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎盒齿,沒想到半個(gè)月后念逞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡边翁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年翎承,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片符匾。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叨咖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出啊胶,到底是詐尸還是另有隱情甸各,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布焰坪,位于F島的核電站趣倾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏某饰。R本人自食惡果不足惜儒恋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望黔漂。 院中可真熱鬧诫尽,春花似錦、人聲如沸炬守。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽减途。三九已至酣藻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間观蜗,已是汗流浹背臊恋。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留墓捻,地道東北人抖仅。 一個(gè)月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像砖第,于是被迫代替她去往敵國和親撤卢。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355

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