swift語(yǔ)言AutoreleasePool原理及使用場(chǎng)景
使用場(chǎng)景
在ARC
下,AutoreleasePool
主要應(yīng)用在大量創(chuàng)建臨時(shí)對(duì)象的場(chǎng)景,通過(guò)AutoreleasePool
控制內(nèi)存峰值,是一個(gè)很好的選擇。
NSAutoreleasePool
在MRC
可以調(diào)用NSAutoreleasePool
使對(duì)象延遲釋放,在ARC
下這個(gè)API
已經(jīng)被禁用。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // ... [pool release];
@autoreleasepool
除了NSAutoreleasePool
還可以使用@autoreleasepool
,并且蘋(píng)果推薦使用@autoreleasepool
,因?yàn)檫@個(gè)API
性能更好,在ARC
下依然可以使用@autoreleasepool
。
無(wú)論是MRC
還是ARC
,autorelease
最大的作用,是在大量創(chuàng)建對(duì)象的同時(shí),通過(guò)修飾讓內(nèi)存得到提前釋放,從而降低內(nèi)存峰值。
@autoreleasepool { NSMutableArray *channelItemsJSONArray = [NSMutableArray arrayWithContentsOfFile:[self cachedChannelItemsFile]]; NSArray *items = [self channelItemsJSONArray]; if (![items writeToFile:[self cachedChannelItemsFile] atomically:YES]) { [channelItemsJSONArray writeToFile:[self cachedChannelItemsFile] atomically:YES]; } items = nil; }
__autoreleasing
在ARC
下,需要被自動(dòng)釋放的對(duì)象,可以用__autoreleasing
修飾,讓對(duì)象延遲釋放。
+ (NSArray *)parseString:(NSString *)originalM3U8Str m3u8Host:(NSString *)m3u8url error:(NSError *__autoreleasing *)errorPtr;
源碼分析
__AtAutoreleasePool結(jié)構(gòu)體
struct __AtAutoreleasePool { __AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush(); } ~__AtAutoreleasePool() { objc_autoreleasePoolPop(atautoreleasepoolobj); } void * atautoreleasepoolobj; };
@autoreleasepool
本質(zhì)上會(huì)被系統(tǒng)轉(zhuǎn)換成C++
的__AtAutoreleasePool
結(jié)構(gòu)體,@autoreleasepool
的大括號(hào)開(kāi)始,對(duì)應(yīng)著objc_autoreleasePoolPush
函數(shù)。大括號(hào)結(jié)束,對(duì)應(yīng)著objc_autoreleasePoolPop
函數(shù)。通過(guò)clang
命令將OC
代碼轉(zhuǎn)成C++
代碼,可以看到有一個(gè)__AtAutoreleasePool
結(jié)構(gòu)體。
__AtAutoreleasePool
結(jié)構(gòu)體在創(chuàng)建的時(shí)候會(huì)執(zhí)行objc_autoreleasePoolPush
函數(shù),在釋放的時(shí)候會(huì)執(zhí)行析構(gòu)函數(shù),并執(zhí)行objc_autoreleasePoolPop
函數(shù)。在這兩個(gè)函數(shù)內(nèi)部,會(huì)調(diào)用AutoreleasePoolPage
的push
和pop
函數(shù)。
AutoreleasePoolPage
在運(yùn)行時(shí)代碼中,objc_autoreleasePoolPop
和objc_autoreleasePoolPush
,都調(diào)用了AutoreleasePoolPage
類(lèi)的實(shí)現(xiàn)。
void * objc_autoreleasePoolPush(void) { return AutoreleasePoolPage::push(); } void objc_autoreleasePoolPop(void *ctxt) { AutoreleasePoolPage::pop(ctxt); }
在AutoreleasePoolPage
的定義中,可以看到有parent
和child
的定義,當(dāng)page
中對(duì)象太多存儲(chǔ)不下時(shí),會(huì)創(chuàng)建其他的page
對(duì)象來(lái)存儲(chǔ),AutoreleasePoolPage
的結(jié)構(gòu)是一個(gè)雙向鏈表。在插入新的autorelease
對(duì)象時(shí),也會(huì)從鏈表頭向后查找,直到找到未滿的page
。
class AutoreleasePoolPage { magic_t const magic; // 校驗(yàn)page的結(jié)構(gòu)是否完整 id *next; // 指向下一個(gè)可以存放autorelease對(duì)象的地址 pthread_t const thread; // 當(dāng)前所在的線程 AutoreleasePoolPage * const parent; // 當(dāng)前page的父節(jié)點(diǎn) AutoreleasePoolPage *child; // 當(dāng)前page的子節(jié)點(diǎn) uint32_t const depth; // page的深度 uint32_t hiwat; }
AutoreleasePoolPage
是一個(gè)C++
的類(lèi),每個(gè)page
占4096
個(gè)字節(jié),也就是16
進(jìn)制的0x1000
,也就是4kb
的空間。這些空間中,其自身的成員變量只占56
個(gè)字節(jié),也就是下面七個(gè)成員變量,每個(gè)占8
字節(jié),總共56
個(gè)字節(jié)。其他的四千多個(gè)字節(jié),都用來(lái)存放被autorelease
修飾的對(duì)象內(nèi)存地址。
POOL_BOUNDARY
POOL_BOUNDARY
的作用是,區(qū)分不同的自動(dòng)釋放池,也就是不同的@autoreleasepool
。調(diào)用push
時(shí),會(huì)傳入POOL_BOUNDARY
并返回一個(gè)地址例如0x1038
,0x1038
是不存儲(chǔ)@autorelease
對(duì)象的地址的,起到一個(gè)標(biāo)識(shí)作用,用來(lái)分割不同的@autoreleasepool
。
調(diào)用pop
時(shí),會(huì)傳入end
的地址,并從后到前調(diào)用對(duì)象的release
方法,直到POOL_BOUNDARY
為止。如果存在多個(gè)page
,會(huì)從child
的page
的最末尾開(kāi)始調(diào)用,直到POOL_BOUNDARY
。page
的結(jié)構(gòu)是一個(gè)棧結(jié)構(gòu),釋放的時(shí)候也是從棧頂開(kāi)始釋放。
next
指針指向棧頂,是棧里面很常見(jiàn)的一個(gè)設(shè)計(jì)。AutoreleasePoolPage
和POOL_BOUNDARY
的區(qū)別在于,AutoreleasePoolPage
負(fù)責(zé)維護(hù)存儲(chǔ)區(qū)域,而POOL_BOUNDARY
則負(fù)責(zé)分割存儲(chǔ)在page
中的對(duì)象地址,以@autoreleasepool
為單位進(jìn)行分割。
多層嵌套
@autoreleasepool { NSObject *p1 = [[NSObject alloc] init]; NSObject *p2 = [[NSObject alloc] init]; @autoreleasepool { NSObject *p3 = [[NSObject alloc] init]; @autoreleasepool { NSObject *p4 = [[NSObject alloc] init]; } } }
如果是多層@autoreleasepool
的嵌套,會(huì)用同一個(gè)AutoreleasePoolPage
對(duì)象。以下面的三個(gè)嵌套為例,在同一個(gè)page
中的順序是下圖這樣。不同的@autoreleasepool
以POOL_BOUNDARY
做分割。
push
創(chuàng)建一個(gè)autoreleasePool
之后,就會(huì)調(diào)用push
函數(shù)。在push
函數(shù)中會(huì)判斷是否調(diào)試模式下,如果調(diào)試模式會(huì)每次生成一個(gè)新的page
。debug
環(huán)境代碼可以直接忽略,只保留autoreleaseFast
函數(shù)。
static inline void *push() { id *dest; if (DebugPoolAllocation) { dest = autoreleaseNewPage(POOL_BOUNDARY); } else { dest = autoreleaseFast(POOL_BOUNDARY); } return dest; }
autoreleaseFast
在函數(shù)內(nèi)部,會(huì)通過(guò)hotPage
獲取當(dāng)前的page
,hotPage
函數(shù)內(nèi)部本質(zhì)上是一個(gè)page
和key
的映射。
- 如果
page
不為空并且有空間,則調(diào)用page
的add
函數(shù)將對(duì)象添加到page
中,并將POOL_BOUNDARY
添加在當(dāng)前的位置。 - 如果
page
已經(jīng)被創(chuàng)建但沒(méi)有空間,會(huì)調(diào)用autoreleaseFullPage
函數(shù)創(chuàng)建新的page
,并且將鏈表的末尾指向新創(chuàng)建的page
。 - 如果沒(méi)有創(chuàng)建
page
,則調(diào)用autoreleaseNoPage
函數(shù)創(chuàng)建一個(gè)新的page
,并且將當(dāng)前線程的hotPage
設(shè)置為新創(chuàng)建的page
。
static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); if (page && !page->full()) { return page->add(obj); } else if (page) { return autoreleaseFullPage(obj, page); } else { return autoreleaseNoPage(obj); } }
autoreleaseFullPage
- 在
autoreleaseFullPage
函數(shù)中,會(huì)從page
的鏈表中,從前往后找到末尾的節(jié)點(diǎn)。 - 創(chuàng)建一個(gè)新的
page
,在創(chuàng)建函數(shù)AutoreleasePoolPage
中會(huì)處理parent
和child
指針的問(wèn)題,返回的page
可以直接用。 - 調(diào)用
setHotPage
將page
設(shè)置到哈希表中,并且調(diào)用page
的add
函數(shù)將autorelease
修飾的對(duì)象,添加到page
中。
static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) { do { if (page->child) page = page->child; else page = new AutoreleasePoolPage(page); } while (page->full()); setHotPage(page); return page->add(obj); }
autoreleaseNoPage
autoreleaseNoPage
函數(shù)的核心代碼比較簡(jiǎn)單,就是創(chuàng)建一個(gè)新的page
,隨后設(shè)置POOL_BOUNDARY
標(biāo)志,并且把對(duì)象添加進(jìn)去。在函數(shù)中需要留意POOL_BOUNDARY
標(biāo)志,很多地方都用來(lái)做page
是否為空的判斷。
static __attribute__((noinline)) id *autoreleaseNoPage(id obj) { AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); setHotPage(page); if (pushExtraBoundary) { page->add(POOL_BOUNDARY); } return page->add(obj); }
add
add
函數(shù)比較簡(jiǎn)單,核心邏輯就是將obj
放入next
指針的位置,并且對(duì)next
指針進(jìn)行++
,指向下一個(gè)位置。*next++
表示先用后加,先將obj
存入next的地址,隨后+1
。
id *add(id obj) { ASSERT(!full()); unprotect(); id *ret = next; *next++ = obj; protect(); return ret; }
pop
調(diào)用pop
函數(shù)時(shí),有三步處理。
- 判斷
autoreleasepool
是否為空,通過(guò)EMPTY_POOL_PLACEHOLDER
占位符判斷,為空則清空這個(gè)page
。 - 傳入的
stop
是否不等于POOL_BOUNDARY
標(biāo)識(shí),如果不等于則可能是一個(gè)有問(wèn)題的page
。 - 調(diào)用
popPage
方法,釋放對(duì)象。
static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; // 1. if (token == (void*)EMPTY_POOL_PLACEHOLDER) { page = hotPage(); if (!page) { return setHotPage(nil); } page = coldPage(); token = page->begin(); } else { page = pageForPointer(token); } // 2. stop = (id *)token; if (*stop != POOL_BOUNDARY) { if (stop == page->begin() && !page->parent) { } else { return badPop(token); } } // 3. return popPage<false>(token, page, stop); }
popPage
popPage
函數(shù)核心代碼就是調(diào)用releaseUntil
函數(shù),在最開(kāi)始會(huì)調(diào)用releaseUntil
函數(shù)去完成釋放操作。
按照page
達(dá)到一半就擴(kuò)容的原則,后面的if
語(yǔ)句會(huì)判斷執(zhí)行pop
后page
鏈表的狀態(tài)。
如果少于半滿,就將子節(jié)點(diǎn)刪除。
如果大于半滿,則保留子節(jié)點(diǎn),并刪除后面的節(jié)點(diǎn)。
static void popPage(void *token, AutoreleasePoolPage *page, id *stop) { page->releaseUntil(stop); if (page->child) { if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); } } }
releaseUntil
在releaseUntil
函數(shù)內(nèi)部,核心邏輯是從當(dāng)前page
,從后到前調(diào)用objc_release
,釋放被autorelease
修飾的對(duì)象。
- 獲取當(dāng)前的
hotPage
。 - 判斷
page
是否為空,如果為空則表示里面的對(duì)象被釋放完,則將page
的父節(jié)點(diǎn)page
設(shè)置為hotPage
。 - 獲得上一個(gè)節(jié)點(diǎn),
->
的算數(shù)優(yōu)先級(jí)比--
要高,所以是先通過(guò)next
獲取當(dāng)前節(jié)點(diǎn)地址,這是一個(gè)為空的待存入節(jié)點(diǎn),隨后執(zhí)行--
操作獲取上一個(gè)對(duì)象地址。 - 通過(guò)
memset
將上一個(gè)節(jié)點(diǎn)釋放。 - 判斷上一個(gè)節(jié)點(diǎn)是否占位符號(hào)
POOL_BOUNDARY
,如果不是則調(diào)用objc_release
釋放對(duì)象。 - 在
while
循環(huán)結(jié)束后,將當(dāng)前page
設(shè)置為hotPage
。
void releaseUntil(id *stop) { while (this->next != stop) { AutoreleasePoolPage *page = hotPage(); while (page->empty()) { page = page->parent; setHotPage(page); } page->unprotect(); id obj = *--page->next; memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); if (obj != POOL_BOUNDARY) { objc_release(obj); } } setHotPage(this); }
autorelease
對(duì)象調(diào)用autorelease
方法會(huì)被編譯器轉(zhuǎn)換為objc_autoreleaseReturnValue
方法,并且經(jīng)過(guò)多層調(diào)用,會(huì)來(lái)到底層的autorelease
函數(shù)。
在這個(gè)函數(shù)中會(huì)判斷傳入的對(duì)象是否tagged pointer
,因?yàn)?code>tagged pointer沒(méi)有引用計(jì)數(shù)的概念。隨后會(huì)調(diào)用autoreleaseFast
函數(shù),函數(shù)內(nèi)部調(diào)用add
函數(shù)將obj
對(duì)象加入到page
中,并且會(huì)判斷是否需要?jiǎng)?chuàng)建新的page
。
static inline id autorelease(id obj) { assert(!obj->isTaggedPointer()); id *dest __unused = autoreleaseFast(obj); return obj; }
hotPage
hotPage
可以被理解為,page
鏈表的末尾,也就是調(diào)用push
函數(shù)被插入的位置。執(zhí)行hotPage
函數(shù)獲取,以及調(diào)用setHotPage
設(shè)置,都是操作的鏈表的末尾page
。
AutoreleasePoolPage
對(duì)象和線程一一對(duì)應(yīng),并且都被存儲(chǔ)在tls
的哈希表中。通過(guò)tls_get_direct
函數(shù)并傳入key
可以獲取到對(duì)應(yīng)的自動(dòng)釋放池。
static inline AutoreleasePoolPage *hotPage() { AutoreleasePoolPage *result = (AutoreleasePoolPage *) tls_get_direct(key); if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; if (result) result->fastcheck(); return result; }
hotPage
函數(shù)中的判斷是下面的定義,這個(gè)標(biāo)示意思是當(dāng)前page
為空,也就是從未存儲(chǔ)過(guò)任何對(duì)象。是一個(gè)標(biāo)志位,下面是標(biāo)志位的定義。
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
coldPage
coldPage
只有獲取函數(shù),沒(méi)有設(shè)置函數(shù)。這是因?yàn)?code>coldPage函數(shù)本質(zhì)上,就是尋找page
鏈表的根節(jié)點(diǎn),從源碼中的while
循環(huán)可以看到。
static inline AutoreleasePoolPage *coldPage() { AutoreleasePoolPage *result = hotPage(); if (result) { while (result->parent) { result = result->parent; result->fastcheck(); } } return result; }
調(diào)試
_objc_autoreleasePoolPrint
如果想調(diào)試自動(dòng)釋放池,可以通過(guò)_objc_autoreleasePoolPrint
私有API
來(lái)進(jìn)行。將項(xiàng)目改為MRC
,并且在命令行項(xiàng)目中增加下面這些調(diào)試代碼。
int main(int argc, const char * argv[]) { _objc_autoreleasePoolPrint(); // print1 @autoreleasepool { _objc_autoreleasePoolPrint(); // print2 Person *p1 = [[[Person alloc] init] autorelease]; Person *p2 = [[[Person alloc] init] autorelease]; _objc_autoreleasePoolPrint(); // print3 } _objc_autoreleasePoolPrint(); // print4 return 0; }
打印結(jié)果如下,可以看到POOL_BOUNDARY
在page
中也占了一個(gè)位置。
objc[68122]: ############## (print1) objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[68122]: 0 releases pending. // 當(dāng)前自動(dòng)釋放池中沒(méi)有任何對(duì)象 objc[68122]: [0x102802000] ................ PAGE (hot) (cold) objc[68122]: ############## objc[68122]: ############## (print2) objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[68122]: 1 releases pending. // 當(dāng)前自動(dòng)釋放池中有1個(gè)對(duì)象,這個(gè)對(duì)象為POOL_BOUNDARY objc[68122]: [0x102802000] ................ PAGE (hot) (cold) objc[68122]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY objc[68122]: ############## objc[68122]: ############## (print3) objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[68122]: 3 releases pending. // 當(dāng)前自動(dòng)釋放池中有3個(gè)對(duì)象 objc[68122]: [0x102802000] ................ PAGE (hot) (cold) objc[68122]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY objc[68122]: [0x102802040] 0x100704a10 HTPerson //p1 objc[68122]: [0x102802048] 0x10075cc30 HTPerson //p2 objc[68122]: ############## objc[68156]: ############## (print4) objc[68156]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[68156]: 0 releases pending. // 當(dāng)前自動(dòng)釋放池中沒(méi)有任何對(duì)象,因?yàn)锧autoreleasepool作用域結(jié)束,調(diào)用pop方法釋放了對(duì)象 objc[68156]: [0x100810000] ................ PAGE (hot) (cold) objc[68156]: ##############
UIApplicationMain
項(xiàng)目中經(jīng)常會(huì)看到下面的代碼,很多人的解釋是“這個(gè)autoreleasepool
是為了釋放主線程的autorelease
對(duì)象的”。但是,這個(gè)說(shuō)法是錯(cuò)誤的。autoreleasepool
只負(fù)責(zé)自己作用域中添加的對(duì)象,而主線程在運(yùn)行過(guò)程中,也會(huì)隱式創(chuàng)建autoreleasepool
對(duì)象,這個(gè)pool
是包含在main
函數(shù)的pool
里面的。
所以,主線程runloop
每次執(zhí)行循環(huán)后,釋放的對(duì)象是主線程的。而main
函數(shù)的autoreleasepool
釋放的,是main
函數(shù)中直接創(chuàng)建的對(duì)象。
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
釋放時(shí)機(jī)
區(qū)分
如果是在viewDidLoad
方法中創(chuàng)建一個(gè)autorelease
對(duì)象,并不是在這個(gè)方法結(jié)束后釋放對(duì)象,這個(gè)說(shuō)法是錯(cuò)誤的。即便執(zhí)行到viewDidAppear
,依然不會(huì)釋放對(duì)象。
被autorelease
修飾的對(duì)象,釋放時(shí)機(jī)有兩種。
- 如果通過(guò)代碼添加一個(gè)
autoreleasepool
,在作用域結(jié)束時(shí),隨著pool
的釋放,就會(huì)釋放pool
中的對(duì)象。這種情況是及時(shí)釋放的,并不依賴(lài)于runloop
。 - 另一種就是由系統(tǒng)自動(dòng)進(jìn)行釋放,系統(tǒng)會(huì)在
runloop
開(kāi)始的時(shí)候創(chuàng)建一個(gè)pool
,結(jié)束的時(shí)候會(huì)對(duì)pool
中的對(duì)象執(zhí)行release
操作。
runloop
如果是系統(tǒng)創(chuàng)建的pool
,需要手動(dòng)開(kāi)啟runloop
,主線程默認(rèn)已經(jīng)開(kāi)啟并運(yùn)行,子線程需要調(diào)用currentRunLoop
方法開(kāi)啟并運(yùn)行runloop
,子線程中系統(tǒng)創(chuàng)建pool
的流程才會(huì)正常工作。
包括主線程在內(nèi)的每個(gè)線程,如果在線程中使用到了AutoreleasePool
,則會(huì)創(chuàng)建兩個(gè)Observer
并添加到當(dāng)前線程的Runloop
中,通過(guò)這兩個(gè)Observer
進(jìn)行對(duì)象的自動(dòng)內(nèi)存管理。
// activities = 0x1,kCFRunLoopEntry <CFRunLoopObserver 0x60000012f000 [0x1135c2bb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10eee6276)} // activities = 0xa0,kCFRunLoopBeforeWaiting | kCFRunLoopExit <CFRunLoopObserver 0x60000012ef60 [0x1135c2bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10eee6276)}
首先會(huì)創(chuàng)建一個(gè)Observer
并監(jiān)聽(tīng)kCFRunLoopEntry
消息,時(shí)機(jī)是在進(jìn)入Runloop
前,此Observer
的優(yōu)先級(jí)設(shè)置為-2147483647
的最高優(yōu)先級(jí),以保證回調(diào)發(fā)生在Runloop
其他事件前。
然后創(chuàng)建另一個(gè)Observer
,并監(jiān)聽(tīng)kCFRunLoopBeforeWaiting
和kCFRunLoopExit
消息,時(shí)機(jī)分別在進(jìn)入Runloop
休眠和退出Runloop
時(shí),將Observer
的優(yōu)先級(jí)設(shè)置為2147483647
,以保證回調(diào)發(fā)生在Runloop
其他事件之后。
兩個(gè)Observer
都有相同的回調(diào)函數(shù)_wrapRunLoopWithAutoreleasePoolHandler
,在第一次回調(diào)時(shí)會(huì)在內(nèi)部調(diào)用_objc_autoreleasePoolPush
函數(shù),創(chuàng)建自動(dòng)釋放池。
在kCFRunLoopBeforeWaiting
將要進(jìn)入休眠前,調(diào)用_objc_autoreleasePoolPop
函數(shù)釋放自動(dòng)釋放池中的對(duì)象,并調(diào)用_objc_autoreleasePoolPush
函數(shù)創(chuàng)建一個(gè)新的釋放池。在kCFRunLoopExit
將要退出Runloop
時(shí)調(diào)用_objc_autoreleasePoolPop
函數(shù),釋放自動(dòng)釋放池中的對(duì)象。
以上就是swift語(yǔ)言AutoreleasePool原理及使用場(chǎng)景的詳細(xì)內(nèi)容,更多關(guān)于swift AutoreleasePool 原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
swift 3.0 正則表達(dá)式查找/替換字符的實(shí)現(xiàn)代碼
正則表達(dá)式使用單個(gè)字符串來(lái)描述、匹配一系列符合某個(gè)句法規(guī)則的字符串。本文重點(diǎn)給大家介紹swift 3.0 正則表達(dá)式查找/替換字符的實(shí)現(xiàn)代碼,需要的朋友參考下吧2017-08-08SwiftUI?引導(dǎo)頁(yè)界面實(shí)現(xiàn)示例
這篇文章主要為大家介紹了SwiftUI?引導(dǎo)頁(yè)界面實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09swift中defer幾個(gè)簡(jiǎn)單的使用場(chǎng)景詳解
在Swift 2.0中,Apple提供了defer關(guān)鍵字,讓我們可以實(shí)現(xiàn)同樣的效果,這篇文章主要介紹了關(guān)于swift中defer幾個(gè)簡(jiǎn)單的使用場(chǎng)景的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用defer具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧。2018-03-03Swift開(kāi)發(fā)之UITableView狀態(tài)切換效果
這篇文章主要介紹了Swift開(kāi)發(fā)之UITableView狀態(tài)切換效果的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08LeetCode?題解?Swift?有效的完全平方數(shù)
這篇文章主要為大家介紹了LeetCode?題解?Swift?有效的完全平方數(shù)方案示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Swift版使用ThPullRefresh實(shí)現(xiàn)下拉上拉刷新數(shù)據(jù)
這篇文章主要介紹了Swift版使用ThPullRefresh實(shí)現(xiàn)下拉上拉刷新數(shù)據(jù),需要的朋友可以參考下2016-01-01