【iOS】方法交換

方法交換

  • method-swizzling是什么
  • 相關API
  • 方法交換的風險
    • method-swizzling使用過程中的一次性問題
    • 在當前類中進行方法交換
    • 類方法的方法交換
  • 方法交換的應用

method-swizzling是什么

method-swizzling的含義是方法交換,他的主要作用是在運行的時候將一個方法的實現替換為另一個方法的實現,這就是我們說的iOS黑魔法

OC中,利用method-swizzling實現AOP,AOP(面向切面編程)是一種編程思想,區別于OOP。

其中,AOP是面向切面進行提取封裝,提取各個模塊中的公共部分,提高模塊的復用率,降低業務之間的耦合性;而OOP更加傾向于對業務模塊的封裝,劃分出更加清晰的邏輯單元。

在之前學習探索消息流程的時候,我們了解到可以通過SEL方法查找器來查找method方法,而后得到對應的IMP。而方法交換其實就是將SEL與IMP原本的對應斷開,并將SEL和新的IMP生成對應關系

這里筆者附上一張看到的圖片來解釋其關系:

在這里插入圖片描述

相關API

//通過sel獲取方法Method
class_getInstanceMethod://獲取實例方法
class_getClassMethod://獲取類方法method_getImplementation://獲取一個方法的實現
method_setImplementation://設置一個方法的實現
method_getTypeEncoding://獲取方法實現的編碼類型
class_addMethod://添加方法實現
class_replaceMethod://用一個方法的實現,替換另一個方法的實現,即aIMP 指向 bIMP,但是bIMP不一定指向aIMP
method_exchangeImplementations://交換兩個方法的實現,即 aIMP -> bIMP, bIMP -> aIMP

方法交換的風險

下面我們來看看方法交換中會遇到的問題

method-swizzling使用過程中的一次性問題

一次性:method-swizzling寫在load方法中,但是load會主動的調用多次,這會導致方法的重復交換,令方法SEL指向又恢復成原來的IMP的問題出現

故而,這里我們可以通過單例模式的原則,令方法交換僅僅執行一次,這里我們需要使用GCD來實現單例,下面舉一個例子說明該問題:

+ (void)load{static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{[LGRuntimeTool lg_bestMethodSwizzlingWithClass:self oriSEL:@selector(helloword) swizzledSEL:@selector(lg_studentInstanceMethod)];});
}

在當前類中進行方法交換

當我們進行方法交換的時候,必須交換的是當前類中的方法,若是方法在父類中,直接交換會導致父類的方法被錯誤的修改,這樣會影響其所有的子類。

  • 方法交換的隔離性:必須確保交換的目標方法是當前類已實現或動態添加的方法,避免污染父類方法列表。
  • 動態注冊的重要性:通過 class_addMethod 隔離父類實現,保證交換僅作用于當前類及其子類。
  • 風險規避:在子類分類中操作父類方法需謹慎,推薦使用 method_setImplementationclass_replaceMethod 控制影響范圍。

這里我們先來看看若是直接和其父類進行方法交換會引起的后果:

在這里插入圖片描述

在這里插入圖片描述

上面的是父類中的方法,下面在子類的分類中方法交換,我們來看看會發生什么

在這里插入圖片描述

根據斷點顯示,我們可以發現在子類Student中方法交換之后子類調用最后結果是正確的,但是到了最后一個斷點的時候,可以發現程序報錯了。我們來看看這是為什么?

這里我們先看子類Student調用personInstanceMethod方法,由于其imp交換成了lg_studentInstanceMethod,而在子類的分類中有該方法,所以不會報錯。但是當父類Person中的imp也被交換成了lg_studentInstanceMethod,但我們并沒有在父類中實現該方法,即相關的imp無法找到,就會導致程序崩潰掉。

那么我們怎么做就可以讓程序不崩潰呢,這里我們可以通過class_addMethod嘗試添加要交換的方法,下面先給出示例:

在這里插入圖片描述

