溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

不可錯(cuò)過的iOS開發(fā)技巧有哪些

發(fā)布時(shí)間:2021-12-24 15:19:03 來源:億速云 閱讀:170 作者:小新 欄目:移動(dòng)開發(fā)

這篇文章主要介紹了不可錯(cuò)過的iOS開發(fā)技巧有哪些,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

主要記錄了以下幾個(gè)問題:

NSString屬性什么時(shí)候用copy,什么時(shí)候用strong?

Foundation中的斷言處理

IBOutletCollection

NSRecursiveLock遞歸鎖的使用

NSHashTable

NSString屬性什么時(shí)候用copy,什么時(shí)候用strong?

我們?cè)诼暶饕粋€(gè)NSString屬性時(shí),對(duì)于其內(nèi)存相關(guān)特性,通常有兩種選擇(基于ARC環(huán)境):strong與copy。那這兩者有什么區(qū)別呢?什么時(shí)候該用strong,什么時(shí)候該用copy呢?讓我們先來看個(gè)例子。

示例

我們定義一個(gè)類,并為其聲明兩個(gè)字符串屬性,如下所示:

  1. @interface TestStringClass () 

  2.  

  3. @property (nonatomic, strong) NSString *strongString; 

  4. @property (nonatomic, copy) NSString *copyedString; 

  5.  

  6. @end

上面的代碼聲明了兩個(gè)字符串屬性,其中一個(gè)內(nèi)存特性是strong,一個(gè)是copy。下面我們來看看它們的區(qū)別。

首先,我們用一個(gè)不可變字符串來為這兩個(gè)屬性賦值,

  1. - (void)test { 

  2.  

  3. NSString *string = [NSString stringWithFormat:@"abc"]; 

  4. self.strongString = string; 

  5. self.copyedString = string; 

  6.  

  7. NSLog(@"origin string: %p, %p", string, &string); 

  8. NSLog(@"strong string: %p, %p", _strongString, &_strongString); 

  9. NSLog(@"copy string: %p, %p", _copyedString, &_copyedString); 

  10. }

其輸出結(jié)果是:

origin string: 0x7fe441592e20, 0x7fff57519a48 strong string: 0x7fe441592e20, 0x7fe44159e1f8 copy string: 0x7fe441592e20, 0x7fe44159e200

我們要以看到,這種情況下,不管是strong還是copy屬性的對(duì)象,其指向的地址都是同一個(gè),即為string指向的地址。如果我們換作MRC環(huán)境,打印string的引用計(jì)數(shù)的話,會(huì)看到其引用計(jì)數(shù)值是3,即strong操作和copy操作都使原字符串對(duì)象的引用計(jì)數(shù)值加了1。

接下來,我們把string由不可變改為可變對(duì)象,看看會(huì)是什么結(jié)果。即將下面這一句

NSString *string = [NSString stringWithFormat:@"abc"];

改成:

NSMutableString *string = [NSMutableString stringWithFormat:@"abc"];

其輸出結(jié)果是:

origin string: 0x7ff5f2e33c90, 0x7fff59937a48 strong string: 0x7ff5f2e33c90, 0x7ff5f2e2aec8 copy string: 0x7ff5f2e2aee0, 0x7ff5f2e2aed0

可以發(fā)現(xiàn),此時(shí)copy屬性字符串已不再指向string字符串對(duì)象,而是深拷貝了string字符串,并讓_copyedString對(duì)象指向這個(gè)字符串。在MRC環(huán)境下,打印兩者的引用計(jì)數(shù),可以看到string對(duì)象的引用計(jì)數(shù)是2,而_copyedString對(duì)象的引用計(jì)數(shù)是1。

此時(shí),我們?nèi)绻バ薷膕tring字符串的話,可以看到:因?yàn)開strongString與string是指向同一對(duì)象,所以_strongString的值也會(huì)跟隨著改變(需要注意的是,此時(shí)_strongString的類型實(shí)際上是NSMutableString,而不是NSString);而_copyedString是指向另一個(gè)對(duì)象的,所以并不會(huì)改變。

結(jié)論

由于NSMutableString是NSString的子類,所以一個(gè)NSString指針可以指向NSMutableString對(duì)象,讓我們的strongString指針指向一個(gè)可變字符串是OK的。

