iOS面試題--原理篇

runtime怎麼添加屬性、方法等

  • ivar表示成員變量
  • class_addIvar
  • class_addMethod
  • class_addProperty
  • class_addProtocol
  • class_replaceProperty

是否可以把比較耗時的操作放在NSNotificationCenter中

  • 首先必須明確通知在哪個線程中發出,那麼處理接受到通知的方法也在這個線程中調用
  • 如果在異步線程發的通知,那麼可以執行比較耗時的操作;
  • 如果在主線程發的通知,那麼就不可以執行比較耗時的操作

runtime 如何實現 weak 屬性

首先要搞清楚weak屬性的特點

weak策略表明該屬性定義了一種“非擁有關係” (nonowning relationship)。
爲這種屬性設置新值時,設置方法既不保留新值,也不釋放舊值。此特質同assign類似;
然而在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)

作爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流羣:413038000,不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!

推薦閱讀

iOS開發——最新 BAT面試題合集(持續更新中)

那麼runtime如何實現weak變量的自動置nil?

runtime對註冊的類,會進行佈局,會將 weak 對象放入一個 hash 表中。
用 weak 指向的對象內存地址作爲 key,當此對象的引用計數爲0的時候會調用對象的 dealloc 方法,
假設 weak 指向的對象內存地址是a,那麼就會以a爲key,在這個 weak hash表中搜索,找到所有以a爲key的 weak 對象,從而設置爲 nil。

weak屬性需要在dealloc中置nil麼

  • 在ARC環境無論是強指針還是弱指針都無需在 dealloc 設置爲 nil , ARC 會自動幫我們處理
  • 即便是編譯器不幫我們做這些,weak也不需要在dealloc中置nil
  • 在屬性所指的對象遭到摧毀時,屬性值也會清空
// 模擬下weak的setter方法,大致如下
- (void)setObject:(NSObject *)object
{
    objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
    [object cyl_runAtDealloc:^{
        _object = nil;
    }];
}

一個Objective-C對象如何進行內存佈局?(考慮有父類的情況)

  • 所有父類的成員變量和自己的成員變量都會存放在該對象所對應的存儲空間中
  • 父類的方法和自己的方法都會緩存在類對象的方法緩存中,類方法是緩存在元類對象中
  • 每一個對象內部都有一個isa指針,指向他的類對象,類對象中存放着本對象的如下信息
    • 對象方法列表
    • 成員變量的列表
    • 屬性列表
  • 每個 Objective-C 對象都有相同的結構,如下圖所示
Objective-C 對象的結構圖
ISA指針
根類(NSObject)的實例變量
倒數第二層父類的實例變量
父類的實例變量
類的實例變量
  • 根類對象就是NSObject,它的super class指針指向nil
  • 類對象既然稱爲對象,那它也是一個實例。類對象中也有一個isa指針指向它的元類(meta class),即類對象是元類的實例。元類內部存放的是類方法列表,根元類的isa指針指向自己,superclass指針指向NSObject類

一個objc對象的isa的指針指向什麼?有什麼作用?

  • 每一個對象內部都有一個isa指針,這個指針是指向它的真實類型
  • 根據這個指針就能知道將來調用哪個類的方法

下面的代碼輸出什麼?

