程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> Objective-C的方法替換

Objective-C的方法替換

編輯:關於C語言

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     { www.2cto.com         // 這個會替換掉通常使用的-[NSView drawRect:]         [[NSColor blueColor]set];         NSRectFill(r);     }     @end   這種方法其實僅僅適用於復寫目標類的父類中實現的函數。如果直接復寫目標類中的方法,使用歸類會帶來兩個問題: 它無法調用方法的之前的實現。替換掉後,之前的實現就被完全改寫了。但大部分情況下,只是想增加些功能,並不期望完全替代。 如果被多個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) 復寫不是你自家的類是危險的! 盡量避免這麼做,要不然就盡最大的可能細心處理。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved