Can't decode Swift object in framework - frameworks

I had a perfectly working app. I wanted to modularize my app because I envision needing bits and pieces of it in other apps. So, I created two frameworks. The two frameworks build fine and my app with the two frameworks embedded in it also builds fine.
My problem comes when I try to unarchive data which has a class that is now in one of my frameworks. I get this error:
reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (myProjecy.MyObject) for key (NS.objects); the class may be defined in source code or a library that is not linked'
In my app I can create instances MyObject fine. All the required methods in the framework are either open or public (or the app wouldn't even build).
What an I missing?
The class in the framework is in Swift and the class trying to unarchive it in is Obj-C. I'm using Xcode 9.
Thanks.

OK. I found the problem. Re-visiting this post led me to a solution.
In my case, because the original object was archived while still part of the app and my app specified to use module name then the object was archived with the app's module name. Now, the unarchiver is trying to use the module name in the framework which is what leads to the problem.
So, I basically had to sprinkle a few of these:
NSKeyedUnarchiver.setClass(ClassName.self, forClassName: "AppModule.ClassName")
NSKeyedArchiver.setClassName("AppModule.ClassName", for: ClassName.self)
And everything works fine!
Without this code the the unarchiver tries to use "FrameworkModule.ClassName".

Related

Xcode Project with shared CoreData for iOS & MacOS Targets

A few months ago I started to work on a MacOS Application which required CoreData implementation. Today I am beginning to work on a related iOS application that is based on the same Api, and though relies on the same model. I added my iOS target on my project and I mutualised some classes (by adding them to both targets), including the CoreData Stack:
I added my app.xcdatamodeld on both targets
I added my Object+CoreDataClass.swift & Object+CoreDataProperties.swift on both targets
I modified my ManagedObjectsController to support both iOS and MacOS implementation
by defining the appDelegate for both iOS and OSX, I can access it the same way to get my context let context = appDelegate.persistentContainer.viewContext
It works fine but I was wondering if I am doing this right. Is this the correct way to mutualise access to appDelegate instances between two targets?
Should I use some kind of Protocole & Generic Typing?
Or should I simply build a ManagedObjectController for each target?
Thanks
Declaring a protocol helps if you have multiple classes which you want to both support common functions. But in this case, UIApplication and NSApplication already support the common functions you need! The problem is that you need access two different global symbols.
One alternative worth considering is: Instead of declaring two classes IosAppDelegate and MacAppDelegate, declare a single class AppDelegate, and move that dirty #if code out of your ManagedObjectsController class and into AppDelegate. Then, this AppDelegate could be used wherever you need a reference to the shared app delegate. This is more than a few places in most projects.
But if you want to get your product out the door asap, and this ManagedObjectsController is the only place you need the shared app delegate, your code is fine.

TypeAlias a class contained in NSKeyedArchiver file

My app uses a file created with NSKeyArchiver, it has a reference to an object that is contained in an external Framework. The external Framework is no longer is available in the app Xcode project. So when I attempt to load the archive file, the app will crash when attempting to resolve the type of this object. i.e MyMobileKit_Token
I was wondering if it's possible to create a 'local' instance of the object type in the app, then using the typealias fool the unarchive operation to reference the local type i.e Token
Or would I have to create a new 'proxy' Framework project with this one class and add the proxy framework to my app project keeping the naming of the framework and class exactly the same.
Any ideas?
Thanks
Aha, I found the answer. I created a class called Token with the same properties in the framework. Then I added the following line before opening the archive file:
NSKeyedUnarchiver.setClass(Token.self, forClassName: "MyMobileKit.Token")
Seemed to work out.

How do you create public extensions, in a shared framework, for XCTest?

For example, I never use the description of XCTestCase.expectation, so I'd like to use a function to provide a default for it, and make it clear via naming that I'm initializing the expectation, as you can't really use an initializer for XCTestExpectation. But if the extension is not in a test target, then it can't compile:
Cannot load underlying module for 'XCTest'
import XCTest
public extension XCTestCase {
func makeExpectation() -> XCTestExpectation {
return expectation(withDescription: "")
}
}
I've created an xcworkspace here (https://github.com/dtweston/TestFrameworkSample) that demonstrates a solution to your issue. There are two projects in this workspace:
SampleApp project with an iOS app target and a unit test target.
SharedTestFramework project that imports XCTest and declares the single extension you put in your question.
The SampleAppTests target links to the SharedTestFramework to be able to use the extension it defines. The single test file imports the SharedTestFramework.
With those steps, I also encounter the Cannot load underlying module for 'XCTest' when building the SharedTestFramework.
The fix for that is to update the Framework Search Paths to include "$(PLATFORM_DIR)/Developer/Library/Frameworks". Now the SharedTestFramework compiles correctly, and as you can see in the workspace I uploaded, the SampleAppTests target is able to use it successfully.
Old and busted answer
Are you building a separate framework that is designed to be imported into test targets? If that's the case then I think you just need to reference XCTest.framework from this custom framework you're building.
On the other hand, if you're trying to add this extension to a framework that is used by your app target, that seems like a bad idea, because it would mean linking XCTest.framework to the binary that goes to the store and runs on people's devices.
I'm not sure if that's possible. I'm more confident that it's not a scenario Apple expects or supports.

+(void)load message not sent to framework class in device runtime

I've trawled through questions here on SO looking for any hints to why I'm seeing this behaviour, and nothing yet.
Consider a class (actually two classes exhibiting the same problem), built into a static library, wrapped in a framework bundle (steps used). They inherit from Foundation framework class clusters (NSMutableDictionary and NSMutableArray).
The use of these classes relies on some static variables being initialised before a static function (not class method!) is used to allocate and initialise an instance (a kind of factory helper function I guess?).
When an iOS app project links to that framework there is a difference the Objective-C runtime class loading behaviour between the Simulator and the Device.
Specifically, on a device (iPhone 4, iOS 4.3.3) when the app is loaded these classes do not get a +load message, and the static vars do not initialize, therefore the static factory method fails. On the Simulator, the messages are sent, and all works as intended. Could it be a problem with the Device runtime having a
My question is, can my framework be configured differently to ensure the +load messages are sent? Or have I run into a bug with static library/framework class loading in iOS?
The classes are from the JSONKit library (JKArray, JKDictionary).
An example project that illustrates this problem is here – https://github.com/ohhorob/JSONKit-in-framework-demo
EDIT: As per #bbum's suggestion, I've verified that the JKDictionary and JKArray classes are in fact loaded and available while the application is running. The DeviceBroken branch on the GitHub project is updated with the verification used.
I filed a bugreport (#9461567) with Apple.
The +load methods are not called because you did not actually create a static library but a Relocatable Object File bundle. If you create the static framework with either make-fmwk or the iOS Universal Framework template then the load methods will be called as expected.
Odd; I'd do an NSLog(#"klassy klass %#", [MysteryClass class]); and make sure the classes are actually loaded (but see below -- this may "fix" the problem).
If they are, then this is a bug in the DYLD loader and please file it.
If not, then it is likely that the linker is stripping the class(es) because nothing references them directly. Try adding [MysteryClass class] in the app's applicationDidFinishLaunching: method (doesn't really matter where or, even, if it gets executed... but that'll be an obvious spot).
Also, I'd suggest not using +load and, instead, writing a constructor function. I.e.:
__attribute__((constructor))
static void initLibrary()
{
....
}
If this is a linker issue, that may or may not fix the problem. It is, however, much clearer as to your intentions than the rather magical +load method.

NSConcreteData vs NSData

I have added an extension to NSData (base64 extension), which I kept over a separate infrastructure class lib project. But when i use this method from my main project i am getting an error like this: "-[NSConcreteData encodeBase64]: unrecognized selector sent to instance 0x121e60'".
But if i keep the same class in my main project itself, this will execute with out any issue.
I call this method in the following way:
[dev setToken:[token encodeBase64]];
Please suggest why this is not working if i put the extension in another project. (I am already using some other extensions, eg. for NSDate, like this with out any issue.)
Is this on iPhone OS 3.0? The 3.0 SDK broke the use of -ObjC, but you usually are able to link in categories for a static library by adding the -all_load option to Other Linker Flags within your target application.
The issue is that the metadata necessary to configure a category is usually stripped by the linker because it appears to be dead. If you add the "-ObjC" LDFLAG to your project it will tell the linker to link all the potential ObjC info even if it appears to be dead.