Effective Objective-C 2.0 編寫高質量iOS與OS X代碼的52個有效方法(Matt Galloway著)讀書筆記(一)

来源:https://www.cnblogs.com/gfxxbk/archive/2019/12/25/12095998.html
-Advertisement-
Play Games

第一章:熟悉 第1條:瞭解 語言的起源 第2條:在類的頭文件中儘量少引入其他頭文件 背景: 使用 可以引入其他文件的所有介面細節。 問題: 1. .h頭文件中,在編譯一個使用了某類的文件時,不需要知道這個類的全部細節,只需要知道有這個類就好。 2. A頭文件中引入B頭文件,C頭文件引入A頭文件,就會 ...


第一章:熟悉 Objective-C

第1條:瞭解 Objective-C 語言的起源

第2條:在類的頭文件中儘量少引入其他頭文件

背景:

使用 #import "ClassName.h" 可以引入其他文件的所有介面細節。

問題:
  1. .h頭文件中,在編譯一個使用了某類的文件時,不需要知道這個類的全部細節,只需要知道有這個類就好。
  2. A頭文件中引入B頭文件,C頭文件引入A頭文件,就會一起引入B頭文件的所有內容。此過程若持續下去,則要引入許多根本用不到的內容,這當然會增加編譯時間。
解決辦法:
  1. 使用 @class ClassName “向前聲明”(forward declaring),只聲明有這個類,沒有具體細節,可以解決上述問題。

    除非確實有必要,否則不要引入頭文件。一般來說,應在某個類的頭文件中使用向前聲明來提及別的類,併在實現文件中引入那些類的頭文件。這樣做可以儘量降低類之間的耦合(coupling)。

    繼承遵從協議 不能使用向前聲明。 有時無法使用向前聲明,比如要聲明某個類遵循一項協議。這種情況下,儘量把“該類遵循某協議”的這條聲明移至“分類”中。如果不行的話,就把協議單獨放在一個頭文件中,然後將其引入。

    向前聲明的作用:

    1. 防止引入根本用不到的內容,減少頭文件細節引用。
    2. 解決兩個類相互引用的問題。
  2. 將引入頭文件的時機儘量延後,只在確有需要時才引入,這樣可以減少類的使用者所需引入頭文件的數量。

第3條:多用字面量語法,少用與之等價的方法

使用字面量語法(literal syntax)可以縮減源代碼的長度,使其更為易讀。

第4條:多用類型常量,少用 #define 預處理指令

問題:

#define 定義的常量沒有類型信息,編譯器只會在編譯前據此執行查找與替換操作。即使有人重新定義了常量值,編譯器也不會產生警告信息,這將導致應用程式中的常量值不一致。

解決辦法:
  • 在實現文件中使用 static const 來定義“只在編譯單元內可見的變數”。由於此類常量不在全局符號表中,所以無須為其名稱加首碼。代碼實現如下:
// .h 文件
@interface 類名: 父類名
...
@end

// .m 文件
// 類內使用
static const 類型 常量名 = 常量值;

@implementation 類名
...
@end

  • 在頭文件中使用 extern 來聲明的全局變數,併在相關實現文件中定義其值。這種常量要出現在全局符號表中,所以其名稱應加以區分,通常用與之相關的類名做首碼。
// .h 文件

// 類外可用聲明
extern 類名 const 常量名;

@interface 類名: 父類名
...
@end

// .m 文件
// 類外可用聲明
類名 const 常量名 = 常量;

@implementation 類名
...
@end

常量名稱常用命名法是:

  1. 只在類內使用,在前面加字母 k
  2. 類外也可使用,以類名最為首碼。

第5條:用枚舉表示狀態、選項、狀態碼

  1. 使用枚舉,給這些值起個易懂的名字。
  2. 將枚舉值定義為2的冪,多枚舉選項可以同時使用,可以通過按位或操作進行組合。
  3. 定義枚舉時,指明其底層數據類型,便於處理。
  4. 在處理枚舉類型的 switch 語句中不要實現 default 分支,便於加入新枚舉後,編譯器報錯,知道需要修改的地方。

第二章:對象、消息、運行期

第6條:理解 “屬性” 這一概念

第7條:在對象內部儘量直接訪問實例變數

第8條:理解 “對象等同性” 這一概念

  1. == 操作符比較的是兩個指針本身,不是其所指的對象。
  2. NSObject 協議中聲明的 isEqual 方法判斷兩個對象的等同性。 isEqual 預設實現是:當且僅當其 “指針值” 完全相等時,這兩個對象才相等。
  3. 特定類具有等同性的判定方法。 NSString : isEqualToString: NSArray : isEqualToArray: NSDIctionary : isEqualToDictionary: 若比較的對象不是對應的類型,就會拋出異常,崩潰。
  4. 等同性判定的執行深度取決於受測對象。 對象個數相同的數組比較,對應位置上的對象均相等,數組就相等,這叫做“深度等同性判定”。 為了性能,建議儘可能的降低深度。
  5. 容器中可變類的等同性判定 將某對象放入容器後,又修改其內容,那麼後面的行為將很難預料,建議不要這麼做。

