I'm trying to change the device currentLocale output to perform some interesting unit tests, this is the code that I'm using but it seems that the returning currentLocale doesn't get overridden. Any hint?
extension NSLocale {
class func frLocale()->NSLocale{
return NSLocale(localeIdentifier: "fr_FR")
}
class func forceCurrentLocale(){
let originalSelector = #selector(NSLocale.currentLocale)
let swizzledSelector = #selector(self.frLocale)
let originalMethod = class_getClassMethod(self, originalSelector)
let swizzledMethod = class_getClassMethod(self, swizzledSelector)
let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
}
// EDIT
The code above doesn't work. But If I write it like this it works:
class func forceCurrentLocale(){
let originalSelector = #selector(NSLocale.currentLocale)
let swizzledSelector = #selector(NSLocale.frLocale)
let originalMethod = class_getClassMethod(self, originalSelector)
let swizzledMethod = class_getClassMethod(self, swizzledSelector)
method_exchangeImplementations(originalMethod, swizzledMethod)
}
what's wrong with class_addMethod
in that case?
Your first method would be correct to swizzle an instance method, but not for a class method. What happens – if I understand it correctly – is that
let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
adds an instance method to the class, and returns true
. Then
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
replaces an instance method, which fails.
If you look at the Method Swizzling article from NSHipster then you'll find the following comment:
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(class, originalSelector);
// Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
Translated to Swift that would be
class func forceCurrentLocale(){
let originalSelector = #selector(NSLocale.currentLocale)
let swizzledSelector = #selector(self.frLocale)
let classObject : AnyClass = object_getClass(self)
let originalMethod = class_getClassMethod(classObject, originalSelector)
let swizzledMethod = class_getClassMethod(classObject, swizzledSelector)
let didAddMethod = class_addMethod(classObject, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
and then the swizzling works as expected. (The crucial point is that
class_addMethod()
is called on the class object, not on self
.)
But actually I don't see any advantage over your second method.
didAddMethod
will always return false
because frLocale
is already defined as a class method of NSLocale
.