@implementation Son : Father
- (id)init
{
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end
  • 答案:都輸出 Son

  • 這個題目主要是考察關於objc中對 self 和 super 的理解:

    • self 是類的隱藏參數,指向當前調用方法的這個類的實例。而 super 本質是一個編譯器標示符,和 self 是指向的同一個消息接受者

    • 當使用 self 調用方法時,會從當前類的方法列表中開始找,如果沒有,就從父類中再找;

    • 而當使用 super時,則從父類的方法列表中開始找。然後調用父類的這個方法

    • 調用[self class] 時,會轉化成 objc_msgSend函數

      id objc_msgSend(id self, SEL op, ...)
      
    • 調用 [super class]時,會轉化成 objc_msgSendSuper函數

      id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
      
    • 第一個參數是 objc_super 這樣一個結構體,其定義如下

      struct objc_super {
      __unsafe_unretained id receiver;
      __unsafe_unretained Class super_class;
      };
      
    • 第一個成員是 receiver, 類似於上面的 objc_msgSend函數第一個參數self

    • 第二個成員是記錄當前類的父類是什麼,告訴程序從父類中開始找方法,找到方法後,最後內部是使用 objc_msgSend(objc_super->receiver, @selector(class))去調用, 此時已經和[self class]調用相同了,故上述輸出結果仍然返回 Son

    • objc Runtime開源代碼對- (Class)class方法的實現

    -(Class)class {
      return object_getClass(self);
    }

runtime如何通過selector找到對應的IMP地址?(分別考慮類方法和實例方法)

  • 每一個類對象中都一個對象方法列表(對象方法緩存)
  • 類方法列表是存放在類對象中isa指針指向的元類對象中(類方法緩存)
  • 方法列表中每個方法結構體中記錄着方法的名稱,方法實現,以及參數類型,其實selector本質就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應的方法實現.
  • 當我們發送一個消息給一個NSObject對象時,這條消息會在對象的類對象方法列表裏查找
  • 當我們發送一個消息給一個類時,這條消息會在類的Meta Class對象的方法列表裏查找

objc中的類方法和實例方法有什麼本質區別和聯繫

類方法

1 類方法是屬於類對象的
2 類方法只能通過類對象調用
3 類方法中的self是類對象
4 類方法可以調用其他的類方法
5 類方法中不能訪問成員變量
6 類方法中不能直接調用對象方法
7 類方法是存儲在元類對象的方法緩存中

實例方法

1 實例方法是屬於實例對象的
2 實例方法只能通過實例對象調用
3 實例方法中的self是實例對象
4 實例方法中可以訪問成員變量
5 實例方法中直接調用實例方法
6 實例方法中可以調用類方法(通過類名)
7 實例方法是存放在類對象的方法緩存中

使用runtime Associate方法關聯的對象,需要在主對象dealloc的時候釋放麼?

  • 無論在MRC下還是ARC下均不需要
  • 被關聯的對象在生命週期內要比對象本身釋放的晚很多,它們會在被 NSObject -dealloc 調用的 object_dispose()方法中釋放
  • 補充:對象的內存銷燬時間表,分四個步驟
1.調用 -release :引用計數變爲零
 * 對象正在被銷燬,生命週期即將結束.
 * 不能再有新的 __weak 弱引用,否則將指向 nil.
 * 調用 [self dealloc]
2\. 父類調用 -dealloc
 * 繼承關係中最直接繼承的父類再調用 -dealloc
 * 如果是 MRC 代碼 則會手動釋放實例變量們(iVars)
 * 繼承關係中每一層的父類 都再調用 -dealloc
3\. NSObject 調 -dealloc
 * 只做一件事:調用 Objective-C runtime 中的 object_dispose() 方法
4\. 調用 object_dispose()
 * 爲 C++ 的實例變量們(iVars)調用 destructors
 * 爲 ARC 狀態下的 實例變量們(iVars) 調用 -release
 * 解除所有使用 runtime Associate方法關聯的對象
 * 解除所有 __weak 引用
 * 調用 free()

_objc_msgForward函數是做什麼的?直接調用它將會發生什麼?

  • _objc_msgForward是IMP類型,用於消息轉發的:當向一個對象發送一條消息,但它並沒有實現的時候,_objc_msgForward會嘗試做消息轉發
  • 直接調用_objc_msgForward是非常危險的事,這是把雙刃刀,如果用不好會直接導致程序Crash,但是如果用得好,能做很多非常酷的事
  • JSPatch就是直接調用_objc_msgForward來實現其核心功能的

能否向編譯後得到的類中增加實例變量?能否向運行時創建的類中添加實例變量?爲什麼?

  • 不能向編譯後得到的類中增加實例變量;
  • 能向運行時創建的類中添加實例變量;
  • 分析如下:
    • 因爲編譯後的類已經註冊在runtime中,類結構體中的objc_ivar_list 實例變量的鏈表和instance_size實例變量的內存大小已經確定,同時runtime 會調用class_setIvarLayout 或 class_setWeakIvarLayout來處理strong weak引用,所以不能向存在的類中添加實例變量
    • 運行時創建的類是可以添加實例變量,調用 class_addIvar函數,但是得在調用objc_allocateClassPair之後,objc_registerClassPair之前,原因同上。

runloop和線程有什麼關係?

  • 每條線程都有唯一的一個RunLoop對象與之對應的
  • 主線程的RunLoop是自動創建並啓動
  • 子線程的RunLoop需要手動創建
  • 子線程的RunLoop創建步驟如下:
    • 在子線程中調用[NSRunLoop currentRunLoop]創建RunLoop對象(懶加載,只創建一次)

    • 獲得RunLoop對象後要調用run方法來啓動一個運行循環

      // 啓動RunLoop
      [[NSRunLoop currentRunLoop] run];
      
    • RunLoop的其他啓動方法

      // 第一個參數:指定運行模式
      // 第二個參數:指定RunLoop的過期時間,即:到了這個時間後RunLoop就失效了
      [[NSRunLoop currentRunLoop] runMode:kCFRunLoopDefaultMode beforeDate:[NSDate distantFuture]];
      

runloop的mode作用是什麼?

  • 用來控制一些特殊操作只能在指定模式下運行,一般可以通過指定操作的運行mode來控制執行時機,以提高用戶體驗
  • 系統默認註冊了5個Mode
    • kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行,對應OC中的:NSDefaultRunLoopMode
    • UITrackingRunLoopMode:界面跟蹤 Mode,用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他Mode影響
    • kCFRunLoopCommonModes:這是一個標記Mode,不是一種真正的Mode,事件可以運行在所有標有common modes標記的模式中,對應OC中的NSRunLoopCommonModes,帶有common modes標記的模式有:UITrackingRunLoopMode和kCFRunLoopDefaultMode
    • UIInitializationRunLoopMode:在啓動 App時進入的第一個 Mode,啓動完成後就不再使用
    • GSEventReceiveRunLoopMode:接受系統事件的內部Mode,通常用不到

以+scheduledTimerWithTimeInterval…的方式觸發的timer,在滑動頁面上的列表時,timer會暫定回調,爲什麼?如何解決?

  • 這裏強調一點:在主線程中以+scheduledTimerWithTimeInterval…的方式觸發的timer默認是運行在NSDefaultRunLoopMode模式下的,當滑動頁面上的列表時,進入了UITrackingRunLoopMode模式,這時候timer就會停止

  • 可以修改timer的運行模式爲NSRunLoopCommonModes,這樣定時器就可以一直運行了

  • 以下是我的筆記補充:

    • 在子線程中通過scheduledTimerWithTimeInterval:…方法來構建NSTimer

      • 方法內部已經創建NSTimer對象,並加入到RunLoop中,運行模式爲NSDefaultRunLoopMode
      • 由於Mode有timer對象,所以RunLoop就開始監聽定時器事件了,從而開始進入運行循環
      • 這個方法僅僅是創建RunLoop對象,並不會主動啓動RunLoop,需要再調用run方法來啓動
    • 如果在主線程中通過scheduledTimerWithTimeInterval:…方法來構建NSTimer,就不需要主動啓動RunLoop對象,因爲主線程的RunLoop對象在程序運行起來就已經被啓動了

      // userInfo參數:用來給NSTimer的userInfo屬性賦值,userInfo是隻讀的,只能在構建NSTimer對象時賦值
      [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(run:) userInfo:@"ya了個hoo" repeats:YES];
      
      // scheduledTimer...方法創建出來NSTimer雖然已經指定了默認模式,但是【允許你修改模式】
      [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
      
      // 【僅在子線程】需要手動啓動RunLoop對象,進入運行循環
      [[NSRunLoop currentRunLoop] run];
      

猜想runloop內部是如何實現的?

  • 從字面意思看:運行循環、跑圈;
  • 本質:內部就是do-while循環,在這個循環內部不斷地處理各種事件(任務),比如:Source、Timer、Observer;
  • 每條線程都有唯一一個RunLoop對象與之對應,主線程的RunLoop默認已經啓動,子線程的RunLoop需要手動啓動;
  • 每次RunLoop啓動時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode,如果需要切換Mode,只能退出Loop,再重新指定一個Mode進入,這樣做主要是爲了隔離不同Mode中的Source、Timer、Observer,讓其互不影響;

不手動指定autoreleasepool的前提下,一個autorealese對象在什麼時刻釋放?(比如在一個vc的viewDidLoad中創建)

  • 分兩種情況:手動干預釋放時機、系統自動去釋放
    • 手動干預釋放時機:指定autoreleasepool就是所謂的:當前作用域大括號結束時就立即釋放
    • 系統自動去釋放:不手動指定autoreleasepool,Autorelease對象會在當前的 runloop 迭代結束時釋放,下面詳細說明釋放時機
      • RunLoop中的三個狀態會處理自動釋放池,通過打印代碼發現有兩個Observer監聽到狀態值爲:1和160(32+128)
        • kCFRunLoopEntry(1) // 第一次進入會創建一個自動釋放池
        • kCFRunLoopBeforeWaiting(32) // 進入休眠狀態前先銷燬自動釋放池,再創建一個新的自動釋放池
        • kCFRunLoopExit(128) // 退出RunLoop時銷燬最後一次創建的自動釋放池
  • 如果在一個vc的viewDidLoad中創建一個Autorelease對象,那麼該對象會在 viewDidAppear 方法執行前就被銷燬了(是這樣的嗎???)

蘋果是如何實現autoreleasepool的?

  • autoreleasepool以一個隊列數組的形式實現,主要通過下列三個函數完成.
objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_aurorelease
  • 看函數名就可以知道,對autorelease分別執行push,和pop操作。銷燬對象時執行release操作

GCD的隊列(dispatch_queue_t)分哪兩種類型?背後的線程模型是什麼樣的?

  • 串行隊列
  • 並行隊列
  • dispatch_global_queue();是全局併發隊列
  • dispatch_main_queue();是一種特殊串行隊列
  • 背後的線程模型:自定義隊列 dispatch_queue_t queue; 可以自定義是並行:DISPATCH_QUEUE_CONCURRENT 或者 串行DISPATCH_QUEUE_SERIAL

蘋果爲什麼要廢棄dispatch_get_current_queue?

  • 容易誤用造成死鎖

如何用GCD同步若干個異步調用?(如根據若干個url異步加載多張圖片,然後在都下載完成後合成一張整圖)

  • 必須是併發隊列才起作用
  • 需求分析
    • 首先,分別異步執行2個耗時的操作
    • 其次,等2個異步操作都執行完畢後,再回到主線程執行一些操作
  • 使用隊列組實現上面的需求
// 創建隊列組
dispatch_group_t group =  dispatch_group_create();

// 獲取全局併發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// 往隊列組中添加耗時操作
dispatch_group_async(group, queue, ^{
    // 執行耗時的異步操作1
});

// 往隊列組中添加耗時操作
dispatch_group_async(group, queue, ^{
    // 執行耗時的異步操作2
});

// 當併發隊列組中的任務執行完畢後纔會執行這裏的代碼
dispatch_group_notify(group, queue, ^{
    // 如果這裏還有基於上面兩個任務的結果繼續執行一些代碼,建議還是放到子線程中,等代碼執行完畢後在回到主線程

    // 回到主線程
    dispatch_async(group, dispatch_get_main_queue(), ^{
        // 執行相關代碼...
    });
});

dispatch_barrier_async的作用是什麼?

  • 函數定義
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
  • 必須是併發隊列,要是串行隊列,這個函數就沒啥意義了
  • 注意:這個函數的第一個參數queue不能是全局的併發隊列
  • 作用:在它前面的任務執行結束後它才執行,在它後面的任務等它執行完成後纔會執
  • 示例代碼
-(void)barrier
{
    dispatch_queue_t queue = dispatch_queue_create("12342234", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });

    // 在它前面的任務執行結束後它才執行,在它後面的任務等它執行完成後纔會執行
    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });
}

