歡迎你們前往騰訊雲社區,獲取更多騰訊海量技術實踐乾貨哦~html
做者: QQ音樂技術團隊
在和服務器傳輸文本的時候,可能會由於某一個字符的編碼格式不一樣、少了一個字節、多了一個字節等緣由致使整段文本都沒法解碼。而實際上若是能夠找到這個字符,而後替換成其餘字符的話,那整段文本其餘字符都是能夠解碼的,用戶在UI上也許能猜想出正確的字符是什麼,這種體驗是好於用戶看到一片空白。git
代碼的思路是對於沒法用initWithData:encoding:方法解析的數據,則逐個字節的進行解析。源碼的一個分支以下:github
while(檢索未超過文件長度)
{
if(1字節長的編碼)
{/*正確編碼,繼續循環*/}
else if (2字節長的編碼)
{
CFStringRef cfstr = CFStringCreateWithBytes(kCFAllocatorDefault, {byte1, byte2}, 2, kCFStringEncodingUTF8, false);
if (cfstr)
{/*正確編碼,繼續循環*/}
else
{/*替換字符*/}
}
else if(3,4,5,6個字節長的解碼)...
}
複製代碼
發現沒法解析的字符後進行替換。這個方法的弊端在於CFStringCreateWithBytes方法分配的字符串是堆空間,若是數據過長,則很容易產生內存碎片。bash
解決這個問題有兩種思路:一是在棧空間分配內存,二是分配一個能夠重複利用的堆空間。服務器
從CFStringCreateWithBytes提供的參數看,調用者能夠指定內存分配器。查閱官方文檔對第一個參數CFAllocatorRef alloc給出的釋義:The allocator to use to allocate memory for the new string. Pass NULL or kCFAllocatorDefault to use the current default allocator。接下來研究下這個內存分配器的數據結構以及系統提供的六個分配器的區別。數據結構
先看下CFAllocatorRef的數據結構:app
typedef const struct CF_BRIDGED_TYPE(id) __CFAllocator * CFAllocatorRef;
struct __CFAllocator {
CFRuntimeBase _base;
CFAllocatorRef _allocator;
CFAllocatorContext _context;
};
複製代碼
只考慮iOS平臺的話,__CFAllocator只有三個成員。其中CFAllocatorContext _context是分配器的核心,其做用是能夠自定義分配和釋放的回調函數:函數
typedef void * (*CFAllocatorAllocateCallBack)(CFIndex allocSize, CFOptionFlags hint, void *info);
typedef void (*CFAllocatorDeallocateCallBack)(void *ptr, void *info);
typedef struct {
...
CFAllocatorAllocateCallBack allocate;
CFAllocatorDeallocateCallBack deallocate;
...
} CFAllocatorContext;
複製代碼
當系統使用這個分配器進行分配,釋放,重分配等操做的時候會調用相應的回調函數來執行(上面代碼省略了部分回調函數,有興趣深刻了解的同窗可查看CFBase.m的源碼)。測試
接下來看系統爲提供的一系列分配器的源碼(只考慮iOS平臺)。動畫
static void * __CFAllocatorCPPMalloc(CFIndex allocSize, CFOptionFlags hint, void *info)
{return malloc(allocSize); }
static void __CFAllocatorCPPFree(void *ptr, void *info)
{free(ptr);}
複製代碼
kCFAllocatorMallocZone:看源碼這個分配器在iOS上和kCFAllocatorMalloc是同樣的,但在Mac的操做系統上是有區別的(malloc和malloc_zone_malloc)。
kCFAllocatorNull:其實什麼都不會作,直接返回NULL。看文檔說明主要是用於在釋放的時候內存實際上不該該被釋放。
static void *__CFAllocatorNullAllocate(CFIndex size, CFOptionFlags hint, void *info)
{ return NULL;}
複製代碼
kCFAllocatorUseContext:是一個固定的地址,它只用於CFAllocatorCreate()建立分配器的時候。表示建立分配器時使用自身的context->allocate方法來分配內存。由於分配器也是一個CF對象。
const CFAllocatorRef kCFAllocatorUseContext = (CFAllocatorRef)0x03ab;
複製代碼
kCFAllocatorDefault:這個是取系統當前的默認分配器,這個須要結合另外兩個API來理。解:CFAllocatorGetDefault和CFAllocatorSetDefault方法。(源碼中set方法有一段有意思的註釋:系統retain了兩次allocator,目的是爲了在設置默認分配器的時候,以前的默認分配器不會釋放。那這裏不是會形成內存泄漏了嗎?以爲要慎用)。
kCFAllocatorSystemDefault:這個纔是系統級別的默認分配器,若是不調用CFAllocatorSetDefault(),則用CFAllocatorGetDefault()取出的分配器就是這個。從源碼來看,目前和kCFAllocatorMalloc沒區別(也許好久以前由於__CFAllocatorSystemAllocate不是用malloc實現的。後來兼容了,這裏的故事有知道的歡迎告知)
看完系統提供的分配器後發現都是在堆空間分配內存,沒有合適的。後發現系統提供了另一個API:CFAllocatorCreate。這時能夠考慮自定義一個分配器,分配器在分配內存的時候,返回一塊固定大小的內存重複使用。
void *customAlloc(CFIndex size, CFOptionFlags hint, void *info)
{
return info;
}
void *customRealloc(void *ptr, CFIndex newsize, CFOptionFlags hint, void *info)
{
NSLog(@"警告:發生了內存從新分配");
return NULL;//不寫這個回調系統也是返回NULL的。這裏簡單的打句log。
}
void customDealloc(void *ptr, void *info)
{
//由於alloc的地址是外部傳來的,因此應該由外部來管理,這裏不要釋放
}
CFAllocatorRef customAllocator(void *address)
{
CFAllocatorRef allocator = NULL;
if (NULL == allocator)
{
CFAllocatorContext context = {0, NULL, NULL, NULL, NULL, customAlloc, customRealloc, customDealloc, NULL};
context.info = address;
allocator = CFAllocatorCreate(kCFAllocatorSystemDefault, &context);
}
return allocator;
}
int main()
{
char allocAddress[160] = {0};
CFAllocatorRef allocator = customAllocator(allocAddress);
CFStringRef cfstr = CFStringCreateWithBytes(allocator, tuple, 2, kCFStringEncodingUTF8, false);
if (cfstr)
{
//CFRelease(cfstr);//這裏不要釋放,這裏分配的內存是allocAddress的棧空間,由系統本身本身回收就好
}
CFAllocatorDeallocate(kCFAllocatorSystemDefault, (void *)allocator);
}
複製代碼
這裏用了一個技巧是重複使用的內存首地址利用context的info來傳遞。allocAddress的大小爲何是160個字節呢?這個大小隻要取CFStringRef須要的最大長度就能夠了。若是本身項目須要引用這個方法,須要考慮這個size須要設置多大。(取決於CFStringCreateWithBytes()的numBytes參數值,這裏會有字節對齊的知識)。
建立的CFAllocatorRef也是在堆空間上,它也須要被釋放。系統一樣提供了釋放API:CFAllocatorDeallocate。這裏須要注意dealloc的allocator須要和create時是同一個allocator。不然沒法釋放,形成內存泄漏。
自定義分配器讓咱們對內存的分配擁有了必定的可操做性,文中的應用場景是在建立對象時返回一塊固定的內存區域重複使用,避免了重複建立和釋放致使的內存碎片問題。這種可操做性相信之後在解決內存方面問題時會爲你多提供一種解決方案。
CFBase的源碼最近一次更新是2015.9.11日。這份源碼最新也是基於iOS9的。在寫這種底層代碼的時候須要格外當心,做者在寫的時候由於CFAllocatorCreate和CFAllocatorDeallocate的allocator參數傳的不一樣,致使內存泄漏,須要多多測試。發佈到外網的時候須要加上灰度策略以及開關控制。
最後分享一個額外小知識,iOS線程的默認棧空間大小是512KB(這個在蘋果出了新系統和新機器後可能會變大,因此使用的時候儘可能多測試)。這裏踩過坑,程序源碼中orignalBytes一開始是臨時變量,分配在棧上,可是因爲字符串太長,致使棧溢出crash,因此後面分配在堆上了。
2.gist.github.com/oleganza/78…
3.developer.apple.com/library/pre…
4.developer.apple.com/library/pre…
一站式知足電商節雲計算需求的祕訣
iOS 開發之動畫中的時間
使用 Skeleton Screen 提高用戶感知體驗
此文已由做者受權騰訊雲技術社區發佈,轉載請註明文章出處原文連接:https://cloud.tencent.com/community/article/383806