中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

不可錯過的iOS開發技巧有哪些

發布時間:2021-12-24 15:19:03 來源:億速云 閱讀:171 作者:小新 欄目:移動開發

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

主要記錄了以下幾個問題:

NSString屬性什么時候用copy,什么時候用strong?

Foundation中的斷言處理

IBOutletCollection

NSRecursiveLock遞歸鎖的使用

NSHashTable

NSString屬性什么時候用copy,什么時候用strong?

我們在聲明一個NSString屬性時,對于其內存相關特性,通常有兩種選擇(基于ARC環境):strong與copy。那這兩者有什么區別呢?什么時候該用strong,什么時候該用copy呢?讓我們先來看個例子。

示例

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

  1. @interface TestStringClass () 

  2.  

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

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

  5.  

  6. @end

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

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

  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. }

其輸出結果是:

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

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

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

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

改成:

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

其輸出結果是:

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

可以發現,此時copy屬性字符串已不再指向string字符串對象,而是深拷貝了string字符串,并讓_copyedString對象指向這個字符串。在MRC環境下,打印兩者的引用計數,可以看到string對象的引用計數是2,而_copyedString對象的引用計數是1。

此時,我們如果去修改string字符串的話,可以看到:因為_strongString與string是指向同一對象,所以_strongString的值也會跟隨著改變(需要注意的是,此時_strongString的類型實際上是NSMutableString,而不是NSString);而_copyedString是指向另一個對象的,所以并不會改變。

結論

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

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

當源字符串是NSMutableString時,strong屬性只是增加了源字符串的引用計數,而copy屬性則是對源字符串做了次深拷貝,產生一個新的對象,且copy屬性對象指向這個新的對象。另外需要注意的是,這個copy屬性對象的類型始終是NSString,而不是NSMutableString,因此其是不可變的。

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

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

關于字符串的內存管理,還有些有意思的東西,可以參考NSString特性分析學習。

參考

NSString copy not copying?

NSString特性分析學習

NSString什么時候用copy,什么時候用strong

Foundation中的斷言處理

經常在看一些第三方庫的代碼時,或者自己在寫一些基礎類時,都會用到斷言。所以在此總結一下Objective-C中關于斷言的一些問題。

Foundation中定義了兩組斷言相關的宏,分別是:

NSAssert / NSCAssert NSParameterAssert / NSCParameterAssert

這兩組宏主要在功能和語義上有所差別,這些區別主要有以下兩點:

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

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

當斷言失敗時,通常是會拋出一個如下所示的異常:

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

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

我們很少直接去調用NSAssertionHandler的斷言處理方法,通常都是自動調用的。

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

  1. // 返回與當前線程的NSAssertionHandler對象。 

  2. // 如果當前線程沒有相關的斷言處理器,則該方法會創建一個并指定給當前線程 

  3. + (NSAssertionHandler *)currentHandler 

  4.  

  5. // 當NSCAssert或NSCParameterAssert斷言失敗時,會調用這個方法 

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

  7.  

  8. // 當NSAssert或NSParameterAssert斷言失敗時,會調用這個方法 

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

另外,還定義了一個常量字符串,

NSString * const NSAssertionHandlerKey;

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

關于斷言,還需要注意的一點是在Xcode 4.2以后,在release版本中斷言是默認關閉的,這是由宏NS_BLOCK_ASSERTIONS來處理的。也就是說,當編譯release版本時,所有的斷言調用都是無效的。

我們可以自定義一個繼承自NSAssertionHandler的斷言處理類,來實現一些我們自己的需求。如Mattt Thompson的NSAssertionHandler實例一樣:

  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

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

如下代碼所示:

  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. }

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

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

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與相關文件做連接時,我們經常會用到兩個關鍵字:IBOutlet和IBAction。經常用xib或storyboard的童鞋應該用這兩上關鍵字非常熟悉了。不過UIKit還提供了另一個偽關鍵字IBOutletCollection,我們使用這個關鍵字,可以將界面上一組相同的控件連接到同一個數組中

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

#ifndef IBOutletCollection #define IBOutletCollection(ClassName) #endif

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

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

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

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

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

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

- (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]; }]; } }

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

不過在使用IBOutletCollection時,需要注意兩點:

IBOutletCollection集合中對象的順序是不確定的。我們通過調試方法可以看到集合中對象的順序跟我們連接的順序是一樣的。但是這個順序可能會因為不同版本的Xcode而有所不同。所以我們不應該試圖在代碼中去假定這種順序。

不管IBOutletCollection(ClassName)中的控件是什么,屬性的類型始終是NSArray。實際上,我們可以聲明是任何類型,如NSSet,NSMutableArray,甚至可以是UIColor,但不管我們在此設置的是什么類,IBOutletCollection屬性總是指向一個NSArray數組。

