Swift extension in Cocoapods Framework causing Unrecognised Selector sent to Class - objective-c

I have a Swift framework which is managed via Cocoapods and contains an extension like so:
public extension UIImage {
public static func maskedImageWithColor( color: UIColor, forImageNamed image: UIImage) {
// Implementation
}
}
When I write it in Objective C in my main application I get no compiler errors or warnings.
// No problems here!
[UIImage maskedImageWithColor:UIColor.blackColor() forImageNamed:#"myImage"];
When I run the app, however, it explodes in a mess of 'Unrecognised Selector' errors.
The weird thing is, when the framework is contained within the project and added directly to the relevant parts of build phases (i.e. not managed by Cocoapods), it all works as expected.
Other aspects of the framework—classes, enums, etc.—all work fine too, it just seems to be extensions and only when they're in an framework managed by Cocoapods.
Any ideas on what I might be missing here?

This is because the linker is not loading category methods form static library by default. If you are adding category methods(extension in swift) with static library, add -ObjC to other linker flags in your targets build settings.
Read more here

Related

Adding Swift Class to Objective-C Project with Multiple Targets

I have an existing Obj-C project that houses many targets that all share the same AppDelegate. I want to bridge a swift class that is used by select targets. I can do this easily when I have one target.
When I add a swift file to the project, I select the desired targets and the necessary bridging-header.h files are generated, but when I try to import those -swift.h files, they are can't be found.
Are there steps I'm missing when it comes to projects that have multiple build targets?
EDIT - More Details
I wanted to add a little bit more detail on how my project is set up.
I have a Framework, we'll call it AppFactory, coded in Obj-C. I have multiple build targets that generate different versions of the Core app depending on information in that target's plist. I want a swift file to be utilized by these apps. In my build settings the Defines Module is marked to Yes, I have create this swift class:
#objec class SwiftClass: NSObject { }
And in doing that, Xcode generated the proper Briding-Header.h files.
According to Apple Guides when you have multiple build targets your import header should include the ProductName/ProductModuleName-Swift.h, which should be auto generated by Xcode.
When I look in to my derived data folder, the ProductModuleName-Swift.h does exist, with in each targets build folder, under the AppFactoryCore folder.
I found a solution to my problem.
Swift File Targets:
Instead of having SwiftClass.swift target both the framework and the selected targets (AppA, AppB & AppC), I backpedaled and solely targeted the framework, AppFactoryCore.
Build Settings (Packaging > Defines Module):
I reverted each app target's Defines Module property from YES to NO, and set this property to YES for the framework target.
Swift Class Declaration:
The guide states:
Because the generated header for a framework target is part of the framework’s public interface, only declarations marked with the public or open modifier appear in the generated header for a framework target.
So I added access control modifiers to my class and class functions
#objc open class SwiftClass: NSObject {
//Code
}
Import Header:
Since SwiftClass.swift is only targeting the framework target, and it is in fact a framework that is being used, the header import SwiftClass.swift into the universal AppDelegate was
#import <AppFactoryCore/AppFactoryCore-Swift.h>
Which finally became visible once all previously stated modifications were done.
Now that the file is global to all targets I added a custom attribute to identify if the target running was is one that should utilize SwiftClass.swift.
Hope this helps anyone else trying to accomplish a relatively similar task!

Firebase Analytics has not been created. ld: warning: Some object files have incompatible Objective-C category definitions

