iOS筆記

Oranges發表於2017-12-23

381FE78B-9ED4-4405-AFB6-4B36843A2D1E.png

1.對於 NSString 這個類,判斷兩個例項相等時,==、isEqual 方法和 isEqualToString 方法有什麼區別?我們平常用的時候應該用哪一個呢?

==判斷的僅僅只是指標,而一般isEqual方法判斷的是內容,對於NSString來說,isEqual根isEqualToString的區別在於.前者還會判斷是不是同一個類,如果你能確定是兩個string相互比較,呼叫isEqualToString的效率要高一點,在isEqualToString: 裡面會判斷傳入引數的型別的;

2.假設有一個 NSString* foo,描述一下呼叫 [foo isKindOf:NSArray.class] 的時候,objc_msgSend 都做了哪些事情吧~~
 NSString *foo;
((BOOL (*)(id, SEL, Class))(void *)objc_msgSend)((id)foo, sel_registerName("isKindOfClass:"), ((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("class")));
複製程式碼

objc_msgSend 在尋找方法的時候,總體的流程是先找這個類。在這個例子裡,就是先找 NSString (或者更具體的、我們看不到的實現類比如 CFString 什麼的)這個類的方法列表,看看有沒有這個方法;如果沒有這個方法,就找它的父類…… 這樣一層一層往上找,直到 NSObject,就找到了這個方法。當然如果找到最上層,還找不到方法,還有 message forward 的處理. 每個類會對上面這個過程的結果有一個快取。具體實現是類似一張雜湊表,以 selector 為鍵,找到的 imp 為值。所以,第一步其實是到這個類的快取裡找,如果快取命中,就不用重複上面那個一層一層往父類找的過程了,如果沒找到,再進行上面尋找方法的流程

更多的介紹可以看下這篇文章,作者Mike Ash

3.載入圖片的方式:

1.imageNamed:有快取

2.imageWithContentsOfFile:無快取

注意:載入Assets.xcassets裡面的圖片:

打包後變成Assets.car,拿不到路徑,只能通過imageNamed:的方式來載入圖片,不能通過imageWithContentsOfFile方式來載入圖片

4. UISearchBar的一些設定

1.設定searBar輸入框的背景色

[[[self.searchBar.subviews objectAtIndex:0].subviews objectAtIndex:1] setBackgroundColor:[UIColor greenColor]];
複製程式碼

2.改變searchBar中佔位字的字型顏色

UITextField *searchField = [self.searchBar valueForKey:@"_searchField"];
[searchField setValue:[UIColor whiteColor] forKeyPath:@"_placeholderLabel.textColor"];
複製程式碼
5.寫過 java 的同學都知道,java 充滿了 try catch,做很多事情都要用 try 包裹。OC 裡也有 try catch 和 exception 結構,但我們工程中用得卻不多,這是為什麼呢? 另外,swift 呢?

在OC裡面的異常跟錯誤兩種。首先肯定的是try catch可以捕獲應用異常,而OC一旦發生異常就會崩潰,那麼try catch機制看起來可以幫助我們減少應用的崩潰,但是我們看看OC裡常見的異常有哪些: 1、越界 2、訊息傳送錯誤 3、zombie

1、3情況其實可以歸屬於一種大型別:記憶體使用錯誤。既然記憶體被錯誤使用了,那麼為了避免發生更嚴重的後果,我們應該直接讓程式崩潰而不是嘗試去catch它。因為一旦這個操作被catch了應用繼續執行的時候可能會訪問到私密的記憶體資料,從而導致更嚴重的後果, 2情況在訊息傳送錯誤的情況下,系統本身提供了訊息轉發的階段讓我們可以避免應用crash,而且通過轉發還能實現多種功能,因此catch這種異常並沒有什麼意義

另外就要提到OC的try-catch機制,本身作為C語言的擴充套件語言,異常捕獲機制基於C標準庫的setjmp跟longjmp函式實現,在嘗試捕獲一塊程式碼的異常的時候,try-catch做了大量的工作,具體可以參考 http://www.cnblogs.com/markhy/p/3169035.html 這篇文章,相比起catch異常的回報,消耗太大,所以OC不推薦使用try-catch,除了異常之後,應用故障還包括Error型別。Error型別就有趣的多,發生exception的時候基本而言應用是遇到了無法繼續執行下去的事件所觸發的。但是Error是針對某種特定操作過程中可能會發生錯誤,但是錯誤不致命,比如把NSData跟NSDictionary互轉的過程中可能會導致Error,但是後果也只是兩者其中一者為空,並不影響整個應用的安全性。又比如網路請求可能會發生Error。簡單來說iOS的異常是致命的,但是還有Error作為操作異常的補充,所以try-catch沒有足夠的使用條件,就是丟擲exception之後,方法裡剩餘程式碼本應release的物件不會再release,會造成記憶體洩漏。如果大量使用try catch,記憶體洩漏會越來越嚴重

6.在 OC 開發中常用哪些方式來報告有錯誤呢,比如說,資料解析失敗,網路請求失敗,連結中斷……等等

常用以下幾種方法:

  1. 異常返回值:如方法返回值為 BOOL,成功返回 YES,失敗返回 NO。或數學計算,出錯時返回 NAN (not a number)
  2. 用引數返回 error。傳入一個 error 地址,如果有錯誤就把錯誤通過那個地址傳出
  3. delegate 方法,比如我們熟知的網路請求,失敗時會呼叫一些 didFail 方法
  4. 有 delegate 就會有 block,也是很多網路框架常用的,引數傳入 success、failure block,成功調 success,失敗調 failure

我覺得 NSAssert 不算是一種錯誤處理,它主要是除錯時候用的,只能描述現階段對條件的要求,不是用來讓呼叫者知道有錯誤了,該處理錯誤了。java 裡不太一樣,可以用 assert 丟擲異常,OC 裡一般不這麼用。

7.怎麼把 error 用引數傳出去。兩點:1. 基本用法是 *error = [NSError errorWithDomain…] 2. 不要在 block 中產生這個 error,3.這個 testWithEnumBlockError 除了在 block 中產生 error 之外還有一個問題~~ 做實驗的時候這麼寫無所謂,但在實際工程中一定要避免 >< 是什麼呢?~
  • 就是用引數返回 error 的時候,在寫 *error = [NSError errorWithDomain…] 前 一定要判斷一下 if(error != nil) 不然就會 crash 啦

因為呼叫者可能不關心這個 error ,所以傳進來的就是 nil,nil 就是 0,修改地址為 0 處的值肯定不讓改的 ><

8.問題是,代理協議裡經常會有一些 @optional 的方法,delegate 有可能實現、也可能不實現這個方法。比如 UIScrollView,如果我們關心它的滾動事件,就寫一個 didScroll 方法,不關心就不寫。那麼,如果讓你來寫一個 UIScrollView,你怎麼知道該不該呼叫 delegate 的 didScroll 方法呢?~在不知道 delegate 實現沒實現 didScroll 方法時,如果我知道我滾了,該怎麼呼叫 delegate 方法呢?比如我直接調 [self.delegate didScroll…] 可能會 crash 是吧:
  • 最簡單的方法是判斷 if (self.delegate respondToSelector:…) 如果 respond 再調,如果這個方法呼叫不頻繁,這樣做就可以。不過,如果呼叫非常頻繁,可能還需要提高下效能,給 NSObject 加一個 category,裡面實現這些 delegate 方法(有一點是它的方法都以 carousel: 開頭,不容易跟本來已有的方法混淆,不然給 NSObject 加 category 可能會出現意料之外的結果),如果 delegate 沒有實現對應的方法,自然就走到了 category 的方法中。
  • 就是在設定 delegate 的時候,把它對於每個 delegate 方法是否 respond 分別存一個標誌位。最低限度,用一個 struct 來存,每個方法一個 bit 就可以,這樣的好處是不用每次再詢問是否 respond。有一個問題是,有可能在 delegate 設定之後,又通過 runtime 等方法增加了新的方法,從而改變了是否 respond delegate 方法的情況……如果是自己的工程裡,知道自己一般不會這麼作(一聲),這樣做就沒問題;如果是封裝控制元件給別人用,就可能要考慮這種情況 ><
9.@interface someClass(private) 這個用法,大家見過嗎?~ 這是什麼意思呢?~ 跟更為常見的 @interface someClass() 有什麼區別呢?~
  • http://stackoverflow.com/questions/1052233/iphone-obj-c-anonymous-category-or-private-category
  • 加不加這個 (private) 是不是沒區別。還是有一點區別的,加了之後會在 backtrace 顯示 -[SomeClass(Private) someMethod]
  • @interface MyClass() 是編譯時特性
  • @interface MyClass(private) 是執行時特性 #####10.

AAC1F30E174BE16D5D2FB597A86BD643.jpg

  • 答案是 第一個是 NSArray、第二個是 NSMutableArray 原因是 NSMutableDictionary 在 set key-value 的時候會把 key copy 一下。這就帶來一個問題,不支援 NSCopying 協議的類當不了 key(而 NSCache 就沒這個問題)
  • 在以上例子裡,雖然 NSMutableArray 被 copy 成 NSArray 了,但是如果你傳進去一個相同內容 NSMutableArray 的話還是能取出對應的值,這是因為 NSMutableArray 的 isEqual 實現沒 bug
  • 而有些別的類就不一定如此了,一個例子就是 NSIndexPath,有時候我們想用 UITableView 的 indexpath 快取一些東西,比如行高啊什麼的。如果直接在 cellForRow 裡面把它傳過來的 indexPath 放到 NSDictionary 裡面,下次取的時候可能就取不出來。因為它傳過來的實際可能是個 NSMutableIndexPath,比較好的做法是 手動再生成一個 [NSIndexPath indexPathForRow…] 然後直接把這個不可變的扔進字典,下次以同樣的方式生成 key 就肯定能取出來了。
11.用 NSCache 做快取比跟 NSDictionary 有什麼區別吧~~
  • NSCache 不會 copy key,而 NSDictionary 會之外,還有幾條
  • NSCache 是執行緒安全的,NSDictionary 不是
  • NSCache 有過期策略,具體策略無人知曉,可以認為跟 cost、訪問頻率都有關。既可以限制最大快取數,又可以限制最大記憶體。從這點來看是遠比 NSDictionary 適合做快取的,NSMutableArray 如果不加鎖,幾個執行緒一直往裡寫,中間可以出現一個 nil。NSMutableDictionary 我沒試過,但肯定會出問題,而 get、set 方法加鎖或者放到序列佇列裡就不會有執行緒安全問題了。猜想 NSCache 就是這麼實現的 #####12.正好進度快到多執行緒了,用 NSOperationQueue 和gcd做有什麼區別?- NsOperation好處大概就是這樣
  • NSOperationQueue可以取消,gcd一般發出去就發出去了,也可以取消
  • NSOperationQueue可以指定依賴關係
  • NSOperationQueue可以限制最大併發數
  • Operation物件可以重用,在有些業務情境下
  • 可以有任務的優先順序,gcd是隊伍的優先順序,NSOperation粒度細一些

Gcd的好處:

  • 寫起來程式碼短,因為沒那層包裝開銷小一些,
  • 可以取消, dispatch block cancel
13.dispatch_sync 如何造成死鎖?能寫出一個例子嘛?~ 為何會死鎖?平常工程開發中,如何安全避免這種死鎖?
  • SDWebImage的原始碼
    8B4A991CEA7879272EDD776F8D98347C.jpg
    就是先判一下是否在當前執行緒,如果在的話就直接執行,否則再 dispatch。如果自己用得多,也可以加這個巨集~ <只有主佇列和自己寫的序列佇列會有這問題>

14.今天總結一下 dealloc 裡該寫什麼和不該寫什麼,倉鼠寫幾條常見的,剩下的大家可以自己補充~

該寫什麼:

  • 從 NotificationCenter remove observer,remove KVO,把 timer invalidate,有些非 ARC 管理的比如 CFRelease,有些流要關閉,用了 RAC 持有 disposable 物件的可以 dispose,這些都是比較常見的
  • 還有一些會 removeGestureRecognizer,addToRunLoop 的 removeFromRunLoop,dispatch_release 掉自己持有的 queue,不過這些可能不是必要的
  • 有些人會取消掉這個物件應該接受的網路請求,當然如果沒停掉的話,返回來應該也沒什麼關係
  • 另外一點是如果有 delegate 可以這一步設成 nil,比如自己寫的 tableViewController 最好在 dealloc 裡把 _tableView.delegate = nil、dataSource = nil。雖然現在 tableView 的 dataSource 和 delegate 是 weak 的,但這樣似乎可以減少一些非常偶發的 bug……

不該做什麼:

  • 看一些老程式碼的時候,裡面會一口氣把自己的所有 property 都設成 nil,看下來一大串 _xxx = nil; _xxx = nil; 這個在現在的 ARC 環境是已經沒必要的了,不用浪費時間和篇幅來寫這個。
  • 最好不要呼叫非同步帶回撥的方法,因為等方法回來的時候這個物件肯定已經沒了。
  • 不要調 super dealloc,實際上想調也調不了…… ARC 下編譯器會自動調的
  • 在 init 方法和 dealloc 方法裡都不要用點語法,而是直接用下劃線 _property 進行存取。因為點語法會觸發 getter、setter 方法,不知道這些方法會被重寫成什麼,可能會涉及或者觸發一些不該在 dealloc 階段做的事比如 KVO 之類

15.在 iOS 裡如何實現同步機制吧~ 比如昨天偶然看到的一道面試題:如果讓你實現 atomic 屬性,你會怎麼實現?

  • 首先最常使用的是 @synchronized 塊,這是同步機制中最慢的一個,也是語法上最方便的一個。所以不太頻繁的話用這個即可~
  • 然後一種思路是加鎖。最簡單的有 NSLock,要注意可能的死鎖,為了解決可以考慮 NSRecursiveLock。這裡有一篇文章介紹了幾大常用的鎖:http://www.jianshu.com/p/6c8bf19eb10d
  • 另一種思路是用事件佇列。比如 gcd 可以建立 serial queue,扔進去的操作就會按順序依次執行了。一個更高效的方法是用 barrier block,可以把 setter 方法設為 barrier,getter 方法還是普通的。這樣 getter 方法可以並行,仍能取到安全資料,不會取到比如 set 到一半的資料。

相關文章