而上面的例子可以看出,當(dāng)源字符串是NSString時(shí),由于字符串是不可變的,所以,不管是strong還是copy屬性的對(duì)象,都是指向源對(duì)象,copy操作只是做了次淺拷貝。

當(dāng)源字符串是NSMutableString時(shí),strong屬性只是增加了源字符串的引用計(jì)數(shù),而copy屬性則是對(duì)源字符串做了次深拷貝,產(chǎn)生一個(gè)新的對(duì)象,且copy屬性對(duì)象指向這個(gè)新的對(duì)象。另外需要注意的是,這個(gè)copy屬性對(duì)象的類型始終是NSString,而不是NSMutableString,因此其是不可變的。

這里還有一個(gè)性能問題,即在源字符串是NSMutableString,strong是單純的增加對(duì)象的引用計(jì)數(shù),而copy操作是執(zhí)行了一次深拷貝,所以性能上會(huì)有所差異。而如果源字符串是NSString時(shí),則沒有這個(gè)問題。

所以,在聲明NSString屬性時(shí),到底是選擇strong還是copy,可以根據(jù)實(shí)際情況來定。不過,一般我們將對(duì)象聲明為NSString時(shí),都不希望它改變,所以大多數(shù)情況下,我們建議用copy,以免因可變字符串的修改導(dǎo)致的一些非預(yù)期問題。

關(guān)于字符串的內(nèi)存管理,還有些有意思的東西,可以參考NSString特性分析學(xué)習(xí)。

參考

NSString copy not copying?

NSString特性分析學(xué)習(xí)

NSString什么時(shí)候用copy,什么時(shí)候用strong

Foundation中的斷言處理

經(jīng)常在看一些第三方庫(kù)的代碼時(shí),或者自己在寫一些基礎(chǔ)類時(shí),都會(huì)用到斷言。所以在此總結(jié)一下Objective-C中關(guān)于斷言的一些問題。

Foundation中定義了兩組斷言相關(guān)的宏,分別是:

NSAssert / NSCAssert NSParameterAssert / NSCParameterAssert

這兩組宏主要在功能和語義上有所差別,這些區(qū)別主要有以下兩點(diǎn):

如果我們需要確保方法或函數(shù)的輸入?yún)?shù)的正確性,則應(yīng)該在方法(函數(shù))的頂部使用NSParameterAssert / NSCParameterAssert;而在其它情況下,使用NSAssert / NSCAssert。

另一個(gè)不同是介于C和Objective-C之間。NSAssert / NSParameterAssert應(yīng)該用于Objective-C的上下文(方法)中,而NSCAssert / NSCParameterAssert應(yīng)該用于C的上下文(函數(shù))中。

當(dāng)斷言失敗時(shí),通常是會(huì)拋出一個(gè)如下所示的異常:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'true is not equal to false'

Foundation為了處理斷言,專門定義了一個(gè)NSAssertionHandler來處理斷言的失敗情況。NSAssertionHandler對(duì)象是自動(dòng)創(chuàng)建的,用于處理失敗的斷言。當(dāng)斷言失敗時(shí),會(huì)傳遞一個(gè)字符串給NSAssertionHandler對(duì)象來描述失敗的原因。每個(gè)線程都有自己的NSAssertionHandler對(duì)象。當(dāng)調(diào)用時(shí),一個(gè)斷言處理器會(huì)打印包含方法和類(或函數(shù))的錯(cuò)誤消息,并引發(fā)一個(gè)NSInternalInconsistencyException異常。就像上面所看到的一樣。

我們很少直接去調(diào)用NSAssertionHandler的斷言處理方法,通常都是自動(dòng)調(diào)用的。

NSAssertionHandler提供的方法并不多,就三個(gè),如下所示:

  1. // 返回與當(dāng)前線程的NSAssertionHandler對(duì)象。 

  2. // 如果當(dāng)前線程沒有相關(guān)的斷言處理器,則該方法會(huì)創(chuàng)建一個(gè)并指定給當(dāng)前線程 

  3. + (NSAssertionHandler *)currentHandler 

  4.  

  5. // 當(dāng)NSCAssert或NSCParameterAssert斷言失敗時(shí),會(huì)調(diào)用這個(gè)方法 

  6. - (void)handleFailureInFunction:(NSString *)functionName file:(NSString *)object lineNumber:(NSInteger)fileName description:(NSString *)line, format,... 

  7.  

  8. // 當(dāng)NSAssert或NSParameterAssert斷言失敗時(shí),會(huì)調(diào)用這個(gè)方法 

  9. - (void)handleFailureInMethod:(SEL)selector object:(id)object file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format, ...

