4. iOS面試題原理篇2

lldbgdb)常用的調試命令?

  • po:打印對象,會調用對象description方法。是print-object的簡寫
  • expr:可以在調試時動態(tài)執(zhí)行指定表達式,并將結果打印出來,很有用的命令
  • print:也是打印命令,需要指定類型
  • bt:打印調用堆棧,是thread backtrace的簡寫,加all可打印所有thread的堆棧
  • br l:是breakpoint list的簡寫

BAD_ACCESS在什么情況下出現?

  • 訪問一個僵尸對象,訪問僵尸對象的成員變量或者向其發(fā)消息
  • 死循環(huán)

如何調試BAD_ACCESS錯誤

  • 設置全局斷點快速定位問題代碼所在行

? 開啟僵尸對象調試功能


Edit Scheme.png
Arguments.png

簡述下Objective-C中調用方法的過程(runtime

  • Objective-C是動態(tài)語言,每個方法在運行時會被動態(tài)轉為消息發(fā)送,即:objc_msgSend(receiver, selector),整個過程介紹如下:

  • objc在向一個對象發(fā)送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類

  • 然后在該類中的方法列表以及其父類方法列表中尋找方法運行

  • 如果,在最頂層的父類(一般也就NSObject)中依然找不到相應的方法時,程序在運行時會掛掉并拋出異常unrecognized selector sent to XXX

  • 但是在這之前,objc的運行時會給出三次拯救程序崩潰的機會,這三次拯救程序奔潰的說明見問題《什么時候會報unrecognized selector的異常》中的說明

  • 補充說明:Runtime 鑄就了Objective-C 是動態(tài)語言的特性,使得C語言具備了面向對象的特性,在程序運行期創(chuàng)建,檢查,修改類、對象及其對應的方法,這些操作都可以使用runtime中的對應方法實現。

什么是method swizzling(俗稱黑魔法)

  • 簡單說就是進行方法交換
  • 在Objective-C中調用一個方法,其實是向一個對象發(fā)送消息,查找消息的唯一依據是selector的名字。利用Objective-C的動態(tài)特性,可以實現在運行時偷換selector對應的方法實現,達到給方法掛鉤的目的

? 每個類都有一個方法列表,存放著方法的名字和方法實現的映射關系,selector的本質其實就是方法名,IMP有點類似函數指針,指向具體的Method實現,通過selector就可以找到對應的IMP


selectorA.jpeg

* 交換方法的幾種實現方式

  • 利用 method_exchangeImplementations 交換兩個方法的實現
  • 利用 class_replaceMethod 替換方法的實現

? 利用 method_setImplementation 來直接設置某個方法的IMP

selectorA.jpeg

objc中向一個nil對象發(fā)送消息將會發(fā)生什么?

  • 在Objective-C中向nil發(fā)送消息是完全有效的——只是在運行時不會有任何作用

  • 如果一個方法返回值是一個對象,那么發(fā)送給nil的消息將返回0(nil)

  • 如果方法返回值為指針類型,其指針大小為小于或者等于sizeof(void*)

  • float,double,long double 或者long long的整型標量,發(fā)送給nil的消息將返回0

  • 如果方法返回值為結構體,發(fā)送給nil的消息將返回0。結構體中各個字段的值將都是0

  • 如果方法的返回值不是上述提到的幾種情況,那么發(fā)送給nil的消息的返回值將是未定義的

  • 具體原因分析

  • objc是動態(tài)語言,每個方法在運行時會被動態(tài)轉為消息發(fā)送,即:objc_msgSend(receiver, selector)

  • 為了方便理解這個內容,還是貼一個objc的源代碼

    struct objc_class
*   {
*   // isa指針指向Meta Class,因為Objc的類的本身也是一個Object,
*   // 為了處理這個關系,runtime就創(chuàng)造了Meta Class,
*   // 當給類發(fā)送[NSObject alloc]這樣消息時,實際上是把這個消息發(fā)給了Class Object
*   Class isa OBJC_ISA_AVAILABILITY;
*   #if !__OBJC2__
*   Class super_class OBJC2_UNAVAILABLE; // 父類
*   const char *name OBJC2_UNAVAILABLE; // 類名
*   long version OBJC2_UNAVAILABLE; // 類的版本信息,默認為0
*   long info OBJC2_UNAVAILABLE; // 類信息,供運行期使用的一些位標識
*   long instance_size OBJC2_UNAVAILABLE; // 該類的實例變量大小
*   struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
*   struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
*   // 方法緩存,對象接到一個消息會根據isa指針查找消息對象,
*   // 這時會在method Lists中遍歷,
*   // 如果cache了,常用的方法調用時就能夠提高調用的效率。
*   // 這個方法緩存只存在一份,不是每個類的實例對象都有一個方法緩存
*   // 子類會在自己的方法緩存中緩存父類的方法,父類在自己的方法緩存中也會緩存自己的方法,而不是說子類就不緩存父類方法了
*   struct objc_cache *cache OBJC2_UNAVAILABLE;
*   struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協議鏈表
*   #endif
*   } OBJC2_UNAVAILABLE;
  • objc在向一個對象發(fā)送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,然后在該類中的方法列表以及其父類方法列表中尋找方法運行,然后再發(fā)送消息的時候,objc_msgSend方法不會返回值,所謂的返回內容都是具體調用時執(zhí)行的。
  • 如果向一個nil對象發(fā)送消息,首先在尋找對象的isa指針時就是0地址返回了,所以不會出現任何錯誤

objc中向一個對象發(fā)送消息[obj foo]和objc_msgSend()函數之間有什么關系?

  • [obj foo];在objc動態(tài)編譯時,會被轉意為:objc_msgSend(obj, @selector(foo));

什么時候會報unrecognized selector的異常?

  • 當調用該對象上某個方法,而該對象上沒有實現這個方法的時候, 可以通過“消息轉發(fā)”進行解決,如果還是不行就會報unrecognized selector異常

  • objc是動態(tài)語言,每個方法在運行時會被動態(tài)轉為消息發(fā)送,即:objc_msgSend(receiver, selector),整個過程介紹如下:

  • objc在向一個對象發(fā)送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類

  • 然后在該類中的方法列表以及其父類方法列表中尋找方法運行

  • 如果,在最頂層的父類中依然找不到相應的方法時,程序在運行時會掛掉并拋出異常unrecognized selector sent to XXX 。但是在這之前,objc的運行時會給出三次拯救程序崩潰的機會

  • 三次拯救程序崩潰的機會

  • Method resolution

  • objc運行時會調用+resolveInstanceMethod:或者 +resolveClassMethod:,讓你有機會提供一個函數實現。

  • 如果你添加了函數并返回 YES,那運行時系統就會重新啟動一次消息發(fā)送的過程

  • 如果 resolve 方法返回 NO ,運行時就會移到下一步,消息轉發(fā)

  • Fast forwarding

  • 如果目標對象實現了-forwardingTargetForSelector:,Runtime 這時就會調用這個方法,給你把這個消息轉發(fā)給其他對象的機會

  • 只要這個方法返回的不是nil和self,整個消息發(fā)送的過程就會被重啟,當然發(fā)送的對象會變成你返回的那個對象。

  • 否則,就會繼續(xù)Normal Fowarding。

  • 這里叫Fast,只是為了區(qū)別下一步的轉發(fā)機制。因為這一步不會創(chuàng)建任何新的對象,但Normal forwarding轉發(fā)會創(chuàng)建一個NSInvocation對象,相對Normal forwarding轉發(fā)更快點,所以這里叫Fast forwarding

  • Normal forwarding

  • 這一步是Runtime最后一次給你挽救的機會。

  • 首先它會發(fā)送-methodSignatureForSelector:消息獲得函數的參數和返回值類型。

  • 如果-methodSignatureForSelector:返回nil,Runtime則會發(fā)出-doesNotRecognizeSelector:消息,程序這時也就掛掉了。

  • 如果返回了一個函數簽名,Runtime就會創(chuàng)建一個NSInvocation對象并發(fā)送-forwardInvocation:消息給目標對象

HTTP協議中POST方法和GET方法有那些區(qū)別?

  • GET用于向服務器請求數據,POST用于提交數據
  • GET請求,請求參數拼接形式暴露在地址欄,而POST請求參數則放在請求體里面,因此GET請求不適合用于驗證密碼等操作
  • GET請求的URL有長度限制,POST請求不會有長度限制

使用block時什么情況會發(fā)生引用循環(huán),如何解決?

block內如何修改block外部變量?

使用系統的某些block api(如UIViewblock版本寫動畫時),是否也考慮循環(huán)引用問題?

  • 系統的某些block api中,UIView的block版本寫動畫時不需要考慮,但也有一些api 需要考慮
  • 以下這些使用方式不會引起循環(huán)引用的問題
[UIView animateWithDuration:duration animations:^

{ [self.superview layoutIfNeeded]; }];

[[NSOperationQueue mainQueue] addOperationWithBlock:^

{ self.someProperty = xyz; }];

[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"

 object:nil

 queue:[NSOperationQueue mainQueue]

 usingBlock:^(NSNotification * notification)

 { self.someProperty = xyz; }];
  • 但如果方法中的一些參數是 成員變量,那么可以造成循環(huán)引用,如 GCD 、NSNotificationCenter調用就要小心一點,比如 GCD 內部如果引用了 self,而且 GCD 的參數是 成員變量,則要考慮到循環(huán)引用,舉例如下:

  • GCD

  • 分析:self-->_operationsQueue-->block-->self形成閉環(huán),就造成了循環(huán)引用 __weak typeof(self) weakSelf = self;

*   dispatch_group_async(_operationsGroup, _operationsQueue, ^
*   {
*   [weakSelf doSomething];
*   [weakSelf doSomethingElse];
*   } );
  • NSNotificationCenter

  • 分析:self-->_observer-->block-->self形成閉環(huán),就造成了循環(huán)引用

*   __weak __typeof__(self) weakSelf = self;
*   _observer = [[NSNotificationCenter defaultCenter]
*   addObserverForName:@"testKey"
*   object:nil
*   queue:nil
*   usingBlock:^(NSNotification *note){
*   [weakSelf dismissModalViewControllerAnimated:YES];
*   }];

OC中常見的循環(huán)引用總結

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容