第9條:以 “類族模式” 隱藏實現細節

類族模式:使用繼承,實現多種職能的子類,父類通過設定不同的類型來創建某種子類,執行其相應的職能。 作用:將實現細節隱藏在一套簡單的公共介面後面。 需要註意的是,創建的實例的真實類型是什麼,需要我們知道

新增 CocoaNSArray 這樣的類族的子類,需要遵守以下幾條規則:

  1. 子類應該繼承自類族中的抽象基類。
  2. 子類應該定義自己的數據存儲方式。 NSArray 本身只是包在其他隱藏對象外面的殼,它僅僅定義了所有數組都需具備的一些介面。
  3. 子類應當覆寫超類文檔中指明需要覆寫的方法。 在每個抽象的基類中,都有一些子類必須覆寫的方法。

第10條:在既有類中使用關聯對象存放自定義數據

在對象中存放相關信息:

  1. 從對象所屬的類中繼承一個子類,然後修改這個子類對象。
  2. 通過“關聯對象”的特性,給某對象關聯許多其他對象,這些對象通過“鍵”來區分。
關聯對象方法:
  • 以給定的鍵和存儲策略為某對象設置關聯對象值

    objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
    

    參數說明:

    • object 關聯的源對象。
    • key 關聯的 key。通常使用靜態全局變數做鍵。
    • value 關聯 key 所對應的值。傳 nil 可以清除現有的關聯。
    • policy 關聯的存儲策略,也就是對應的記憶體管理語義,是一個枚舉值。
      objc_AssociationPolicy 枚舉值如下表: | 關聯類型 | 等效的 @property 屬性 | | --- | --- | OBJC_ASSOCIATION_ASSIGN | assign | OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic, retain | OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic, copy | OBJC_ASSOCIATION_RETAIN | retain | OBJC_ASSOCIATION_COPY | copy |
  • 根據給定的鍵從某對象中獲取對應的關聯對象值。

    objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
    
  • 移除指定對象的全部關聯對象。

    objc_removeAssociatedObjects(id _Nonnull object)
    

註:只有在其他方法都行不通時才考慮使用它。若是濫用,則很快就會令代碼失控,使其難於調試。

第11條:理解 objc_msgSend 的作用

OC方法調用

代碼:

id returnValue = [someObject messageName: paramter];

代碼說明:

  • someObject 接受者。
  • messageName 選擇子。

選擇子和參數合起來稱為“消息”

底層C語言代碼實現:

id returnValue = objc_msgSend(someObject, @selector(messageName:), paramter);
消息發送機制核心函數

原型代碼:

void objc_msgSend(id self, SEL cmd, ...)

這是個“參數個數可變函數”,能接受兩個或者兩個以上的參數。 參數說明:

  • self 接受者。
  • cmd 選擇子(方法的名字)。
  • 後續參數為消息中的那些參數,順序不變。

具體實現:

graph TD A[objc_msgSend] -->|獲取接受者和選擇子| B(接受者) B -->|查找與選擇子名稱相符的方法| C{方法列表} C -->|找到| D[方法實現代碼] C -->|未找到| E{沿著繼承體向上查找} E -->|找到| F[方法實現代碼] E -->|未找到| G[消息轉發]

註:OC 方法調用需要很多步驟,較為耗時。objc_msgSend 會將匹配結果緩存在“快速映射表”,每個類都有這樣一塊緩存。雖然還是不如“靜態綁定的函數調用操作”那麼迅速,但是也不會慢很多。

邊界情況:

  • objc_msgSend_stret 待發送的消息要返回結構體,就交由此函數處理。
  • objc_msgSend_fpret 待發送的消息要返回浮點數,就交由此函數處理。
  • objc_msgSendSuper 要給超類發消息,就交由此函數處理。如:[super message:parameter]
尾調用優化

Objective-C 對象的每個方法都可以看做簡單的 C 函數,其原型如下:

<return_type> Class_selector(id self, SEL _cmd, ...)

這個原型和 objc_msgSend 函數很像,是為了利用 “尾調用優化” 技術。令 “跳至方法實現” 這一操作跟簡單些。

使用範圍:某函數的最後一項操作僅僅是調用另一個函數而不會將其返回值另作他用。 步驟:編譯器會生成調轉至另一函數所需的指令碼,不會向調用堆棧中推人新的 “棧幀”。 不優化後果:

  1. 每次調用 Objective-C 方法之前,都需要為調用 objc_msgSend 函數準備 “棧幀”,可以在 “棧蹤跡” 中看到。
  2. 過早的發生 “棧溢出” 現象。

第12條:理解消息轉發機制

消息轉發: 第一階段:動態方法解析: 徵詢接收者(所屬的類),看其是否能動態添加方法,以處理當前這個 “未知的選擇子”。 第二階段: 1. 備援的接收者: 請接收者看看有沒有其他對象(備援的接收者)能處理這條消息。 2. 完整的消息轉發機制: 運行期系統會把與消息有關的全部細節都封裝到 NSInvocation 對象中,再給接收者最後一次機會,令其設法解決當前還未處理的這條消息。