另外,還定義了一個(gè)常量字符串,

NSString * const NSAssertionHandlerKey;

主要是用于在線程的threadDictionary字典中獲取或設(shè)置斷言處理器。

關(guān)于斷言,還需要注意的一點(diǎn)是在Xcode 4.2以后,在release版本中斷言是默認(rèn)關(guān)閉的,這是由宏NS_BLOCK_ASSERTIONS來處理的。也就是說,當(dāng)編譯release版本時(shí),所有的斷言調(diào)用都是無效的。

我們可以自定義一個(gè)繼承自NSAssertionHandler的斷言處理類,來實(shí)現(xiàn)一些我們自己的需求。如Mattt Thompson的NSAssertionHandler實(shí)例一樣:

  1. @interface LoggingAssertionHandler : NSAssertionHandler 

  2. @end 

  3.  

  4. @implementation LoggingAssertionHandler 

  5.  

  6. - (void)handleFailureInMethod:(SEL)selector 

  7. object:(id)object 

  8. file:(NSString *)fileName 

  9. lineNumber:(NSInteger)line 

  10. description:(NSString *)format, ... 

  11. NSLog(@"NSAssert Failure: Method %@ for object %@ in %@#%i", NSStringFromSelector(selector), object, fileName, line); 

  12.  

  13. - (void)handleFailureInFunction:(NSString *)functionName 

  14. file:(NSString *)fileName 

  15. lineNumber:(NSInteger)line 

  16. description:(NSString *)format, ... 

  17. NSLog(@"NSCAssert Failure: Function (%@) in %@#%i", functionName, fileName, line); 

  18.  

  19. @end

上面說過,每個(gè)線程都有自己的斷言處理器。我們可以通過為線程的threadDictionary字典中的NSAssertionHandlerKey指定一個(gè)新值,來改變線程的斷言處理器。

如下代碼所示:

  1. - (BOOL)application:(UIApplication *)application 

  2. didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 

  3. NSAssertionHandler *assertionHandler = [[LoggingAssertionHandler alloc] init]; 

  4. [[[NSThread currentThread] threadDictionary] setValue:assertionHandler 

  5. forKey:NSAssertionHandlerKey]; 

  6. // ... 

  7.  

  8. return YES; 

  9. }

而什么時(shí)候應(yīng)該使用斷言呢?通常我們期望程序按照我們的預(yù)期去運(yùn)行時(shí),如調(diào)用的參數(shù)為空時(shí)流程就無法繼續(xù)下去時(shí),可以使用斷言。但另一方面,我們也需要考慮,在這加斷言確實(shí)是需要的么?我們是否可以通過更多的容錯(cuò)處理來使程序正常運(yùn)行呢?

Mattt Thompson在NSAssertionHandler中的倒數(shù)第二段說得挺有意思,在此摘抄一下:

But if we look deeper into NSAssertionHandler—and indeed, into our own hearts, there are lessons to be learned about our capacity for kindness and compassion; about our ability to forgive others, and to recover from our own missteps. We can't be right all of the time. We all make mistakes. By accepting limitations in ourselves and others, only then are we able to grow as individuals.

參考

NSAssertionHandler

NSAssertionHandler Class Reference

IBOutletCollection

在IB與相關(guān)文件做連接時(shí),我們經(jīng)常會(huì)用到兩個(gè)關(guān)鍵字:IBOutlet和IBAction。經(jīng)常用xib或storyboard的童鞋應(yīng)該用這兩上關(guān)鍵字非常熟悉了。不過UIKit還提供了另一個(gè)偽關(guān)鍵字IBOutletCollection,我們使用這個(gè)關(guān)鍵字,可以將界面上一組相同的控件連接到同一個(gè)數(shù)組中。

我們先來看看這個(gè)偽關(guān)鍵字的定義,可以從UIKit.framework的頭文件UINibDeclarations.h找到如下定義:

#ifndef IBOutletCollection #define IBOutletCollection(ClassName) #endif

另外,在Clang源碼中,有更安全的定義方式,如下所示:

#define IBOutletCollection(ClassName) __attribute__((iboutletcollection(ClassName)))

從上面的定義可以看到,與IBOutlet不同的是,IBOutletCollection帶有一個(gè)參數(shù),該參數(shù)是一個(gè)類名。

通常情況下,我們使用一個(gè)IBOutletCollection屬性時(shí),屬性必須是strong的,且類型是NSArray,如下所示:

@property (strong, nonatomic) IBOutletCollection(UIScrollView) NSArray *scrollViews;

假定我們的xib文件中有三個(gè)橫向的scrollView,我們便可以將這三個(gè)scrollView都連接至scrollViews屬性,然后在我們的代碼中便可以做一些統(tǒng)一處理,如下所示:

- (void)setupScrollViewImages { for (UIScrollView *scrollView in self.scrollViews) { [self.imagesData enumerateObjectsUsingBlock:^(NSString *imageName, NSUInteger idx, BOOL *stop) { UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(CGRectGetWidth(scrollView.frame) * idx, 0, CGRectGetWidth(scrollView.frame), CGRectGetHeight(scrollView.frame))]; imageView.contentMode = UIViewContentModeScaleAspectFill; imageView.image = [UIImage imageNamed:imageName]; [scrollView addSubview:imageView]; }]; } }

這段代碼會(huì)影響到三個(gè)scrollView。這樣做的好處是我們不需要手動(dòng)通過addObject:方法將scrollView添加到scrollViews中。

不過在使用IBOutletCollection時(shí),需要注意兩點(diǎn):

IBOutletCollection集合中對(duì)象的順序是不確定的。我們通過調(diào)試方法可以看到集合中對(duì)象的順序跟我們連接的順序是一樣的。但是這個(gè)順序可能會(huì)因?yàn)椴煌姹镜腦code而有所不同。所以我們不應(yīng)該試圖在代碼中去假定這種順序。

不管IBOutletCollection(ClassName)中的控件是什么,屬性的類型始終是NSArray。實(shí)際上,我們可以聲明是任何類型,如NSSet,NSMutableArray,甚至可以是UIColor,但不管我們?cè)诖嗽O(shè)置的是什么類,IBOutletCollection屬性總是指向一個(gè)NSArray數(shù)組。

關(guān)于第二點(diǎn),我們以上面的scrollViews為例,作如下修改:

@property (strong, nonatomic) IBOutletCollection(UIScrollView) NSSet *scrollViews;

實(shí)際上我們?cè)诳刂婆_(tái)打印這個(gè)scrollViews時(shí),結(jié)果如下所示:

(lldb) po self.scrollViews <__NSArrayI 0x1740573d0>( <UIScrollView: 0x12d60d770; frame = (0 0; 320 162); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x1740574f0>; layer = <CALayer: 0x174229480>; contentOffset: {0, 0}; contentSize: {0, 0}>, <UIScrollView: 0x12d60dee0; frame = (0 0; 320 161); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x174057790>; layer = <CALayer: 0x1742297c0>; contentOffset: {0, 0}; contentSize: {0, 0}>, <UIScrollView: 0x12d60e650; frame = (0 0; 320 163); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x1740579a0>; layer = <CALayer: 0x1742298e0>; contentOffset: {0, 0}; contentSize: {0, 0}> )

可以看到,它指向的是一個(gè)NSArray數(shù)組。

另外,IBOutletCollection實(shí)際上在iOS 4版本中就有了。不過,現(xiàn)在的Objective-C已經(jīng)支持object literals了,所以定義數(shù)組可以直接用@[],方便了許多。而且object literals方式可以添加不在xib中的用代碼定義的視圖,所以顯得更加靈活。當(dāng)然,兩種方式選擇哪一種,就看我們自己的實(shí)際需要和喜好了。

參考

IBAction / IBOutlet / IBOutletCollection

IBOutletCollection.m

NSRecursiveLock遞歸鎖的使用