一般交換方法: 交換自己有的方法 – 走下面 因為自己有意味添加方法失敗

交換自己沒有實現的方法:

  • 首先第一步:會先嘗試給自己添加要交換的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)

  • 然后再將父類的IMP給swizzle personInstanceMethod(imp) -> swizzledSEL

當我們兩個方法都沒有實現的情況下,就會進入無限遞歸,導致最后棧溢出:

原因是 棧溢出遞歸死循環了,那么為什么會發生遞歸呢?----主要是因為 personInstanceMethod沒有實現,然后在方法交換時,始終都找不到oriMethod,然后交換了寂寞,即交換失敗,當我們調用personInstanceMethod(oriMethod)時,也就是oriMethod會進入LG中lg_studentInstanceMethod方法,然后這個方法中又調用了lg_studentInstanceMethod,此時的lg_studentInstanceMethod并沒有指向oriMethod ,然后導致了自己調自己,即遞歸死循環

類方法的方法交換

其實類方法的方法交換和實例方法的方法交換差不多,這里我給出一個例子,這里和實例方法的區別其實只是類方法存在元類中

+ (void)lg_bestClassMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{if (!cls) NSLog(@"傳入的交換類不能為空");Method oriMethod = class_getClassMethod([cls class], oriSEL);Method swiMethod = class_getClassMethod([cls class], swizzledSEL);if (!oriMethod) { // 避免動作沒有意義// 在oriMethod為nil時,替換后將swizzledSEL復制一個不做任何事的空實現,代碼如下:class_addMethod(object_getClass(cls), oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){NSLog(@"來了一個空的 imp");}));}// 一般交換方法: 交換自己有的方法 -- 走下面 因為自己有意味添加方法失敗// 交換自己沒有實現的方法://   首先第一步:會先嘗試給自己添加要交換的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)//   然后再將父類的IMP給swizzle  personInstanceMethod(imp) -> swizzledSEL//oriSEL:personInstanceMethodBOOL didAddMethod = class_addMethod(object_getClass(cls), oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));if (didAddMethod) {class_replaceMethod(object_getClass(cls), swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));}else{method_exchangeImplementations(oriMethod, swiMethod);}}

方法交換的應用

方法交換最常用的一個應用是防止數組、字典等越界崩潰。在iOS中,NSNumberNSArrayNSDictionary這些都是類簇,一個NSArray的實現可能由多個類組成,所以我們必須獲取到其"真身"進行交換,直接對NSarray進行操作是無效的。

下面列舉了NSArray和NSDictionary本類的類名,可以通過Runtime函數取出本類。

類名真身
NSArray__NSArrayI
NSMutableArray__NSArrayM
NSDictionary__NSDictionaryI
NSMutableDictionary__NSDictionaryM

這里我以NSArray為例來看看方法交換的應用:

#import "NSArray+crush.h"
#import "objc/objc-runtime.h"
@implementation NSArray (crush)
+ (void)load {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{NSLog(@"load");Method fromMethod = class_getInstanceMethod(objc_getClass("NSConstantArray"), @selector(objectAtIndex:));Method toMethod = class_getInstanceMethod(objc_getClass("NSConstantArray"), @selector(new_objectAtIndex:));method_exchangeImplementations(fromMethod, toMethod);});}
- (id)new_objectAtIndex:(NSUInteger)index {NSLog(@"new_objectAtIndex");if (index >= self.count) {// 越界處理NSLog(@"Index %lu out of bounds, array count is %lu.", (unsigned long)index, (unsigned long)self.count);return nil;} else {// 正常訪問,注意這里調用的是替換后的方法,因為實現已經交換return [self new_objectAtIndex:index];}}@end

這里我們新建一個NSArray的分類,交換一下objectAtIndexnew_objectAtIndex方法,我們來看看結果:

在這里插入圖片描述

這里的NSArray類型是NSConstantArray,雖然我們已經越界了但是程序并沒有退出,我們打印了一個報錯,這樣就保證了這個函數的安全.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/907829.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/907829.shtml
英文地址,請注明出處:http://en.pswp.cn/news/907829.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python - 爬蟲;Scrapy框架之插件Extensions(四)

閱讀本文前先參考 https://blog.csdn.net/MinggeQingchun/article/details/145904572 在 Scrapy 中,擴展(Extensions)是一種插件,允許你添加額外的功能到你的爬蟲項目中。這些擴展可以在項目的不同階段執行,比如啟動…

95套HTML高端大數據可視化大屏源碼分享

概述?? 在大數據時代,數據可視化已成為各行各業的重要需求。這里精心整理了95套高端HTML大數據可視化大屏源碼,這些資源采用現代化設計風格,可幫助開發者快速構建專業的數據展示界面。 ??主要內容?? ??1. 設計風格與特點?? 采用…

redis未授權(CVE-2022-0543)

概述 Redis 默認綁定在 0.0.0.0:6379,在未配置防火墻或訪問控制的情況下會將服務暴露在公網上。若未設置訪問密碼(默認通常為空),攻擊者可直接未授權訪問 Redis。利用 Redis 提供的 CONFIG 命令,攻擊者可修改配置并將…

(面試)OkHttp實現原理

OkHttp 是一個高效的 HTTP 客戶端,被廣泛應用于 Android 和 Java 應用中。它提供了許多強大的特性,例如連接池、透明的 GZIP 壓縮、HTTP/2 支持等。理解 OkHttp 的實現原理有助于更好地使用和調試它。 以下是 OkHttp 的一些核心實現原理: 1…

Netty 實戰篇:構建簡易注冊中心,實現服務發現與調用路由

本文將為前面構建的輕量級 RPC 框架添加“服務注冊與發現”功能,支持多服務節點動態上線、自動感知與調用路由,為構建真正可擴展的分布式系統打好基礎。 一、背景:為什么需要注冊中心? 如果每個客戶端都硬編碼連接某個 IP/端口的…

c++之分支

深入理解 C 分支結構:從基礎到實戰 在 C 編程的世界里,分支結構是控制程序流程的重要手段,它賦予程序 “思考” 和 “選擇” 的能力,讓程序能夠根據不同的條件執行不同的代碼塊。本文將帶大家深入探索 C 分支結構,結合…

LLMs之MCP:如何使用 Gradio 構建 MCP 服務器

LLMs之MCP:如何使用 Gradio 構建 MCP 服務器 導讀:本文詳細介紹了如何使用Gradio構建MCP服務器,包括前提條件、構建方法、關鍵特性和相關資源。通過一個簡單的字母計數示例,演示了如何將Gradio應用轉換為LLM可以使用的工具。Gradi…

ubuntu20.04.5-arm64版安裝robotjs

ubuntu20.04.5arm上使用robotjs #ssh,可選 sudo apt update sudo apt install openssh-server sudo systemctl status ssh sudo systemctl enable ssh sudo systemctl enable --now ssh #防火墻相關,可選 sudo ufw allow ssh sudo ufw allow 2222/tc…

craw4ai 抓取實時信息,與 mt4外行行情結合實時交易,基本面來覺得趨勢方向,搞一個外匯交易策略

結合實時信息抓取、MT4行情數據、基本面分析的外匯交易策略框架,旨在通過多維度數據融合提升交易決策質量:行不行不知道先試試,理論是對的,只要基本面方向沒錯 策略名稱:Tri-Sync 外匯交易系統 核心理念 「基本面定方…

Python中scapy庫詳細使用(強大的交互式數據包操作程序和庫)

更多內容請見: 爬蟲和逆向教程-專欄介紹和目錄 文章目錄 一、scapy概述1.1 scapy介紹1.2 安裝1.3 交互模式1.4 安全注意事項二、基本使用2.1 數據包構造基礎2.2 數據包發送2.3 數據包嗅探2.4 數據包分析與操作2.5 網絡掃描技術2.6 協議實現示例三、高級功能3.1 數據包重放3.2 …

基于Web的瀕危野生動物保護信息管理系統設計(源碼+定制+開發)瀕危野生動物監測與保護平臺開發 面向公眾參與的野生動物保護與預警信息系統

博主介紹: ?我是阿龍,一名專注于Java技術領域的程序員,全網擁有10W粉絲。作為CSDN特邀作者、博客專家、新星計劃導師,我在計算機畢業設計開發方面積累了豐富的經驗。同時,我也是掘金、華為云、阿里云、InfoQ等平臺…

[SAP] 矩陣復制(Matrix Copy)

SAP中的復制粘貼功能被稱為矩陣復制,通過點擊對話框或屏幕,并執行下述命令,使用矩陣復制就可以復制多行文本 ① 按下Ctrl-Y,從左上到右下拖拉鼠標來選擇文本 ② 文本高亮顯示后,按下Ctrl-C ③ 移到新的位置插入文本…

【筆記】在 MSYS2(MINGW64)中安裝 Python 工具鏈的記錄

#工作記錄 📌 安裝背景 操作系統:MSYS2 MINGW64當前時間:2025年6月1日Python 版本:3.12(默認通過 pacman 安裝)目標工具鏈: pipxnumpypipsetuptoolswheel 🛠? 安裝過程與結果記錄…

OpenCV CUDA模塊結構分析與形狀描述符------在 GPU 上計算圖像的原始矩(spatial moments)函數spatialMoments()

操作系統:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 編程語言:C11 算法描述 該函數用于在 GPU 上計算圖像的原始矩(spatial moments)。這些矩可用于描述圖像中物體的形狀特征,如面積、質…

Nacos實戰——動態 IP 黑名單過濾

1、需求分析 一些惡意用戶(?可能是黑客、爬蟲、DDoS ?攻擊者)可能頻繁請求服務器資?源,導致資源占用過高。針對這種問題,可以通過IP? 封禁,可以有效拉?黑攻擊者,防止資源?被濫用,保障合法…

opencv + jpeg_turbo(啟用SIMD加速)

背景 opencv的imreadimwrite耗時過大 一張5M的圖片讀用了140ms,寫一張1.7M的圖片用149ms 平臺:mingw64編譯Windows程序版本:opencv4.5.4 加速方案 opencv啟用openmpopencv啟用jpeg_turbojpeg_turbo啟動SIMD加速 下載jpeg_turbo源碼 opencv源碼自帶…

Redis 主從節點

Redis 主從節點的核心區別 特性主節點 (Master)從節點 (Slave/Replica)讀寫權限可讀可寫只讀(默認配置)數據流向數據來源從主節點同步數據連接關系可連接多個從節點只能連接一個主節點故障切換故障時需要手動/自動提升從節點可被提升為新的主節點命令執…

汽車安全:功能安全FuSa、預期功能安全SOTIF與網絡安全Cybersecurity 解析

汽車安全的三重防線:深入解析FuSa、SOTIF與網絡安全技術 現代汽車已成為裝有數千個傳感器的移動計算機,安全挑戰比傳統車輛復雜百倍。 隨著汽車智能化、網聯化飛速發展,汽車電子電氣架構已從簡單的分布式控制系統演變為復雜的移動計算平臺。現…

github好玩的工具

以下是 GitHub 上一些有趣且實用的開源工具推薦,涵蓋 AI 應用、效率提升、趣味開發等方向,結合最新趨勢和項目熱度整理: 一、AI 與深度偽造工具 Deep-Live-Cam 僅需一張圖片即可在視頻直播中實時替換人臉,適用于內容創作和虛擬角色開發,支持多平臺硬件運行(如 NVIDIA CUD…

Python應用for循環臨時變量作用域

大家好!如果你剛開始學習Python,可能會對for循環中臨時變量的作用域感到好奇。下面通過一個簡單的練習,幫助你理解這個概念。 代碼呈現: i 0 for i in range(5):print(i)print(i)代碼介紹: 首先我們初始化變量i 0然后進入for循環,這里i成為…