以下代碼運行結果如何?

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
}
  • 答案:主線程死鎖

lldb(gdb)常用的調試命令?

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

BAD_ACCESS在什麼情況下出現?

  • 訪問一個殭屍對象,訪問殭屍對象的成員變量或者向其發消息
  • 死循環

如何調試BAD_ACCESS錯誤

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

image

  • 開啓殭屍對象調試功能

image

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

  • Objective-C是動態語言,每個方法在運行時會被動態轉爲消息發送,即:objc_msgSend(receiver, selector),整個過程介紹如下:
    • objc在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類
    • 然後在該類中的方法列表以及其父類方法列表中尋找方法運行
    • 如果,在最頂層的父類(一般也就NSObject)中依然找不到相應的方法時,程序在運行時會掛掉並拋出異常unrecognized selector sent to XXX
    • 但是在這之前,objc的運行時會給出三次拯救程序崩潰的機會,這三次拯救程序奔潰的說明見問題《什麼時候會報unrecognized selector的異常》中的說明
  • 補充說明:Runtime 鑄就了Objective-C 是動態語言的特性,使得C語言具備了面向對象的特性,在程序運行期創建,檢查,修改類、對象及其對應的方法,這些操作都可以使用runtime中的對應方法實現。

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

  • 簡單說就是進行方法交換
  • 在Objective-C中調用一個方法,其實是向一個對象發送消息,查找消息的唯一依據是selector的名字。利用Objective-C的動態特性,可以實現在運行時偷換selector對應的方法實現,達到給方法掛鉤的目的
  • 每個類都有一個方法列表,存放着方法的名字和方法實現的映射關係,selector的本質其實就是方法名,IMP有點類似函數指針,指向具體的Method實現,通過selector就可以找到對應的IMP

