How to make same iOS Swift App with only some differences in the code and assets with scalable and clean mode
Create a single project with multiple targets. Each target would have a different info.plist and whatever other changes you need, but shared source.
Framework entry point in Swift
(My TL;DR is at the bottom.)
As already stated, there is no entry point like you are thinking. Instead, you should do this:
In your Framework target (I'll assume the framework is named MyFramework):
Add files, classes, properties, subclassed controls, etc. and mark things as public
, private
, internal
, and fileprivate
. (See the access level section in the Apple documentation.)
For instance:
public class MyClass1 {
public var property1 = ""
private var property2 = ""
public func myFunc() -> String {
print("Hello World!")
}
}
private class MyClass2 {
var property1 = ""
var property2 = ""
func myFunc() -> String {
return "Hello World!"
}
}
In your app target (again, assuming your framework is named myFramework):
include MyFramework
class ViewController: UIViewController {
func tryThis() {
let myClass1 = myClass1()
print(myClass1.myFunc()) // prints "Hello World!"
// the line below will generate a build error
// as myClass2 is marks private
let myClass2 = myClass2()
}
}
TL;DR
Learn your Access Levels, add code into your Framework target, and import
the framework into your app.
iOS import custom framework into project
I didn't solve my issue but I've managed to do something similar.
Instead of creating a Cocoa touch framework I created a Cocoa Touch static library and it worked.
- I build my library,
- I get the products (*.h and .a files), copy them in my project,
- I add the lib.a in Linked Frameworks and libraries,
- I create the Bridging Header and #import my "customlib.h",
- Magic ! It works I can use my class.
How to build a custom Swift framework and how is it related to the SPM?
Currently, Swift Package Manager (SPM) and Xcode Frameworks follow different paths. For Linux, you have to follow the SPM path since the only way to compile a Linux swift application is to use SPM. For macOS command line apps, you can follow the SPM path as well. For iOS apps and macOS UI apps, you have to follow the Xcode Frameworks path.
For the SPM path, you make the project of your framework SPM-enabled: add Package.swift
file and set the file layout of your project according to SPM conventions. The project also has to be a git repository. Then the git repository of your project can be specified as a dependency to other SPM-enabled frameworks/applications. Each SPM-enabled project can be converted to an Xcode project any time by using swift package generate-xcodeproj
command.
The Xcode Frameworks path is the standard, pre-SPM way of working with frameworks with Xcode, which is described elsewhere. You create an Xcode Project that will define your framework.
So, if you want your framework to be used both in SPM-enabled projects for Linux and macOS command line apps, and in Xcode-enabled projects for iOS and macOS UI apps, you have to follow the dual path. You make your project SPM-enabled and add an Xcode Project which will define your framework. You will have to maintain your project information twice - in Package.swift
file and in the Xcode Project.
Real-time data changes using two-way binding for each instance in SwiftUI
One approach we had conversation about is you can store your data in an EnviornmentObject
and create an object to store it's properties and the view will take a binding and the view's job is to update the object's properties. In your case this view is ChildView
. So because I know your code from previous posts I will include it here.
I have renamed Child
to ChildView
because really it's job is just to show the circle and update it, but additionally I have created a model called Child
which is what we want to present.
Child.swift
import SwiftUI
struct Child: Identifiable {
let id: UUID = UUID()
var location: CGSize
init(location: CGSize = .zero) {
self.location = location
}
}
It's a very simple declaration, we have specified a location
and an ID
to be able to identify it.
then I changed ChildView
to the following
ChildView.swift
struct ChildView: View {
@Binding var child: Child
var onDragged = {}
@State private var isInitalDrag = true
@State private var isOnce = true
@State private var currentPosition: CGSize = .zero
@State private var newPosition: CGSize = .zero
var body: some View {
Circle()
.frame(width: 50, height: 50)
.foregroundColor(.blue)
.offset(self.currentPosition)
.gesture(
DragGesture()
.onChanged { value in
if self.isInitalDrag && self.isOnce {
self.onDragged()
self.isOnce = false
}
self.currentPosition = CGSize(
width: CGFloat(value.translation.width + self.newPosition.width),
height: CGFloat(value.translation.height + self.newPosition.height)
)
self.child.location = self.currentPosition
}
.onEnded { value in
self.newPosition = self.currentPosition
self.isOnce = true
self.isInitalDrag = false
}
)
.onAppear {
// Pay attention whenever the circle view appears we update it's currentPosition and newPosition to be the child's location
self.currentPosition = self.child.location
self.newPosition = self.child.location
}
}
func onDragged(_ callaback: @escaping () -> ()) -> some View {
ChildView(child: self.$child, onDragged: callaback)
}
}
So as you can see I have removed some of the previous code as it will be irrelevant. The goal is that each ChildView
will present 1 Child
object for us; hence, our ChildView
includes a binding property called child
. I have also changed the rest of our properties to be private
as there is really 0 reason to share these states with different views.
Also notice whenever there is a drag I change the child
object's location property. This is very important because now whenever we reference this child in any view it will have the same location.
Additionally, note I have removed @EnvironmentObject
from the ChildView
as it really doesn't need to change our environment
instead it only announces that it is being dragged and whichever view is calling it can do different actions when dragging, maybe one wants to create new child but the other wants to change a color. So it's best practice to separate these for scalability. think of ChildView
as a component than a real full blown view.
I then changed our EnvironmentObject
to be as follows
AppState.swift
(I think you called it DataBridge
I was lazy to change the name :D)
class AppState : ObservableObject {
@Published var childInstances: [Child] = []
init() {
self.createNewChild()
}
func createNewChild() {
let child = Child()
self.childInstances.append(child)
}
}
It's much simpler than the previous code, as it really has only an array of Child
and pay very close attention, its an array of the object Child
not the view ChildView
like you had before! it also includes a function to create a new child object whenever it's called.
Finally here is your ContentView
ContentView.swift
struct ContentView: View {
@EnvironmentObject var appState: AppState
var body: some View {
ZStack {
ForEach(self.appState.childInstances.enumerated().map({$0}), id:\.element.id) { index, child in
ChildView(child: self.$appState.childInstances[index])
.onDragged {
self.appState.createNewChild()
}
}
VStack {
ForEach(self.appState.childInstances, id: \.self.id) { child in
Text("y: \(child.location.height) : x: \(child.location.width)")
}
}
.offset(y: -250)
}
}
}
In this file all we are doing is enumerating through our child instances (again the objects not the views) and for each child we are creating a new view and passing it the child
object as a Binding
so whenever the ChildView
makes changes it will actually change the original Child
object. Also note that I handle .onDragged
in this view since it's a real view that controls the app and not a partial component that describes an object.
I apologize if it's long but I tried to explain everything so it won't be confusing. This is a scalable approach because now your Child
can have multiple properties, maybe each child can have it's own random color instead of blue? that can be now possible by creating a new property in Child
model called color
and then you reference it in your ChildView
.
This architecture now also allows you to for example in a different view lets call it ChangeColorView.swift
to reference any child from our AppState.childInstances
and then change its color when the location = a different circle's location then set their colors to be the same, etc.... really the sky is the limit. This is known as OOP (Object Oriented Programming).
Let me know if I can help any further.
Best architectural approaches for building iOS networking applications (REST clients)
I want to understand basic, abstract and correct architectural approach for networking applications in iOS
There is no "the best", or "the most correct" approach for building an application architecture. It is a very creative job. You should always choose the most straightforward and extensible architecture, which will be clear for any developer, who begin to work on your project or for other developers in your team, but I agree, that there can be a "good" and a "bad" architecture.
You said:
collect the most interesting approaches from experienced iOS developers
I don't think that my approach is the most interesting or correct, but I've used it in several projects and satisfied with it. It is a hybrid approach of the ones you have mentioned above, and also with improvements from my own research efforts. I'm interesting in the problems of building approaches, which combine several well-known patterns and idioms. I think a lot of Fowler's enterprise patterns can be successfully applied to the mobile applications. Here is a list of the most interesting ones, which we can apply for creating an iOS application architecture (in my opinion): Service Layer, Unit Of Work, Remote Facade, Data Transfer Object, Gateway, Layer Supertype, Special Case, Domain Model. You should always correctly design a model layer and always don't forget about the persistence (it can significantly increase your app's performance). You can use Core Data
for this. But you should not forget, that Core Data
is not an ORM or a database, but an object graph manager with persistence as a good option of it. So, very often Core Data
can be too heavy for your needs and you can look at new solutions such as Realm and Couchbase Lite, or build your own lightweight object mapping/persistence layer, based on raw SQLite or LevelDB. Also I advice you to familiarize yourself with the Domain Driven Design and CQRS.
At first, I think, we should create another layer for networking, because we don't want fat controllers or heavy, overwhelmed models. I don't believe in those fat model, skinny controller
things. But I do believe in skinny everything
approach, because no class should be fat, ever. All networking can be generally abstracted as business logic, consequently we should have another layer, where we can put it. Service Layer is what we need:
It encapsulates the application's business logic, controlling transactions and coordinating responses in the implementation of its operations.
In our MVC
realm Service Layer
is something like a mediator between domain model and controllers. There is a rather similar variation of this approach called MVCS where a Store
is actually our Service
layer. Store
vends model instances and handles the networking, caching etc. I want to mention that you should not write all your networking and business logic in your service layer. This also can be considered as a bad design. For more info look at the Anemic and Rich domain models. Some service methods and business logic can be handled in the model, so it will be a "rich" (with behaviour) model.
I always extensively use two libraries: AFNetworking 2.0 and ReactiveCocoa. I think it is a must have for any modern application that interacts with the network and web-services or contains complex UI logic.
ARCHITECTURE
At first I create a general APIClient
class, which is a subclass of AFHTTPSessionManager. This is a workhorse of all networking in the application: all service classes delegate actual REST requests to it. It contains all the customizations of HTTP client, which I need in the particular application: SSL pinning, error processing and creating straightforward NSError
objects with detailed failure reasons and descriptions of all API
and connection errors (in such case controller will be able to show correct messages for the user), setting request and response serializers, http headers and other network-related stuff. Then I logically divide all the API requests into subservices or, more correctly, microservices: UserSerivces
, CommonServices
, SecurityServices
, FriendsServices
and so on, accordingly to business logic they implement. Each of these microservices is a separate class. They, together, form a Service Layer
. These classes contain methods for each API request, process domain models and always returns a RACSignal
with the parsed response model or NSError
to the caller.
I want to mention that if you have complex model serialisation logic - then create another layer for it: something like Data Mapper but more general e.g. JSON/XML -> Model mapper. If you have cache: then create it as a separate layer/service too (you shouldn't mix business logic with caching). Why? Because correct caching layer can be quite complex with its own gotchas. People implement complex logic to get valid, predictable caching like e.g. monoidal caching with projections based on profunctors. You can read about this beautiful library called Carlos to understand more. And don't forget that Core Data can really help you with all caching issues and will allow you to write less logic. Also, if you have some logic between NSManagedObjectContext
and server requests models, you can use Repository pattern, which separates the logic that retrieves the data and maps it to the entity model from the business logic that acts on the model. So, I advice to use Repository pattern even when you have a Core Data based architecture. Repository can abstract things, like NSFetchRequest
,NSEntityDescription
, NSPredicate
and so on to plain methods like get
or put
.
After all these actions in the Service layer, caller (view controller) can do some complex asynchronous stuff with the response: signal manipulations, chaining, mapping, etc. with the help of ReactiveCocoa
primitives , or just subscribe to it and show results in the view. I inject with the Dependency Injection in all these service classes my APIClient
, which will translate a particular service call into corresponding GET
, POST
, PUT
, DELETE
, etc. request to the REST endpoint. In this case APIClient
is passed implicitly to all controllers, you can make this explicit with a parametrised over APIClient
service classes. This can make sense if you want to use different customisations of the APIClient
for particular service classes, but if you ,for some reasons, don't want extra copies or you are sure that you always will use one particular instance (without customisations) of the APIClient
- make it a singleton, but DON'T, please DON'T make service classes as singletons.
Then each view controller again with the DI injects the service class it needs, calls appropriate service methods and composes their results with the UI logic. For dependency injection I like to use BloodMagic or a more powerful framework Typhoon. I never use singletons, God APIManagerWhatever
class or other wrong stuff. Because if you call your class WhateverManager
, this indicates than you don't know its purpose and it is a bad design choice. Singletons is also an anti-pattern, and in most cases (except rare ones) is a wrong solution. Singleton should be considered only if all three of the following criteria are satisfied:
- Ownership of the single instance cannot be reasonably assigned;
- Lazy initialization is desirable;
- Global access is not otherwise provided for.
In our case ownership of the single instance is not an issue and also we don't need global access after we divided our god manager into services, because now only one or several dedicated controllers need a particular service (e.g. UserProfile
controller needs UserServices
and so on).
We should always respect S
principle in SOLID and use separation of concerns, so don't put all your service methods and networks calls in one class, because it's crazy, especially if you develop a large enterprise application. That's why we should consider dependency injection and services approach. I consider this approach as modern and post-OO. In this case we split our application into two parts: control logic (controllers and events) and parameters.
One kind of parameters would be ordinary “data” parameters. That’s what we pass around functions, manipulate, modify, persist, etc. These are entities, aggregates, collections, case classes. The other kind would be “service” parameters. These are classes which encapsulate business logic, allow communicating with external systems, provide data access.
Here is a general workflow of my architecture by example. Let's suppose we have a FriendsViewController
, which displays list of user's friends and we have an option to remove from friends. I create a method in my FriendsServices
class called:
- (RACSignal *)removeFriend:(Friend * const)friend
where Friend
is a model/domain object (or it can be just a User
object if they have similar attributes). Underhood this method parses Friend
to NSDictionary
of JSON parameters friend_id
, name
, surname
, friend_request_id
and so on. I always use Mantle library for this kind of boilerplate and for my model layer (parsing back and forward, managing nested object hierarchies in JSON and so on). After parsing it calls APIClient
DELETE
method to make an actual REST request and returns Response
in RACSignal
to the caller (FriendsViewController
in our case) to display appropriate message for the user or whatever.
If our application is a very big one, we have to separate our logic even clearer. E.g. it is not *always* good to mix `Repository` or model logic with `Service` one. When I described my approach I had said that `removeFriend` method should be in the `Service` layer, but if we will be more pedantic we can notice that it better belongs to `Repository`. Let's remember what Repository is. Eric Evans gave it a precise description in his book [DDD]:
A Repository represents all objects of a certain type as a conceptual set. It acts like a collection, except with more elaborate querying capability.
So, a Repository
is essentially a facade that uses Collection style semantics (Add, Update, Remove) to supply access to data/objects. That's why when you have something like: getFriendsList
, getUserGroups
, removeFriend
you can place it in the Repository
, because collection-like semantics is pretty clear here. And code like:
- (RACSignal *)approveFriendRequest:(FriendRequest * const)request;
is definitely a business logic, because it is beyond basic CRUD
operations and connect two domain objects (Friend
and Request
), that's why it should be placed in the Service
layer. Also I want to notice: don't create unnecessary abstractions. Use all these approaches wisely. Because if you will overwhelm your application with abstractions, this will increase its accidental complexity, and complexity causes more problems in software systems than anything else
I describe you an "old" Objective-C example but this approach can be very easy adapted for Swift language with a lot more improvements, because it has more useful features and functional sugar. I highly recommend to use this library: Moya. It allows you to create a more elegant APIClient
layer (our workhorse as you remember). Now our APIClient
provider will be a value type (enum) with extensions conforming to protocols and leveraging destructuring pattern matching. Swift enums + pattern matching allows us to create algebraic data types as in classic functional programming. Our microservices will use this improved APIClient
provider as in usual Objective-C approach. For model layer instead of Mantle
you can use ObjectMapper library or I like to use more elegant and functional Argo library.
So, I described my general architectural approach, which can be adapted for any application, I think. There can be a lot more improvements, of course. I advice you to learn functional programming, because you can benefit from it a lot, but don't go too far with it too. Eliminating excessive, shared, global mutable state, creating an immutable domain model or creating pure functions without external side-effects is, generally, a good practice, and new Swift
language encourages this. But always remember, that overloading your code with heavy pure functional patterns, category-theoretical approaches is a bad idea, because other developers will read and support your code, and they can be frustrated or scary of the prismatic profunctors
and such kind of stuff in your immutable model. The same thing with the ReactiveCocoa
: don't RACify
your code too much, because it can become unreadable really fast, especially for newbies. Use it when it can really simplify your goals and logic.
So, read a lot, mix, experiment, and try to pick up the best from different architectural approaches. It is the best advice I can give you.
Related Topics
Perform Segue After Login Successful in Storyboards
How to Change the Speed of Video Playback
Receive Accelerometer Updates in Background Using Coremotion Framework
How to Make Offline Database for My App
What's the State of Developing iOS Apps in Linux
iOS Swift: Could Not Cast Value Type '_Nscfnumber' to 'Nsstring'
Not Receiving Push Notifications from Firebase
iOS Timed Background Processing
Error in Xcode 6 - View Controller Does Not Have an Outlet Named (Subview)
Flutter on iOS: Fatal Error: Module 'Cloud_Firestore' Not Found
How Reliable Is Kvo with Uikit
Uilabel with Two Different Color Text
How to Set an Nscalendarunitminute Repeatinterval on iOS 10 Usernotifications
Uiscrollview Pauses Nstimer While Scrolling
iOS - Corelocation and Geofencing While App Is Closed