《iOS開發進階》觀後-中篇(OC底層原理篇)

簡介

  • 本篇主要內容是OC底層原理篇總結,分別從OC對象模型、Tagged Pointer、block三個方面,在此過程當中還會涉及一些相關的系統底層MCU指針PC指針概念類比。

前言

  • 因爲篇幅問題,會分三篇描述對應不一樣內容。輔助工具,底層原理,開發中要注意問題三個方面,談談對應的總結。本人以爲書只是一個索引,特別對於技術類書籍,基本都是經過書籍引入一些觀點,而後在經過其它第三方途徑進行擴展。因此本文描述內容不必定就是書本內容,會與自身實踐經驗,還有部份內容精簡和拓展。有些基礎概念第三方描述已足夠詳細,實例也足夠詳細,本文僅僅提出一些我的總結理解,不在重複描述具體功能原理。html

  • 文字不如圖片直觀,因此先上一張本系列描述的觀點的思惟導圖,梳理脈絡。紅色部分爲本文內容梳理。ios

  • 《思惟導圖連接(點我打開)最新版本》objective-c

輸入圖片說明

OC對象模型

1 isa指針 (類比8位MCU pc指針)

  • 在Objective-C中,任何類的定義都是對象。類和類的實例(對象)沒有任何本質上的區別。編程

  • isa:是一個Class 類型的指針. 每一個實例對象有個isa的指針,他指向對象的類,而Class裏也有個isa的指針, 指向meteClass(元類)。元類保存了類方法的列表。當類方法被調用時,先會從自己查找類方法的實現,若是沒有,元類會向他父類查找該方法。同時注意的是:元類(meteClass)也是類,它也是對象。元類也有isa指針,它的isa指針最終指向的是一個根元類(root meteClass).根元類的isa指針指向自己,這樣造成了一個封閉的內循環。異步

  • 類比8位MCU的pc指針,我我的理解isa指針,在硬件層面上就是指向代碼的運行地址能夠試RAM地址或ROM地址,等同MCU彙編的PC指針,調用函數時,須要先把當前函數地址壓入堆棧,燃後放置新地址到PC指針,而後退去取回原堆棧內的地址。可是OC isa 在這個基礎上增長了繼承的概念。async

  • 如下是實例引用流程:編程語言

輸入圖片說明

  • 每個對象本質上都是一個類的實例。其中類定義了成員變量和成員方法的列表。對象經過對象的isa指針指向類。函數

  • 每個類本質上都是一個對象,類實際上是元類(meteClass)的實例。元類定義了類方法的列表。類經過類的isa指針指向元類。工具

  • 全部的元類最終繼承一個根元類,根元類isa指針指向自己,造成一個封閉的內循環。atom

2 runtime 運行時,動態方法交換

  • 指一個程序在運行(或者在被執行)的狀態。也就是說,當你打開一個程序使它在電腦上運行的時候,那個程序就是處於運行時刻。在一些編程語言中,把某些能夠重用的程序或者實例打包或者重建成爲「運行庫"。這些實例能夠在它們運行的時候被鏈接或者被任何程序調用。

  • objective-c中runtime:是一套比較底層的純C語言API, 屬於1個C語言庫, 包含了不少底層的C語言API。 在咱們平時編寫的OC代碼中, 程序運行過程時, 其實最終都是轉成了runtime的C語言代碼。

  • RunTime基本經常使用法 (《iOS開發進階》頁碼:220 - 225 ps:這裏說得不夠細)

//1 動態增長變量

