Method swizzling (MacOS)

Share your advanced PureBasic knowledge/code with the community.
Justin
Addict
Addict
Posts: 829
Joined: Sat Apr 26, 2003 2:49 pm

Method swizzling (MacOS)

Post by Justin »

This shows how to do the method swizzling technique to receive notifications for certain events.
The pb callbacks functions must match the method definition, the first two params are always self and cmd, then the rest, see example.

Inside the callback you have to call the original method by calling the new method because since they are switched you are calling the original one, it is a bit confusing but is how it works, see example.

The function only switches the method if the new method does not already exists.

Code: Select all

Procedure cocoa_swizzle_methods(class.i, origMethodName.s, newMethodName.s, newMethodFunc.i)
	Protected.i origSel, newSel
	Protected.i origMethodImp, origTypeEnc
	Protected.i origMethod, newMethod
	Protected.s origTypeEncStr
	
	origSel = sel_registerName_(origMethodName)
	newSel = sel_registerName_(newMethodName)
	
	;Replace only if the new method does not exists.
	If class_getInstanceMethod_(class, newSel) = 0
		origMethod = class_getInstanceMethod_(class, origSel)
		origMethodImp = method_getImplementation_(origMethod)
	
		;Get original method type encoding
		origTypeEnc = method_getTypeEncoding_(origMethod)
		If origTypeEnc
			origTypeEncStr = PeekS(origTypeEnc, -1, #PB_UTF8)
		EndIf 

		;Add new method without implementation
		class_addMethod_(class, newSel, 0, origTypeEncStr)
		newMethod = class_getInstanceMethod_(class, newSel)
		
		;Replace original method with new method implementation (newMethodFunc)
		class_replaceMethod_(class, origSel, newMethodFunc, origTypeEncStr)
		
		;Set the new method implementation with the original method implementation.
		If origMethodImp
			method_setImplementation_(newMethod, origMethodImp)
		EndIf 
	EndIf 
	
	ProcedureReturn origMethodImp
EndProcedure

ProcedureC my_on_dealloc(self.i, cmd.i)
	Debug "my_on_dealloc"
	
	;do something
	
	;Call the original method, since it has been switched my_dealloc holds the
	;original implementation thus not entering in recursion.
	CocoaMessage(0, self, "my_dealloc")
EndProcedure

ProcedureC my_on_viewDidHide(self.i, cmd.i)
	Debug "my_on_viewDidHide"
	
	;do something
	
	;Call original method
	CocoaMessage(0, self, "my_viewDidHide")
EndProcedure

Procedure main()
	Protected.i win, btn1, btn2, ev, btnClass
	
	win = OpenWindow(#PB_Any, 10, 10, 400, 300, "Test")
	btn1 = ButtonGadget(#PB_Any, 10, 10, 80, 30, "Hide")
	btn2 = ButtonGadget(#PB_Any, 10, 50, 80, 30, "Free")
	
	btnClass = object_getClass_(GadgetID(btn1))
	
	cocoa_swizzle_methods(btnClass, "dealloc", "my_dealloc", @my_on_dealloc())
	cocoa_swizzle_methods(btnClass, "viewDidHide", "my_viewDidHide", @my_on_viewDidHide())

	Repeat
		ev = WaitWindowEvent()
		Select ev
			Case #PB_Event_Gadget	
				Select EventGadget()
					Case btn1
						HideGadget(btn1, #True)
						
					Case btn2
						FreeGadget(btn2)
				EndSelect
		EndSelect
	Until ev = #PB_Event_CloseWindow
EndProcedure

main()