NSRecursiveLock實(shí)際上定義的是一個(gè)遞歸鎖,這個(gè)鎖可以被同一線程多次請(qǐng)求,而不會(huì)引起死鎖。這主要是用在循環(huán)或遞歸操作中。我們先來看一個(gè)示例:

  1. NSLock *lock = [[NSLock alloc] init]; 

  2.  

  3. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 

  4.  

  5. static void (^RecursiveMethod)(int); 

  6.  

  7. RecursiveMethod = ^(int value) { 

  8.  

  9. [lock lock]; 

  10. if (value > 0) { 

  11.  

  12. NSLog(@"value = %d", value); 

  13. sleep(2); 

  14. RecursiveMethod(value - 1); 

  15. [lock unlock]; 

  16. }; 

  17.  

  18. RecursiveMethod(5); 

  19. });

這段代碼是一個(gè)典型的死鎖情況。在我們的線程中,RecursiveMethod是遞歸調(diào)用的。所以每次進(jìn)入這個(gè)block時(shí),都會(huì)去加一次鎖,而從第二次開始,由于鎖已經(jīng)被使用了且沒有解鎖,所以它需要等待鎖被解除,這樣就導(dǎo)致了死鎖,線程被阻塞住了。調(diào)試器中會(huì)輸出如下信息:

value = 5 *** -[NSLock lock]: deadlock (<NSLock: 0x1700ceee0> '(null)') *** Break on _NSLockError() to debug.

在這種情況下,我們就可以使用NSRecursiveLock。它可以允許同一線程多次加鎖,而不會(huì)造成死鎖。遞歸鎖會(huì)跟蹤它被lock的次數(shù)。每次成功的lock都必須平衡調(diào)用unlock操作。只有所有達(dá)到這種平衡,鎖***才能被釋放,以供其它線程使用。

所以,對(duì)上面的代碼進(jìn)行一下改造,

NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];

這樣,程序就能正常運(yùn)行了,其輸出如下所示:

value = 5 value = 4 value = 3 value = 2 value = 1

NSRecursiveLock除了實(shí)現(xiàn)NSLocking協(xié)議的方法外,還提供了兩個(gè)方法,分別如下:

  1. // 在給定的時(shí)間之前去嘗試請(qǐng)求一個(gè)鎖 

  2. - (BOOL)lockBeforeDate:(NSDate *)limit 

  3.  

  4. // 嘗試去請(qǐng)求一個(gè)鎖,并會(huì)立即返回一個(gè)布爾值,表示嘗試是否成功 

  5. - (BOOL)tryLock 

  6.  

  7. 這兩個(gè)方法都可以用于在多線程的情況下,去嘗試請(qǐng)求一個(gè)遞歸鎖,然后根據(jù)返回的布爾值,來做相應(yīng)的處理。如下代碼所示: 

  8.  

  9. NSRecursiveLock *lock = [[NSRecursiveLock alloc] init]; 

  10.  

  11. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 

  12.  

  13. static void (^RecursiveMethod)(int); 

  14.  

  15. RecursiveMethod = ^(int value) { 

  16.  

  17. [lock lock]; 

  18. if (value > 0) { 

  19.  

  20. NSLog(@"value = %d", value); 

  21. sleep(2); 

  22. RecursiveMethod(value - 1); 

  23. [lock unlock]; 

  24. }; 

  25.  

  26. RecursiveMethod(5); 

  27. }); 

  28.  

  29. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 

  30.  

  31. sleep(2); 

  32. BOOL flag = [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1]]; 

  33. if (flag) { 

  34. NSLog(@"lock before date"); 

  35.  

  36. [lock unlock]; 

  37. } else { 

  38. NSLog(@"fail to lock before date"); 

  39. });

在前面的代碼中,我們又添加了一段代碼,增加一個(gè)線程來獲取遞歸鎖。我們?cè)诘诙€(gè)線程中嘗試去獲取遞歸鎖,當(dāng)然這種情況下是會(huì)失敗的,輸出結(jié)果如下:

value = 5 value = 4 fail to lock before date value = 3 value = 2 value = 1

另外,NSRecursiveLock還聲明了一個(gè)name屬性,如下:

@property(copy) NSString *name

我們可以使用這個(gè)字符串來標(biāo)識(shí)一個(gè)鎖。Cocoa也會(huì)使用這個(gè)name作為錯(cuò)誤描述信息的一部分。

