I've got an Xcode project with three different targets (say soccer, baseball, basketball) resulting in three different apps. Most of the code is the same, but sometimes it's target-specific.
What's the best way to implement methods which are specific to a target? I'd like to avoid
if ([AppDelegate isSoccerTarget] {
...
} else if () {
...
} else if () {
...
}
I was thinking about using categories which only exist in one of the three targets, but then I can't use a default implementation. And I'd like to avoid inheritance as some classes are already in a class hierarchy and I'd like to keep that simple (avoid person => player, manager resulting in soccerPlayer, basketballPlayer etc.).
What's your way of doing this?
The way I handle it is I place anything that is similar in a super class that is added to all targets, and then I create a new class (for your example "Player") that is different for each target.
So in the source directory I would have subdirectories and files:
basketball/Player.m
baseball/Player.m
...
And then I would select the "Target Membership" for basketball/Player.m to be the "Basketball" target.
This way I only have to instantiate a Player class once and depending on what my target is, it will automatically create the proper class. Hope this helps.
You would make your targets in the Xcode project pane (the file at the very top), and then, in one of the tabs in each target (I forget which one) add some values in the preprocessor macros (might be pre compiler macros). Then, in your code, you can do this: say your preprocessor macro for the baseball target was called BASEBALL and soccer was SOCCER. Your code would look like this:
...blablablaothercode...
#ifdef BASEBALL
NSLog(#"Baseball!");
#endif
#ifdef SOCCER
NSLog(#"Soccer!");
#endif
...blablablaothercode...
These can be used anywhere normal code could be used. Think of it as a 'switch' statement that the compiler can use to see what code to use for each target.
Related
Does any one know how to have different swift control access for different targets. Basically I have an iOS framework in swift with two targets A and B. I wanted a class say "Hello" as public in target A and internal in target B. One of the way to have this define a Swift flag and have something like this.
#if FLAG
public class Hello {
#else
class Hello {
#endif
Open brace with no closing brace in the same scope does compile in swift. One of the possible way to have the empty class under flag and put the rest in an extension. This is not a good solution since I need to make some of the functions also under flag to control the access. Is there any solution to control the access without duplicating the functions ?
Or is the approach fundamentally wrong ? I need to have a wrapper for the class to make it public ?
Unfortunately, this isn't something you can do in Swift. In Objective-C you could do tricks like this and the compiler would ignore anything that wasn't valid inside the macros that weren't excluded. This is not the case for Swift. The entire file must be valid, including parts that are being ignored because of the #if's
I have a rather large Dart application that uses part and part of so that I don't have to use relative imports. However, the application may have something like this at the entry point file:
part 'file1.dart';
part 'file2.dart';
part 'file3.dart';
part 'some_sub_dir/file1.dart';
part 'some_sub_dir/file2.dart';
part 'some_sub_dir/file3.dart';
part 'some_sub_dir/file4.dart';
part 'some_sub_dir/file5.dart';
part 'some_sub_dir/file6.dart';
...
As far as I know, my IDE (WebStorm) doesn't automatically add a part statement when I create a new class. Is there any way I can avoid having to use so many parts? If not, can the process of adding new parts each time be automated?
I understand your problem as I can and I'll try to answer from my experience in Dart.
As I got it you are trying to keep all (or a lot) project files as parts of one library. It's wrong conception. In Dart library is not big thing. Here is few advices how to organize your files.
Don't try to keep a lot of files as parts of one library. Rather keep each file as separated library.
You can keep few classes in one file. But be sure classes works together for one idea.
You can split one library in few part files only if it has a lot of classes or one class is too big. But be sure all this files should be together in one library.
If you don't know how to combine classes in libraries keep each class in separated library. After days you will get understanding which classes plays together.
If one class from one library has usages in not only library but in other libraries - make it separated library.
Place parts in same folder with main library file.
If library has parts put all library files into separated folder.
Use relative links to parts.
Always use absolute links to other libraries. It will help you to make refactoring in future.
For example, this is a project like TODO list application. So, we have view class:
// This is a view html component class. //
library todolist.list_viewer;
import 'package:todolist/task.dart'; // model class
class ListViewer extends HtmlElement {
// it showes list of tasks
}
class TodoRenderer extends HtmlElement {
// this is a renderer for one todo item
}
And this is a model class:
// This is a todo model task //
library todolist.task;
class TodoProvider {
List<TodoItem> todos;
String addItem(TodoItem new Todo) {
//...
}
}
class TodoItem {
String Author;
DateTime date;
}
It looks simple now, divided into separated libraries. But if we want to add RecId class to keep todo database id it becomes too complex. We should split it in two libraries: todo_provider and todo_model and put RecId class into last one. Now it's good again.
If we wanna add one more model: a User, so each todo item may have executor or author. We can't just put it todo_model. Now we should to combine TodoItem and User classes into model library. So we just rename todo_model class to model and add User class into it.
Or instead last action we can make user_model library to keep User class. And as User model has recId property too we should extract RecId class into separated library.
It's all depends on how our classes big and complex.
I understand that:
part/part of is used to break a library into several parts (scripts). You have visibility to public and private members.
import is to "call/use" another library from your library. You have only visibility to public members of the imported library.
WebStorm can't infer you want a script to be part of your library.
I tried to include a class called 'name' and I got an error:
Swift Compiler Error: Use of unresolved identifier 'name'
The class exists and doesn't contain any compile errors.
There could be a few possible issues.
One of the classes has a Testing target and other one doesn't. You have to even include all of your classes in the testing target or none of them.
If it's Objective C class, check that the class is in ObjectiveC bridging header file.
If it's NSManagedObject subclass. Add #objc(className) before the class declaration.
If it's part of a different framework, make sure that the class or function is public
I had this one too. You will probably find that your first class is included in your testing module and that "name" isn't. Simply, if you include a class in testing, then every class that it references has to be in testing.
I had this problem too. I was trying to reference Class 1 within the code in Class 2. My problem was that Class 2 had target memberships in A and B, and Class 1 only had Target Memberships in Class A.
You can fix this by opening the Utilities Tab (farthest right button on the top bar of the Xcode window), and make sure that the same boxes are checked for both classes in the Target Membership subsection.
Got problem solved by
Target -> Build Phases -> Compile Sources -> Adding the class file
Add one more to the list.
If it is part of another framework, make certain that the "Build Active Architecture Only" settings are the same.
There are some enum types in my iOS objective-C app that are used in different classes, for them I guess its fine to put them in a constants.h file, but what about others that are not necessarily used in multiple classes? would it be considered a bad practice?
While sapi's answer isn't wrong, here's what I have a tendency to do...
A group of constants that are used across multiple files will go into a file. Let's say all my Foo constants go in FooConstants.h.
Now another group, say the Bar constants, they'll all go in BarConstants.h.
These files will have constants, enums, and protocol definitions in them.
In the files that need the Foo constants only, I'll import FooConstants.h.
In the files that need the Bar constants only, I'll import BarConstants.h.
And depending on the project, I may have just 1 of these files, or I may have 10 or more. Usually I'll have a file called SegueNames.h, where all of my storyboard segue identifiers are created as constants and put in this file so I never misspell a segue name. I'll also usually have DefaultsKeys.h, where I keep the keys to anything I'm putting in NSUserDefaults.
And then I started realizing every now and then, I might have a file that uses 6 of these constants files, so I started creating Constants.h.
Constants.h has nothing in it except importing all the other constants files. This cleans up the top of some of my files.
But at the end of the day, I do still keep the constants organized in their own files with some sort of grouping putting common constants together. And as sapi points out, any constant that is used only in a single file should be defined within that file.
Yes, it is bad practice.
If you place all of your constants, including enums, into the one file, then importing that file becomes necessary whenever you want to reuse part of your code.
A better practice would be to group your constants by function (at whatever level is appropriate for your app), and to include constants used only in a single class in the class file itself or, if you must, in a separate header.
It depends on the context. How well organized are your classes? If it's a bit of a mess, it doesn't hurt to start with an Errors.h/m file where you define your error codes as enums in the .h file and your error domains as NSStrings in the .m file (with corresponding extern NSString * const in your .h file).
If your organization is a bit better, then you've divided your classes into modules and each module has an entry point, where you should be defining these things. The result doesn't change though: Error header for enum values and extern declarations, error implementation for extern assignments.
All my error declaration files look like this:
// ErrorFile.h
typedef enum {
ModuleErrorOne = 1,
ModuleErrorTwo,
ModuleErrorThree
} ModuleError;
extern NSString * const ModuleErrorDomain;
// ErrorFile.m
NSString * const ModuleErrorDomain = #"ModuleErrorDomain";
You can stick it in your pre-compiled header for a compilation speed boost.
EDIT: Thanks for the comments nhgrif and GraniteRobert, they've improved my answer.
I have got a class in Objective-C:
#interface Category : NSObject
{
// ...
}
All was good and I've used this class with no problems in 3 different projects.
Once I decided to create test target for one project. Then the strange thing occurred: compiler refuses to compile with this error:
Redefenition of 'Category' as a different kind of symbol
it also pointed to runtime.h with
typedef struct objc_category *Category;
Well, it is reasonable.
However, I can not understand why it allowed me to use this class before and what is the difference between original target and test target. I've checked Deploy target, iOS SDK, all macros, header paths, #import <objc/runtime.h> and classes in both targets - these are almost the same.
I don't want to refactor this class's name because of using it in multiples projects, so what can be the reason for such a behavior?
You should refactor the name of this class anyway, to avoid such collisions in the future.
And it will be less painful to do it sooner rather than later.
It is probable that you import a file that imports <objc/runtime.h> at some point.
As the matter of what the difference is between the test target and the regular target, on Xcode testing works by injecting the symbols onto a bundle. There might be some differences in code stripping and symbols visibility that could explain this kind of error.
No wonder Apple recommends to prefix classes with 2 or 3 letters
Although you should do what #Olotiar says in his answer, there's a quick fix.
Go to your project Build Settings, search for "Enable Modules (C and Objective-C)" and set the value to NO.