Objective-C的方法替換

來源:互聯網
上載者:User

Objective-C的方法替換

(Method Replacement for Fun and Profit)

本文將要討論Objective-C中的方法替換(method replacement)和swizzling(移魂大法)。

 

重寫類的方法(Overriding Methods)

Overriding methods在任何物件導向語言中都很常見,主要用於子類化中。在子類中複寫一個方法,然後在子類的執行個體就可以使用這個被重寫的方法。

 

對於一個你無法控制其執行個體化(instantiation)的類,有時你或許會想複寫它的某個方法,雖然有點瘋狂。子類化可做不到,因為你沒有機會子類化你的子類。

 

偽裝(Posing)

Posing是個很有趣的技術,不過已經過時了,因為64位和iPhone環境下的Objective-C Runtime中不再支援它了. 通過這個偽裝(posing),你可子類化,然後將這個子類偽裝成它的父類。像變魔術一般,Runtime會讓這個子類應用於各處,這時方法複寫又有了用處。既然被拋棄了,也就不必多費口舌了。

 

 

歸類(Categories)

使用歸類(category)的技術,可以方便地為一個已經存在的類複寫其方法:

    @implementationNSView(MyOverride)

    

    - (void)drawRect:
(NSRect)r

    {

        // 這個會替換掉通常使用的-[NSView drawRect:]

        [[NSColor blueColor]set];

        NSRectFill(r);

    }

    @end

 

這種方法其實僅僅適用於複寫目標類的父類中實現的函數。如果直接複寫目標類中的方法,使用歸類會帶來兩個問題:

  1. 它無法調用方法的之前的實現。替換掉後,之前的實現就被完全改寫了。但大部分情況下,只是想增加些功能,並不期望完全替代。
  2. 如果被多個category複寫,運行時(runtime)並不保證哪個真正會被使用到。
Swizzling (譯為“移魂大法”比較合適,就是太誇張了!)

使用一個稱為swizzling的技術,可以為歸類(category)解決上面兩個問題,既可以調用舊的實現,又可以避免多個category帶來的不確定性。它的秘訣是使用一個不同的函數名來複寫,然後由運行時(runtime)交換它們。

 

首先,用一個不同的名字複寫:

    @implementationNSView(MyOverride)

   

    - (void)override_drawRect:
(NSRect)r

    {

        // 調用舊的實現。因為它們已經被替換了

        [self override_drawRect: r];

       

        [[NSColor blueColor]set];

        NSRectFill(r);

    }

    @end

 

(譯註:呵呵,不知道你是不是和我一樣,初次看到代碼還以為是個遞迴調用呢。) 其實是這個新的方法在執行時已經和原先的函數對調了(現在還沒做到,往下看!)。在運行時,調用 override_drawRect: 方法其實就是調用舊的實現。

 

接下來,你還要寫些代碼才能完成交換:

    voidMethodSwizzle(Class c,SEL origSEL,SEL
overrideSEL)

    {

        Method origMethod = class_getInstanceMethod(c, origSEL);

        Method overrideMethod= class_getInstanceMethod(c, overrideSEL);

周全起見,有兩種情況要考慮一下。第一種情況是要複寫的方法(overridden)並沒有在目標類中實現(notimplemented),而是在其父類中實現了。第二種情況是這個方法已經存在於目標類中(does existin the class itself)。這兩種情況要區別對待。

 

(譯註: 這個地方有點要明確一下,它的目的是為了使用一個重寫的方法替換掉原來的方法。但重寫的方法可能是在父類中重寫的,也可能是在子類中重寫的。)

 

 對於第一種情況,應當先在目標類增加一個新的實現方法(override),然後將複寫的方法替換為原先(的實現(original one)。

 

運行時函數class_addMethod 如果探索方法已經存在,會失敗返回,也可以用來做檢查用:

       
if(class_addMethod(c, origSEL, method_getImplementation(overrideMethod),method_getTypeEncoding(overrideMethod)))

        {

 

如果添加成功(在父類中重寫的方法),再把目標類中的方法替換為舊有的實現:

            class_replaceMethod(c,overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));

        }

 

(譯註:addMethod會讓目標類的方法指向新的實現,使用replaceMethod再將新的方法指向原先的實現,這樣就完成了交換操作。)

 

如果添加失敗了,就是第二情況(在目標類重寫的方法)。這時可以通過method_exchangeImplementations來完成交換:

        else

        {

            method_exchangeImplementations(origMethod,overrideMethod);

        }

    }

 

對於第二種情況,因為class_getInstanceMethod 會返回父類的實現,如果直接替換,就會替換掉父類的實現,而不是目標類中的實現。(詳細的函數說明在這裡)

 

舉個具體的例子, 假設要替換掉-[NSView description]. 如果NSView 沒有實現-description (可選的) 那你就可會得到NSObject的方法。如果調用method_exchangeImplementations ,
你就會把NSObject 的方法替換成你的代碼。這應該不會是你想要的吧?

 

最後在一個合適位置調用一下就可以了。比如在一個+load 方法中調用:

    + (void)load

    {

        MethodSwizzle(self,@selector(drawRect:),@selector(override_drawRect:));

    }

 

直接重寫(Direct Override)

前面的內容確實有些難懂。Swizzling的概念的確顯得有些古怪,特別是在函數中轉來轉去的,多少讓人有些思維扭曲的感覺。我下面要介紹一個更為簡潔,也更容易理解和實現的方式。

 

這種方式不再需要儲存舊有的方法,也不必動態區分[self override_drawRect: r] 。我們從頭實現。

 

相對於將原有的方法存放於一個新的方法中,這裡使用一個全域指標來儲存:

    void (*gOrigDrawRect)(id,SEL,
NSRect);

 

然後在+load 裡賦值:

    + (void)load

    {

        Method origMethod = class_getInstanceMethod(self,@selector(drawRect:));

        gOrigDrawRect = (void*)method_getImplementation(origMethod);

(我喜歡把它轉換為 void *,因為比那些又長又奇怪的函數指標好輸入多了。)

 

然後像前面介紹的那樣用新的實現替換掉就可以了。因為class_replaceMethod本身會嘗試調用class_addMethod和method_setImplementation,所以直接調用class_replaceMethod就可以了。

 

實現如下:

Method origMethod =class_getInstanceMethod(self,
@selector(drawRect:)); 
gOrigDrawRect = (void *)class_replaceMethod(self,@selector(drawRect:), (IMP)OverrideDrawRect,method_getTypeEncoding(origMethod))

 

最後實現複寫方法。和之前不同的是,這裡是一個方法,而不是方法:

    staticvoidOverrideDrawRect(NSView*self,SEL
_cmd, NSRect r)

    {

        gOrigDrawRect(self,_cmd, r);

        [[NSColor blueColor]set];

        NSRectFill(r);

    }

當然,這個方法不是那麼優雅,不過我認為它更易於運用。

 

溫馨提示(The Obligatory Warning)

複寫不是你自家的類是危險的! 盡量避免這麼做,要不然就盡最大的可能細心處理。

原文地址: FridayQA,2010-01-29, Method Replacement for Fun and Profit

參考: Objective-CRuntime Reference

轉載請註明出處:

http://blog.csdn.net/horkychen

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.