參考

NSRecursiveLock Class Reference

Objective-C中不同方式實(shí)現(xiàn)鎖(二)

NSHashTable

在看KVOController的代碼時(shí),又看到了NSHashTable這個(gè)類,所以就此整理一下。

NSHashTable效仿了NSSet(NSMutableSet),但提供了比NSSet更多的操作選項(xiàng),尤其是在對(duì)弱引用關(guān)系的支持上,NSHashTable在對(duì)象/內(nèi)存處理時(shí)更加的靈活。相較于NSSet,NSHashTable具有以下特性:

NSSet(NSMutableSet)持有其元素的強(qiáng)引用,同時(shí)這些元素是使用hash值及isEqual:方法來做hash檢測(cè)及判斷是否相等的。

NSHashTable是可變的,它沒有不可變版本。

它可以持有元素的弱引用,而且在對(duì)象被銷毀后能正確地將其移除。而這一點(diǎn)在NSSet是做不到的。

它的成員可以在添加時(shí)被拷貝。

它的成員可以使用指針來標(biāo)識(shí)是否相等及做hash檢測(cè)。

它可以包含任意指針,其成員沒有限制為對(duì)象。我們可以配置一個(gè)NSHashTable實(shí)例來操作任意的指針,而不僅僅是對(duì)象。

初始化NSHashTable時(shí),我們可以設(shè)置一個(gè)初始選項(xiàng),這個(gè)選項(xiàng)確定了這個(gè)NSHashTable對(duì)象后面所有的行為。這個(gè)選項(xiàng)是由NSHashTableOptions枚舉來定義的,如下所示:

enum {  // 默認(rèn)行為,強(qiáng)引用集合中的對(duì)象,等同于NSSet NSHashTableStrongMemory = 0,  // 在將對(duì)象添加到集合之前,會(huì)拷貝對(duì)象 NSHashTableCopyIn = NSPointerFunctionsCopyIn,  // 使用移位指針(shifted pointer)來做hash檢測(cè)及確定兩個(gè)對(duì)象是否相等; // 同時(shí)使用description方法來做描述字符串 NSHashTableObjectPointerPersonality = NSPointerFunctionsObjectPointerPersonality,  // 弱引用集合中的對(duì)象,且在對(duì)象被釋放后,會(huì)被正確的移除。 NSHashTableWeakMemory = NSPointerFunctionsWeakMemory }; typedef NSUInteger NSHashTableOptions;

當(dāng)然,我們還可以使用NSPointerFunctions來初始化,但只有使用NSHashTableOptions定義的這些值,才能確保NSHashTable的各個(gè)API可以正確的工作&mdash;包括拷貝、歸檔及快速枚舉。

個(gè)人認(rèn)為NSHashTable吸引人的地方在于可以持有元素的弱引用,而且在對(duì)象被銷毀后能正確地將其移除。我們來寫個(gè)示例:

  1. // 具體調(diào)用如下 

  2. @implementation TestHashAndMapTableClass { 

  3.  

  4. NSMutableDictionary *_dic; 

  5. NSSet *_set; 

  6.  

  7. NSHashTable *_hashTable; 

  8.  

  9. - (instancetype)init { 

  10.  

  11. self = [super init]; 

  12.  

  13. if (self) { 

  14.  

  15. [self testWeakMemory]; 

  16.  

  17. NSLog(@"hash table [init]: %@", _hashTable); 

  18.  

  19. return self; 

  20.  

  21. - (void)testWeakMemory { 

  22.  

  23. if (!_hashTable) { 

  24. _hashTable = [NSHashTable weakObjectsHashTable]; 

  25.  

  26. NSObject *obj = [[NSObject alloc] init]; 

  27.  

  28. [_hashTable addObject:obj]; 

  29.  

  30. NSLog(@"hash table [testWeakMemory] : %@", _hashTable); 

  31.  

  32. 這段代碼的輸出結(jié)果如下: 

  33.  

  34. hash table [testWeakMemory] : NSHashTable { 

  35. [6] <NSObject: 0x7fa2b1562670> 

  36. hash table [init]: NSHashTable { 

  37. }

可以看到,在離開testWeakMemory方法,obj對(duì)象被釋放,同時(shí)對(duì)象在集合中的引用也被安全的刪除。

這樣看來,NSHashTable似乎比NSSet(NSMutableSet)要好啊。那是不是我們就應(yīng)用都使用NSHashTable呢?Peter Steinberger在The Foundation Collection Classes給了我們一組數(shù)據(jù),顯示在添加對(duì)象的操作中,NSHashTable所有的時(shí)間差不多是NSMutableSet的2倍,而在其它操作中,性能大體相近。所以,如果我們只需要NSSet的特性,就盡量用NSSet。

另外,Mattt Thompson在NSHashTable & NSMapTable的結(jié)尾也寫了段挺有意思的話,在此直接摘抄過來:

As always, it's important to remember that programming is not about being clever: always approach a problem from the highest viable level of abstraction. NSSet and NSDictionary are great classes. For 99% of problems, they are undoubtedly the correct tool for the job. If, however, your problem has any of the particular memory management constraints described above, then NSHashTable & NSMapTable may be worth a look.

參考

NSHashTable Class Reference

NSHashTable & NSMapTable

NSHashTable & NSMapTable

The Foundation Collection Classes

零碎

(一) “Unknown class XXViewController in Interface Builder file.”“ 問題處理

最近在靜態(tài)庫(kù)中寫了一個(gè)XXViewController類,然后在主工程的xib中,將xib的類指定為XXViewController,程序運(yùn)行時(shí),報(bào)了如下錯(cuò)誤:

Unknown class XXViewController in Interface Builder file.

之前也遇到這個(gè)問題,但已記得不太清楚,所以又開始在stackoverflow上找答案。

其實(shí)這個(gè)問題與Interface Builder無關(guān),最直接的原因還是相關(guān)的symbol沒有從靜態(tài)庫(kù)中加載進(jìn)來。這種問題的處理就是在Target的”Build Setting”&ndash;>“Other Link Flags”中加上”-all_load -ObjC”這兩個(gè)標(biāo)識(shí)位,這樣就OK了。

(二)關(guān)于Unbalanced calls to begin/end appearance transitions for &hellip;問題的處理

我們的某個(gè)業(yè)務(wù)有這么一個(gè)需求,進(jìn)入一個(gè)列表后需要立馬又push一個(gè)web頁面,做一些活動(dòng)的推廣。在iOS 8上,我們的實(shí)現(xiàn)是一切OK的;但到了iOS 7上,就發(fā)現(xiàn)這個(gè)web頁面push不出來了,同時(shí)控制臺(tái)給了一條警告消息,即如下:

Unbalanced calls to begin/end appearance transitions for ...

在這種情況下,點(diǎn)擊導(dǎo)航欄中的返回按鈕時(shí),直接顯示一個(gè)黑屏。

我們到stackoverflow上查了一下,有這么一段提示:

occurs when you try and display a new viewcontroller before the current view controller is finished displaying.

意思是說在當(dāng)前視圖控制器完成顯示之前,又試圖去顯示一個(gè)新的視圖控制器。

于是我們?nèi)ヅ挪榇a,果然發(fā)現(xiàn),在viewDidLoad里面去做了次網(wǎng)絡(luò)請(qǐng)求操作,且請(qǐng)求返回后就去push這個(gè)web活動(dòng)推廣頁。此時(shí),當(dāng)前的視圖控制器可能并未顯示完成(即未完成push操作)。

Basically you are trying to push two view controllers onto the stack at almost the same time.

當(dāng)幾乎同時(shí)將兩個(gè)視圖控制器push到當(dāng)前的導(dǎo)航控制器棧中時(shí),或者同時(shí)pop兩個(gè)不同的視圖控制器,就會(huì)出現(xiàn)不確定的結(jié)果。所以我們應(yīng)該確保同一時(shí)間,對(duì)同一個(gè)導(dǎo)航控制器棧只有一個(gè)操作,即便當(dāng)前的視圖控制器正在動(dòng)畫過程中,也不應(yīng)該再去push或pop一個(gè)新的視圖控制器。

所以***我們把web活動(dòng)的數(shù)據(jù)請(qǐng)求放到了viewDidAppear里面,并做了些處理,這樣問題就解決了。

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“不可錯(cuò)過的iOS開發(fā)技巧有哪些”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

ios
AI