關于第二點,我們以上面的scrollViews為例,作如下修改:

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

實際上我們在控制臺打印這個scrollViews時,結果如下所示:

(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}> )

可以看到,它指向的是一個NSArray數組。

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

參考

IBAction / IBOutlet / IBOutletCollection

IBOutletCollection.m

NSRecursiveLock遞歸鎖的使用

NSRecursiveLock實際上定義的是一個遞歸鎖,這個鎖可以被同一線程多次請求,而不會引起死鎖。這主要是用在循環或遞歸操作中。我們先來看一個示例:

  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. });

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

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

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

所以,對上面的代碼進行一下改造,

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

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

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

NSRecursiveLock除了實現NSLocking協議的方法外,還提供了兩個方法,分別如下:

  1. // 在給定的時間之前去嘗試請求一個鎖 

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

  3.  

  4. // 嘗試去請求一個鎖,并會立即返回一個布爾值,表示嘗試是否成功 

  5. - (BOOL)tryLock 

  6.  

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

  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. });

在前面的代碼中,我們又添加了一段代碼,增加一個線程來獲取遞歸鎖。我們在第二個線程中嘗試去獲取遞歸鎖,當然這種情況下是會失敗的,輸出結果如下:

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

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

@property(copy) NSString *name

我們可以使用這個字符串來標識一個鎖。Cocoa也會使用這個name作為錯誤描述信息的一部分。

參考

NSRecursiveLock Class Reference

Objective-C中不同方式實現鎖(二)

NSHashTable

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

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

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

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

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

它的成員可以在添加時被拷貝。

它的成員可以使用指針來標識是否相等及做hash檢測。

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

初始化NSHashTable時,我們可以設置一個初始選項,這個選項確定了這個NSHashTable對象后面所有的行為。這個選項是由NSHashTableOptions枚舉來定義的,如下所示:

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

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

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

  1. // 具體調用如下 

  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. 這段代碼的輸出結果如下: 

  33.  

  34. hash table [testWeakMemory] : NSHashTable { 

  35. [6] <NSObject: 0x7fa2b1562670> 

  36. hash table [init]: NSHashTable { 

  37. }

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

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

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

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.”“ 問題處理

最近在靜態庫中寫了一個XXViewController類,然后在主工程的xib中,將xib的類指定為XXViewController,程序運行時,報了如下錯誤:

Unknown class XXViewController in Interface Builder file.

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

其實這個問題與Interface Builder無關,最直接的原因還是相關的symbol沒有從靜態庫中加載進來。這種問題的處理就是在Target的”Build Setting”&ndash;>“Other Link Flags”中加上”-all_load -ObjC”這兩個標識位,這樣就OK了。

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

我們的某個業務有這么一個需求,進入一個列表后需要立馬又push一個web頁面,做一些活動的推廣。在iOS 8上,我們的實現是一切OK的;但到了iOS 7上,就發現這個web頁面push不出來了,同時控制臺給了一條警告消息,即如下:

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

在這種情況下,點擊導航欄中的返回按鈕時,直接顯示一個黑屏。

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

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

意思是說在當前視圖控制器完成顯示之前,又試圖去顯示一個新的視圖控制器。

于是我們去排查代碼,果然發現,在viewDidLoad里面去做了次網絡請求操作,且請求返回后就去push這個web活動推廣頁。此時,當前的視圖控制器可能并未顯示完成(即未完成push操作)。

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

當幾乎同時將兩個視圖控制器push到當前的導航控制器棧中時,或者同時pop兩個不同的視圖控制器,就會出現不確定的結果。所以我們應該確保同一時間,對同一個導航控制器棧只有一個操作,即便當前的視圖控制器正在動畫過程中,也不應該再去push或pop一個新的視圖控制器。

所以***我們把web活動的數據請求放到了viewDidAppear里面,并做了些處理,這樣問題就解決了。

感謝你能夠認真閱讀完這篇文章,希望小編分享的“不可錯過的iOS開發技巧有哪些”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

ios
AI

天峻县| 含山县| 开封县| 修文县| 托克托县| 全州县| 禹城市| 黄山市| 永登县| 白朗县| 新田县| 晋中市| 城口县| 盱眙县| 县级市| 东宁县| 独山县| 托克逊县| 淮南市| 竹山县| 兴仁县| 邮箱| 赣榆县| 临沂市| 丰都县| 永州市| 宁陵县| 宾阳县| 通海县| 潜江市| 淮滨县| 新建县| 多伦县| 自贡市| 姜堰市| 志丹县| 南康市| 青铜峡市| 武隆县| 高邑县| 建瓯市|