ld: warning: Some object files have incompatible Objective-C category definitions. Some category metadata may be lost. All files containing Objective-C categories should be built using the same compiler.
This warning above appeared when I installed the Firebase/Core through cocopods. And I believe it cause an error, because my project can't read the FIRApp.configure() at App deleagate in my project. I am sure I download the GoogleService-Infor.plist and put it in the right place in project, because I done it on my another project before (work properly), my another project didn't have the Objective-C categories warning.
Can anyone help me out?
The process that I've done:
Add pod 'Firebase/Core' in the project podfile, close the xcode.
Open terminal, go to the target project folder, execute pod install.
Appear a warning on terminal (Solution: just put the $(inherited) in the build setting of ALWAYS_..., then problem solve)
[!] The xxxxxx-ebooking [Debug] target overrides the ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES build setting defined in `Pods/Target Support Files/Pods-xxxxxx-ebooking/Pods-xxxxx-ebooking.debug.xcconfig'. This can lead to problems with the CocoaPods installation:
Use the $(inherited) flag, or
Remove the build settings from the target.
Appear a warning on xcode (This issues are simple, just commit the new files then warning will gone):
file:///Users/yyyyyy/Projects/xxxxxx_projects/xxxxxx-ebooking/Pods/Target%20Support%20Files/Pods-xxxxxx-ebooking/Pods-xxxxxx-ebooking.debug.xcconfig: warning: Missing file: /Users/yyyyyy/Projects/xxxxxx_projects/xxxxxx-ebooking/Pods/Target Support Files/Pods-xxxxxx-ebooking/Pods-xxxxxx-ebooking.debug.xcconfig is missing from working copy
Ignore the issues process of 3, 4, because it are easy to solve. Because the most wired warning is :
ld: warning: Some object files have incompatible Objective-C category definitions. Some category metadata may be lost. All files containing Objective-C categories should be built using the same compiler.
Firebase Analytics has not been created. Please, configure Firebase by calling [FIRApp configure]
I did put the FIRApp.configure() in my project. But when I call FA event on some view did load, it will show this warning. I believe it is because the Objective-C warning.
see if using #nonobjc on your static variable resolve the issue
Finally I solved all bugs, thanks my friend help me to debug and stack overflow community. Here is the solution:
In the beginning I thought this warning "ld: warning: Some object files have incompatible Objective-C category definitions" caused the fail of executing the FIRApp.configure(). But it end out it are two different errors!
First issue, The FIRApp.configure() issues is because my project has error migration when swift 2.2 to 3.0. The xcode suggested me to change the old 2.2 syntax to be private method in AppDelegate:
private func application(application: UIApplication, willFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
// This method is your app’s first chance to execute code at launch time.
FIRApp.configure()
return true
}
The FIRApp.configure() will never execute because it is not AppDelegate's method. So, changed back to correct syntax will solve the issues:
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
print("Fire! willFinishLaunchingWithOptions")
return true
}
Second issue, if using the below demo syntax and your project has objective-C in third party plug-in or your code, it will cause the warning "Some object files have incompatible Objective-C...". Maybe, because syntax is old for swift 3.0 so it appear this warning.
class var applicationBuildNumber: String {
if let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
return build
}
return "Build Number Not Available"
}
If you using class function, then this warning will disappear:
class func appLocalBuild() -> String {
return Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as! String
}

Xcode pluginDidLoad not getting called when adding objective-c file in swift plugin

I'm working on a plugin for Xcode. It is supposed to be written in Swift.
When I start with a fresh plugin project (I'm using this Xcode Plugin template which is also available via Alcatraz) the project compiles and runs fine.
The pluginDidLoad method is getting called right after Xcode starts. As soon as I add any Objective-C file (and a bridging header of course) the pluginDidLoad method is not getting called anymore.
The Objective-C file might be as simple as an empty class that is a subclass of NSObject.
Removing the target-memberbership (for the plugin-target) from the newly created Objective-C (.m) file the aforementioned mentioned method is getting called again.
Has anyone developed a Xcode plugin in Swift that also uses Objective-C files before and got this working?
Update
It seems that my original solution only works with Swift only projects because Xcode always takes the objective c class if you have one.
So here is another trick: Extend the NSObject class by the function class func pluginDidLoad(bundle: NSBundle) {} and initialize your plugin there. Then it doesn't matter on which class it is called. You might have to check that also all Swift classes subclass NSObject. I pushed it to my repository that you can have a look
Original Post
I think I could reproduce the problem now. To simplify the problem, let's say that we have only two swift classes PluginMain and PluginHelper.
As you said, sometimes the plugin isn't getting called for some mysterious reason. I was struggling with the problem again and I was wondering how Xcode knows which class is the main class. So I came up with the idea to put the following initializer in both classes PluginMain and PluginHelper
class func pluginDidLoad(bundle: NSBundle) {
let appName = NSBundle.mainBundle().infoDictionary?["CFBundleName"] as? NSString
if appName == "Xcode" {
//sharedPlugin = SwiftySafe(bundle: bundle)
//initialize your shared plugin
}
}
By putting a breakpoint or log message in pluginDidLoad in both classes, I notices that Xcode isn't ignoring the plugin, it is just loading the wrong class (e.g. PluginHelper instead of PluginMain).
The Solution
It turns out that Xcode uses the class that is compiled first as the main class and calls pluginDidLoad only on that. So you can change that by reordering the "Compiled Sources" under your target settings->Build Phases. Move your main class so that it is on top. In the following image you find an example from my project. SwiftySafe is my main class.
My example
You will find my project here https://github.com/creinders/SwiftySafe if you want to compare the settings.
The pluginDidLoad method is called on the principal class. When the principal class is a Swift class, you have to include the module name in the NSPrincipalClass Info.plist key.
So if your target name is MyPlugin and your principal class is MyClass, set NSPrincipalClass to MyPlugin.MyClass.
Also make sure that MyClass inherits from NSObject.
Sometimes you need to tell Xcode to reload bundle. Run this and restart Xcode
defaults delete com.apple.dt.Xcode DVTPlugInManagerNonApplePlugIns-Xcode-7.3

Xcode 6 crashing when using Objective-C subproject inside Swift

I have a fairly large Obj-C library that's a subproject in Xcode in several Obj-C projects and it works well.
I'm trying to use that same library in a Swift project but it's crashing Xcode when it gets to a variable declaration of one of the types from my library.
The headers seem to be working because auto-complete sees the proper types from my library. I've added my library (libMylib.a) in Link Binary with Libraries just like in my other Obj-C projects.
class thing {
var width: CGFloat
// more variables
var foo: MyClass // this is a class contained in my subproject
// functions, etc..
}
It crashes Xcode if the var foo line is there, not even when I create or access it. If it's just there. That's why I think it has something to do with not linking the subproject properly or similar.
Not sure what I'm missing.
Thanks.

Objective-c obfuscation of methods works in DEBUG but crashes in RELEASE

I made a xcode project where i did some security stuff and they asked me to obfuscate the method names
like so
#define specialMethod a9328238
+(void) specialMethod
{
// do security stuff
}
i made a .framework library from the project ( project A ) and included it into another project ( project B ).
but when i run (project B) with a Release build configuration it always crashes like so.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[SecurityClass a9328238]: unrecognized selector sent to class 0x337cc4'
so it crashes when it tries to acces the method.
But when i run (project B) it with a Debug build configuration it runs smooth
(i have kept all my build configuration settings as default)
Where have you placed the #define for obfuscation ? Is it in the header file (.h) or in the implementation file (.m) of the framework ?
For the obfuscation to be effective, it must be placed in a file that is both included by the implementation and the caller.
You can also check that the pre-processing is ok by inspecting the pre-processed file. Select the implementation file and go to the menu Product > Generate Output > Generate Preprocessed File (you can select the configuration at the bottom of the screen).
My hunch is the #define location/visibility as well.
But you may want to consider this from another angle. You could change:
#define specialMethod a9328238
+(void) specialMethod
{
// do security stuff
}
to:
#interface SecurityClass : NSObject
// private obfuscated interface:
+ (void)a9328238;
// {
// do security stuff in a9328238's definition
// }
#end
// here is the public interface:
static inline void SecurityClass_LogIn() {
[SecurityClass a9328238];
}
dropping #define altogether.
In use:
SecurityClass_LogIn();
…
Since this is a class method, you could write an obfuscated function wrapped in a human readable inline instead. A well crafted C implementation will be much more difficult to pick apart than objc.
A more complete example would help us narrow down the possibilities.
Also verify there are no warnings -- the compiler may warn you if you have called an undeclared selector. It's possible that the method is called where the #define is not visible in other cases.
It seems that the executable which imports the obfuscated framework tries to access the non-obfuscated methods.
You should check the symbols in the framework. Use nm on the static library in the framework to see the exported symbols (marked with a 't'). Make sure the symbols are obfuscated.
If you've wrapped everything into a framework, have you made sure that the appropriate headers are exposed outside of the framework? Headers inside a framework aren't exposed the same way as normal files are. Go to your Project->Build Phases, in the bottom right you should see "Add Copy Headers". This will add a new section in your build phases. Inside this section, click the "+" and the headers that define your method names.