image

  • 交換方法的幾種實現方式
    • 利用 method_exchangeImplementations 交換兩個方法的實現
    • 利用 class_replaceMethod 替換方法的實現
    • 利用 method_setImplementation 來直接設置某個方法的IMP

image

objc中向一個nil對象發送消息將會發生什麼?

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

    • 如果一個方法返回值是一個對象,那麼發送給nil的消息將返回0(nil)
    • 如果方法返回值爲指針類型,其指針大小爲小於或者等於sizeof(void*)
    • float,double,long double 或者long long的整型標量,發送給nil的消息將返回0
    • 如果方法返回值爲結構體,發送給nil的消息將返回0。結構體中各個字段的值將都是0
    • 如果方法的返回值不是上述提到的幾種情況,那麼發送給nil的消息的返回值將是未定義的
  • 具體原因分析

    • objc是動態語言,每個方法在運行時會被動態轉爲消息發送,即:objc_msgSend(receiver, selector)
    • 爲了方便理解這個內容,還是貼一個objc的源代碼
struct objc_class
{
// isa指針指向Meta Class,因爲Objc的類的本身也是一個Object,
// 爲了處理這個關係,runtime就創造了Meta Class,
// 當給類發送[NSObject alloc]這樣消息時,實際上是把這個消息發給了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在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,然後在該類中的方法列表以及其父類方法列表中尋找方法運行,然後再發送消息的時候,objc_msgSend方法不會返回值,所謂的返回內容都是具體調用時執行的。
  • 如果向一個nil對象發送消息,首先在尋找對象的isa指針時就是0地址返回了,所以不會出現任何錯誤

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

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

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

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

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

    • objc在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類
    • 然後在該類中的方法列表以及其父類方法列表中尋找方法運行
    • 如果,在最頂層的父類中依然找不到相應的方法時,程序在運行時會掛掉並拋出異常unrecognized selector sent to XXX 。但是在這之前,objc的運行時會給出三次拯救程序崩潰的機會
  • 三次拯救程序崩潰的機會

    • Method resolution
      • objc運行時會調用+resolveInstanceMethod:或者 +resolveClassMethod:,讓你有機會提供一個函數實現。
      • 如果你添加了函數並返回 YES,那運行時系統就會重新啓動一次消息發送的過程
      • 如果 resolve 方法返回 NO ,運行時就會移到下一步,消息轉發
    • Fast forwarding
      • 如果目標對象實現了-forwardingTargetForSelector:,Runtime 這時就會調用這個方法,給你把這個消息轉發給其他對象的機會
      • 只要這個方法返回的不是nil和self,整個消息發送的過程就會被重啓,當然發送的對象會變成你返回的那個對象。
      • 否則,就會繼續Normal Fowarding。
      • 這裏叫Fast,只是爲了區別下一步的轉發機制。因爲這一步不會創建任何新的對象,但Normal forwarding轉發會創建一個NSInvocation對象,相對Normal forwarding轉發更快點,所以這裏叫Fast forwarding
    • Normal forwarding
      • 這一步是Runtime最後一次給你挽救的機會。
      • 首先它會發送-methodSignatureForSelector:消息獲得函數的參數和返回值類型。
      • 如果-methodSignatureForSelector:返回nil,Runtime則會發出-doesNotRecognizeSelector:消息,程序這時也就掛掉了。
      • 如果返回了一個函數簽名,Runtime就會創建一個NSInvocation對象併發送-forwardInvocation:消息給目標對象

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

  • 系統的某些block api中,UIView的block版本寫動畫時不需要考慮,但也有一些api 需要考慮
  • 以下這些使用方式不會引起循環引用的問題
[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; }];
  • 但如果方法中的一些參數是 成員變量,那麼可以造成循環引用,如 GCD 、NSNotificationCenter調用就要小心一點,比如 GCD 內部如果引用了 self,而且 GCD 的參數是 成員變量,則要考慮到循環引用,舉例如下:

    • GCD

      • 分析:self–>_operationsQueue–>block–>self形成閉環,就造成了循環引用
      __weak __typeof__(self) weakSelf = self;
      dispatch_group_async(_operationsGroup, _operationsQueue, ^
      {
      [weakSelf doSomething];
      [weakSelf doSomethingElse];
      } );
      
    • NSNotificationCenter

      • 分析:self–>_observer–>block–>self形成閉環,就造成了循環引用
      __weak __typeof__(self) weakSelf = self;
      _observer = [[NSNotificationCenter defaultCenter]
      addObserverForName:@"testKey"
      object:nil
      queue:nil
      usingBlock:^(NSNotification *note){
          [weakSelf dismissModalViewControllerAnimated:YES];
      }];
      

作爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流羣:413038000,不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!

推薦閱讀

iOS開發——最新 BAT面試題合集(持續更新中)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章