Xcode Project with shared CoreData for iOS & MacOS Targets - swift

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.

Related

Can't decode Swift object in framework

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".

Using only part of a class from a different target

I just created a new target for the Lite version of my app. The Lite app only uses part of a base class that I have in the main app, ie it won't need to use an option that requires it to import 4 or 5 files.
My question is, from a design perspective, what is the best way to handle this so that my Lite version can only use the part of the class that it needs? Obviously, one solution is I just import those 4 unnecessary files into Lite build phase, and just use the whole class (even the parts it doesn't need). This seems inefficient though. I know I can do an ifndef to block those files from being imported if the Lite version is running, but how do I block out the code in the class from also not being picked up by the compiler?
Would a better way just be to have my Lite version create a subclass of the Base class that only uses the options it needs? But then I believe, would I still need to import those unnecessary files?
Just a bit confused about this, first time I've ever created another target that utilizes code from the main target. Any help appreciate thanks.
Put the common/lite functionality in a super class. Heavy functionality in the sub-class.
As another answer points out, you can handle this by putting the lite functionality in a subclass and the full functionality in a superclass.
Another option is to use a single class, and add the full functionality in an Objective-C category. Essentially, you can define methods in the category to supplement – or replace – methods in the base implementation.
Unlike a subclass, however, methods defined in a category can't invoke super to get the base class's functionality. super still refers to the base class's superclass, whether that's NSObject, UIDocument, or what have you – not the implementation without the category.
The advantage is that you only have one class name, so the code which instantiates your class (or classes) doesn't need to use something like #ifdef to switch classes and #includes depending on whether you're building the lite or full version.

+(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.

How to check if not available methods are used if deployment target < base sdk?

I would like to know how you check that your code do not call not available methods when the deployment target is inferior to base SDK ?
It is possible to run the application on a device with the SDK equal to deployment target, but I search a way more 'automatic'. Any idea ?
Regards,
Quentin
The easiest way to do this is to use the __IPHONE_OS_VERSION_MAX_ALLOWED preprocessor define.
You do this by adding
__IPHONE_OS_VERSION_MAX_ALLOWED=__IPHONE_4_2
or something similar to your "Preprocessor Macros" option in Build Settings of your target. You can look up versions available in <Availability.h>.
Unfortunately if you add this define it will cause mismatch errors with your precompiled header. So, to fix that you need to turn off the "Precompile Prefix Header" option in your build settings as well.
Once you do this you'll get a bunch of errors for classes that don't exist on your targeted SDK (for instance NSOrderedSet doesn't exist in iOS 4.2). If you're trying to go back pre-iOS 4 you'll probably get so many errors that the compiler bails--I don't know of a workaround for this. In any case, ignore the errors about missing classes in the UIKit headers, and go to the bottom of the error list; there you should find an error for each time you use a method or class that isn't included in the SDK pointed to by __IPHONE_OS_VERSION_MAX_ALLOWED. Make sure each of these methods is enclosed in an
if( [targetObject respondsToSelector:#selector(thePossiblyMissingSelector:)]
and you should be safe. Classes that may be missing should be tested as well
if ([NSOrderedSet class] != nil)
These settings aren't something you want to accidentally forget to flip back however. To make this an automatic option for testing, do the following:
Create a new build configuration called something like "Old SDK Testing".
Define __IPHONE_OS_VERSION_MAX_ALLOWED and the precompiled head option only for this configuration (hit the disclosure arrow beside each line in Build Settings to access per configuration settings).
Duplicate your current Scheme and set its name to something like "Old SDK Check".
Set the Build Configuration of the Run item in this new scheme to the build configuration you created in step 1.
Select the new Scheme and build.
Notes:
I make no guarantee that this will catch any/all of your issues.
Anything outside of UIKit will not be caught by this check.
This is not a substitute for testing your code on the versions of iOS you
plan to support.
use NSClassFromString();
Class cls = NSClassFromString(#"YourClass");
if (cls == nil)
is this you are looking for?
best way to do that which i found: compile code with old SDK :) link which can help
I think this question is releated with next
I belive that someday Apple allow to compile project for old SDK by simple defining #define __IPHONE_OS_VERSION_MAX_ALLOWED __IPHONE_3_0
upd: I found solution here
4.3 5.0 and 5.1 SDK just fail to compile after trying to redefine this macro
Are you looking for something like
- (BOOL)respondsToSelector:(SEL)aSelector
If you have an instance of a class, you can use the following to see if it understands the method you want to call:
if ([mipmapBrowserView respondsToSelector:#selector(setBackgroundColor:)]) {
// set the background layer since IKImageView supports it
}
Here, mipmapBrowserView is an instance of IKImageView, which was first introduced in Mac OS X 10.5. The setBackgroundColor: method of IKImageView was only added in 10.6, however, so I need to check before I call it. This allows me to build against the 10.6 SDK, and take advantage of the new features, yet still support OS X 10.5 as well. While this example involves OS X rather than iOS, the same method (pun intended?) works in iOS as well.
Note that things are slightly different when you are subclassing a class, and you want to know whether the superclass responds to a certain selector:
"You cannot test whether an object inherits a method from its superclass by sending respondsToSelector: to the object using the super keyword. This method will still be testing the object as a whole, not just the superclass’s implementation. Therefore, sending respondsToSelector: to super is equivalent to sending it to self. Instead, you must invoke the NSObject class method instancesRespondToSelector: directly on the object’s superclass...."

Classes that mimic the behavior of iPhone Application Settings...but within an app?

Has anyone written iPhone classes that mimic the behavior of the Application Settings? It would be nice to be able to define settings tables for use within my app using exactly the same XML structure, etc.
See Is there a library or framework for setting preferences from within an iPhone application?
Esp. the mySettings library mentioned there:
mySettings [...] uses a plist
configuration file like the one used
by the settings app, with some added
options.
[...]
By default the settings themselves are
stored in the standard user defaults
object ([NSUserDefaults
standardUserDefaults]), but you can
use any object that supports key-value
coding. This enables you to use your
model classes directly in the settings
view.
The application settings (or NSUserDefaults) is essentially a glorified NSDictionary. You add objects to the settings and associate them with keys, so you can retrieve them later on.
If you want to do this, just create a class which wraps a singleton instance of a NSDictionary. That way you could reference it throughout your app like:
[[MyAppSettings sharedInstance] objectForKey:key];