@property (nonatomic, assign) BOOL isNotIgnore;
//runtime 動態綁定 屬性
- (BOOL)isNotIgnore{
    //_cmd == @select(isIgnore); 和set方法裏一致
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}
- (void)setIsNotIgnore:(BOOL)isNotIgnore{
    // 注意BOOL類型 須要用OBJC_ASSOCIATION_RETAIN_NONATOMIC 不要用錯,不然set方法會賦值出錯
    objc_setAssociatedObject(self, @selector(isNotIgnore), @(isNotIgnore), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}


//2 對象方法的交換

/**
 *  對象方法的交換
 *
 *  @param anClass    哪一個類
 *  @param method1Sel 方法1
 *  @param method2Sel 方法2
 */
+ (void)exchangeInstanceMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel {
    Method method1 = class_getInstanceMethod(anClass, method1Sel);
    Method method2 = class_getInstanceMethod(anClass, method2Sel);
    method_exchangeImplementations(method1, method2);
}


//3 動態類方法的交換

/**
 *  類方法的交換
 *
 *  @param anClass    哪一個類
 *  @param method1Sel 方法1
 *  @param method2Sel 方法2
 */
+ (void)exchangeClassMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel {
    Method method1 = class_getClassMethod(anClass, method1Sel);
    Method method2 = class_getClassMethod(anClass, method2Sel);
    method_exchangeImplementations(method1, method2);
}


//4 動態類方法的交換

/**
 *  對象方法重置
 *
 *  @param anClass    哪一個類
 *  @param method1Sel 方法1
 */
+ (void)setClassMethod:(Class)anClass oldMethodSel:(SEL) oldMethodSel newMethodSel:(SEL)newMethodSel  {
    Method oldMethod = class_getInstanceMethod(anClass, oldMethodSel);
    Method newMethod = class_getInstanceMethod(anClass, newMethodSel);
    method_setImplementation(oldMethod, method_getImplementation(newMethod));
}

Tagged Pointer

1 32/64位設備區別

  • 這個概念其實對應如今已經有點老了,32爲設備已在蘋果放棄支持的日程表上了。可是對於一些基礎仍是有必要了解一下。簡單就是在64位設備上,同一地址裂開2部分使用,那其中32位最基礎值類保存,另外位數存指針,索引表之類的數據,複用一個地址上的存儲空間。這點和mcu內存一個byte,低4位和高4分開存在2個0-16的數值,是相似的。

  • 假設咱們要存儲一個NSNumber對象,其值是一個整數。正常狀況下,若是這個整數只是一個NSInteger的普通變量,那麼它所佔用的內存是與CPU的位數有關,在32位CPU下佔4個字節,在64位CPU下是佔8個字節的。而指針類型的大小一般也是與CPU位數相關,一個指針所佔用的內存在32位CPU下爲4個字節,在64位CPU下也是8個字節。

  • 普通的iOS程序,若是沒有Tagged Pointer對象,從32位機器遷移到64位機器中後,雖然邏輯沒有任何變化,但這種NSNumber、NSDate一類的對象所佔用的內存會翻倍

  • 爲了改進上面提到的內存佔用和效率問題,蘋果提出了Tagged Pointer對象。因爲NSNumber、NSDate一類的變量自己的值須要佔用的內存大小經常不須要8個字節,拿整數來講,4個字節所能表示的有符號整數就能夠達到20多億(注:2^31=2147483648,另外1位做爲符號位),對於絕大多數狀況都是能夠處理的。

Block

1 系統底層實現

  • 《Block技巧與底層解析》這個說明說得比書本更詳細。我簡單總結一下,底層就是結構體存着函數地址起始,及其長度。在須要時調用該變量。可是中間過程會有變量的複製問題,和引用問題處理。

2 block中變量複製問題

  • __block修飾變量,在block中,使用能直接改變變量的值。

  • 沒有__block修飾變量,在block使用,等於原變量copy了一個新變量,改變其值不影響原值。

3 循環引用 (ARC下)

  • 這個問題出如今block替換代理時,引用變量致使控制器不釋放比較常見。由於copy致使原變量引用加一。

  • 因此block引用self,必須加上弱引聲明。

__weak typeof(BaseViewController) *weakSelf = self;

4 《block基本用法》

內存管理 (參考《OC內存管理》

1 引用計數

  • 本質緣由是由於對象和其餘數據類型在系統中的存儲空間不同,其它局部變量主要存放於棧中,而對象存儲於堆中,當代碼塊結束時這個代碼塊中涉及的全部局部變量會被回收,指向對象的指針也被回收,此時對象已經沒有指針指向,但依然存在於內存中,形成內存泄露。

  • 每一個OC對象都有本身的引用計數器,是一個整數表示對象被引用的次數,即如今有多少東西在使用這個對象。對象剛被建立時,默認計數器值爲1,當計數器的值變爲0時,則對象銷燬。 在每一個OC對象內部,都專門有4個字節的存儲空間來存儲引用計數器。判斷對象要不要回收的惟一依據就是計數器是否爲0,若不爲0則存在。

  • block中,因爲對象會copy,全部會作成原變量引用加入,全部會致使不釋放。

2 ARC/MRC

  • MRC就是手工管理對象引用問題,ARC就是編譯器處理引用問題,自動插入釋放引用代碼。實際銜接用MRC的可能就只有第三方庫,真實開發暫時都是ARC。

3 新舊系統消息處理區別

  • 有一個坑IOS8如下系統對已被釋放的對象發消息會致使APP崩潰,而且dealloc時移除通知.否則有可能奇奇怪怪的問題出現。

GCD (《iOS中GCD的使用小結》 《GCD死鎖及取消》 《GCD 與 NSOperationQueue 區別》

1 異步處理同步化

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   dispatch_async(globalQueue, ^{
       //子線程異步執行下載任務,防止主線程卡頓
       NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
       NSError *error;
       NSString *htmlData = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
       if (htmlData != nil) {
           dispatch_queue_t mainQueue = dispatch_get_main_queue();
            //異步返回主線程,根據獲取的數據,更新UI
           dispatch_async(mainQueue, ^{
               NSLog(@"根據更新UI界面");
           });
       } else {
           NSLog(@"error when download:%@",error);
       }
  });

2 相互依賴

- (void)groupSync
{
    dispatch_queue_t disqueue =  dispatch_queue_create("com.shidaiyinuo.NetWorkStudy", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t disgroup = dispatch_group_create();
    dispatch_group_async(disgroup, disqueue, ^{
        
        NSLog(@"任務一完成");
    });
    
    dispatch_group_async(disgroup, disqueue, ^{
        
        sleep(8);
        NSLog(@"任務二完成");
    });
    
    dispatch_group_notify(disgroup, disqueue, ^{
        
        NSLog(@"dispatch_group_notify 執行");
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        dispatch_group_wait(disgroup, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
        NSLog(@"dispatch_group_wait 結束");
    });
}

3 強制打斷

  • 業務場景 滾動scrolview,監聽最後一次滾動效果。或請求超時強制放棄超時結果。或屢次調用統一函數只取最後一次結果。
@property (nonatomic, strong) dispatch_block_t dispatch_stopLoading_block;

@weakify(self)
if (self.dispatch_stopLoading_block) {
    dispatch_block_cancel(self.dispatch_stopLoading_block);
    self.dispatch_stopLoading_block = NULL;
}
self.dispatch_stopLoading_block = dispatch_block_create(0, ^{
    @strongify(self)
    NSLog(@"任務一完成");
});

//任務延遲啓動
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), self.dispatch_stopLoading_block);

4 信號量 (《淺談GCD中的信號量》

//因爲設定的信號值爲2,先執行兩個線程,等執行完一個,纔會繼續執行下一個,保證同一時間執行的線程數不超過2。

-(void)dispatchSignal{
    //crate的value表示,最多幾個資源可訪問
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);   
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     
    //任務1
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 1");
        sleep(1);
        NSLog(@"complete task 1");
        dispatch_semaphore_signal(semaphore);       
    });<br>
    //任務2
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        dispatch_semaphore_signal(semaphore);       
    });<br>
    //任務3
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 3");
        sleep(1);
        NSLog(@"complete task 3");
        dispatch_semaphore_signal(semaphore);       
    });   
}

原文:http://raychow.linkfun.top/2018/01/07/archives/1_ios/2017-section-2/index/

相關文章
相關標籤/搜索