動態方法解析

是否能新增一個實例方法來處理選擇子,調用方法如下:

// 實例方法
+ (BOOL)resolveInstanceMethod:(SEL)selector
// 類方法
+ (BOOL)resolveClassMethod:(SEL)selector

使用前提:相關方法的實現代碼已經寫好,只等運行的時候動態插入到類裡面。

動態添加方法函數如下:

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

參數說明:

  • cls 添加方法的類
  • name 被添加方法的名字
  • imp 函數指針,指向待添加的方法(C語言實現)。
  • type 待添加方法的 “類型編碼”
備援接收者

是否有其他對象處理這條消息,調用方法如下:

- (id)forwardingTargetForSelector:(SEL)selector

若找到備援對象,該方法返回備援對象,反之,返回 nil

註意:我們無法操作經由這一步所轉發的消息。

完整的消息轉發機制

創建 NSInvocation 對象,此對象包含 選擇子目標參數

消息派發調用方法如下:

- (void)forwardInvocation:(NSInvocation *)invocation

若發現某調用操作不應由本類處理,則向上尋找,直至 NSObject。如果最後調用了 NSObject 的方法,那麼該方法還會繼續調用 doesNotRecognizeSelector: 以拋出異常,表明選擇子最終未能得到處理。

總結:

消息轉發全流程如下圖:

接收者在每一步中均有機會處理消息。步驟越往後,處理消息的代價就越大。

消息轉發代碼:https://github.com/AlonerOwl/Runtime/tree/master/Runtime/MessageSend

第13條:用 ”方法調配(method swizzling)技術“ 調試 ”黑盒方法“

函數指針(IMP):id (*IMP)(id, SEL, ...)

方法表:函數指針所組成的一個集合。

操作類的方法表:

  • 新增選擇子
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
    
    第12條已經說過了。
  • 改變某選擇子所對應的方法實現
  • 交換兩個選擇子所映射到的指針,也就是交換兩個方法實現。 方法交換函數:
    func method_exchangeImplementations(_ m1: Method, _ m2: Method)
    
    參數:兩個待交換的方法實現 獲取方法實現函數:
    Method class_getInstanceMethod(Class cls, SEL name)
    

這個方法可以為那些 “完全不知道具體實現” 的黑盒方法增加日誌記錄功能,這非常有助於程式調試。 若是濫用,反而會令代碼變的不易讀懂且難於維護。

method swizzling代碼:https://github.com/AlonerOwl/Runtime/tree/master/Runtime/MethodSwizzling


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、問題描述 打開Word提示 很抱歉此功能看似已中斷,並需要修複。請使用Windows控制面板中的“程式和功能”選項修複Microsoft Office。 二、解決方法 運行 regedit 進入註冊表編輯器 定位到HKEY_CURRENT_USER \ SOFTWARE \ Microsoft ...
  • 本文章主要是用來索引,分散式系統中相關的鏈接: http://sortbenchmark.org/ 保持更新,資源來源自網路;更多內容請關註 cnblogs.com/xuyaowen; ...
  • 保護模式下三個重要的系統表——GDT、LDT和IDT 這裡主要是解釋中斷描述符表 中斷描述符表IDT將每個異常或中斷向量分別與它們的處理過程聯繫起來。與GDT和LDT表類似,IDT也是由8位元組長描述符組成的一個數組。與GDT不同的是,表中第一項可以包含描述符。為了構成IDT表中的一個索引值,處理器把 ...
  • -- BI EMAIL declare @CC varchar(10),@MAIL varchar(500), @str varchar(800),@year varchar(4),@month varchar(2);declare @file_name varchar(50);declare @m ...
  • 大數據開發獨攬大權 大數據技術很早就在BAT這些公司生根發芽,但直到14、15年大數據技術才廣泛應用在各大互聯網公司,大數據技術由此深入各行各業。 此時大數據開發人才非常緊缺,很多公司大數據從立項,到大數據平臺構建,到項目整個流程開發,到後期大數據項目的運維,都是由大數據開發人員一手完成(此時少有專 ...
  • MySQL簡介:MySQL是開源免費的資料庫,小型的資料庫.已經被Oracle收購了.MySQL5.5版本之後都是由Oracle發佈的版本,MySQL之前被SUN公司收購,而SUN公司現在又被Oracle公司收購,所以現在MySQL也是屬於Oracle旗下的一款產品。 1.什麼是MySQL? 維基百 ...
  • JDBC核心技術 講師:宋紅康 微博:尚矽谷 宋紅康 第1章:JDBC概述 1.1 數據的持久化 持久化(persistence): 把數據保存到可掉電式存儲設備中以供之後使用 。大多數情況下,特別是企業級應用, 數據持久化意味著將記憶體中的數據保存到硬碟 上加以”固化” ,而持久化的實現過程大多通過 ...
  • 一、GraphQL簡介 1、什麼是GraphQL? GraphQL官網:https://graphql.org/,這個是英文的,https://graphql.js.cool/這個是中文的。 GraphQL是一種用於API的查詢語言。GraphQL 既是一種用於 API 的查詢語言也是一個滿足你數據 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...