IOS martial arts esoteric article summary
Writing in the front
The topic of componentization naturally came to my mind recently when I was thinking about how to maintain efficient team output in the context of team expansion and increasing number of projects. The following is a personal sorting and thinking.
A possible secret Demo for this section
One, componentization
When we talk about componentization, the first thing that comes to mind is decoupling and modularization. In fact, componentalization is a way to decouple modules by separating them and layering them and developing communication modes between modules, which is mainly used in team development.
Why componentization?
There are four main reasons:
- 1. Decouple modules
- 2. Module reuse
- 3. Improve the efficiency of teamwork development
- 4. Easy unit testing
When the project is getting bigger and bigger due to various requirements, if the various modules are calling each other, that is, there is a me in each other, there is a you in each other, this situation will result in high coupling. Once we need to change a piece of code, it will affect the whole project, making it difficult to maintain. Its problems are mainly reflected in the following aspects:
- 1. When modifying a function, you need to modify the code of other modules because the module is referenced in other modules. Can be understood as
High coupling makes code modification difficult
- 2. The external interface of the module is not clear, and even exposes the private interface that should not be exposed, which takes time and effort to modify. Can be understood as
Interface confusion caused by unfixed interfaces
- 3. The consequence of highly coupled code is that it affects the development of the rest of the team,
Create code conflicts
- 4. When modules need to be reused to other projects,
It's hard to separate
- 5. The coupling taboo between modules leads to the confusion of interfaces and dependencies.
Unable to unit test
So in order to solve the above problems, we need to adopt a more standardized way to reduce the coupling degree between modules, and then the componentization came into being, componentization can also be understood as modularization.
Description of application of componentization
The benefits of componentization are mentioned above, but because componentization is also costly, requiring time to design interfaces, separate code, etc., not all projects need to be componentized. Your project does not need to be componentized if it has three or more of the following characteristics:
- 1.
Smaller projects
, simple interaction between modules, less coupling - 2. The project
Not by multiple external modules
A reference is just a single little module - 3. The module
Don't need
Reuse, coderarely
Has been modified - 4. Team
small
- 5.
No unit tests need to be written
If your project has more than three of the following characteristics, you must consider componentization:
- 1. The module
Complex logic
, between multiple modulesFrequent cross-references
- 2. The project
It's getting bigger
.Modify the code
Change ofMore and more difficult
(If you change one part of the code, you need to change several other places at the same time.) - 3. The team
The number of more
, submitted code often and other membersconflict
- 4. Project
The compilation takes a long time
- 5. The module
Unit testing
Often due toOther module modifications
whilefailure
Eight indicators of componentization
How to judge whether the componentization of a project is thorough or excellent after componentization can be judged from the following aspects:
- 1.
There is no coupling between modules
, internal module modificationDoes not affect the
Other modules - 2.
Modules can be compiled separately
- 3. Between modules
Clear data transfer
- 4. Modules can
At any time
By another module that provides the same functionalityreplace
- 5. The external interface of the module is clear and easy to maintain
- 6. When
Module interface change
When theExternal code for a module
It can beEfficient refactoring
- 7. As far as possible
With the least amount
Modifications and code to make existing projects modular - Support 8.
OC and Swift
And remix
The first four are used to measure whether a module is truly decoupled, while the last four are used to measure ease of use in project practice
Principle of componentization
Generally, a project is divided into three levels:The business layer
.General layer
.Base layer
, as shown in the figure below:
During componentization, the following points need to be noted:
- Only 1.
Upper to lower
Depend on, cannotLower to upper
Because the lower layer is an abstraction from the upper layer - 2. The project
Common code resources sink
- 3. The horizontal dependence should be as little as possible, and it is best to sink to the general module or basic module
2. Componentization scheme
Currently, there are two common componentization schemes:
- 1,
local
Componentization: mainly through theCreate library in project
, the use ofcocoapods
theworkspec
forLocal management
.Don't need
Upload the projectgit
“, but directly in the projectIn the framework's way
The calling - 2.
cocoapods
Componentization: mainly usecocoapods
To carry outRemote management of modules
.Need to be
Upload the projectgit
(The componentized modules here are divided intoThe public library
andPrivate libraries
forThe company is concerned
.It is usually a private library
)
Local componentization
1. Create a main project
- Create the main project first
- integration
cocopods
To perform local management and execute instructionspod init
- The editor
podfile
And performpod install
2. Create a component
Create your own module:
The main project
: Mainly implements the surface business codeBase
Base class encapsulationTools
: tools (strings, colors, fonts, etc.)Service
: service layer, which encapsulates business tool classes, such as network layer services and persistence servicesPods
: Third-party dependencies
The relationship between each module is as follows
Let’s do a simple module creation, using Service as an example:
- 1. Choose
new -> project -> iOS -> Framework
, create a new oneThe module
- 2. Choose the right one
Group
和WorkSpace
One thing to note here: createdlibrary
The bestPut it in the main project root directory
Under, otherwise follow-uppodfile
performpod install
Will report an error) - 3. To be created
library
theBuild Settings -> Mach-O Type
Change to a static libraryStatic Library
3. The main project calls library
inTCJService
Create a new file and add the following code
-
in
Build Phases -> Headers -> Public
Add the newly created file topublic
So that theOnly the main project can access the file
-
In the main project, select
target -> Linked Binary With Libraries
addTCJService
, you only need tobuild
The main project,library
It can be automatically linked -
Main project call: First
import TCJService
And then use
It is important to note that the child libraries call each other in the same way that the main project calls library by adding dependencies and exposing headers.
4. Use Cocoapods to manage third-party dependencies
So let’s say we need to be inTCJService
Encapsulate network layer code, need to use the tripartite libraryAlamofire
In thepodfile
“, modify as follows
At this point, a local componentized module is configured
Cocoapods componentization
In addition to local componentization, it can also be usedcocoapods
, its principle is shown in the figure belowAgain, let’s take local componentization as an example
Create a private repository — Create a private Spec Repo
Private libraries use private Spec Repo of course, you can use the official Repo of course, but if you just want to add your own Pods, use the private Repo instead. Open: ~/.cocoapods/repos. You’ll see a master folder that is cocoapods’ official Spec Repo.
- in
github
Create aTCJDemoSpecs
Warehouse comes as privateRepo
To do this, go to Github and click “+” in the upper right corner. Select New Repository. Enter Repository name as TCJDemoSpecs, select the repository type as private, and click Create Repository.
- perform
repo
Command add PrivateRepo
To add the private repository locally~/.cocoapods/repos
directory
pod repo add TCJDemoSpecs https://github.com/Tcj1988/TCJDemoSpecs.git
Copy the code
If successful, go to: ~/.cocoapods/reposTCJDemoSpecs
2, Using Pod Lib Create Create pods project, i.e. componentized project: component library
- To create a
TCJDemoSpecs
The project.CDS to
You want to create a directory for the project and then use the terminal to execute —pod lib create TCJDemoSpecs
- Follow the prompts to choose in turn
IOS, Objc, Yes, None, No,TCJ
-
Go to the module directory and will need the files
copy
toTCJDemoSpecs -> Classes
In the -
cd
toExample folders
performpod install
Will,Classes
Update to Pods
3. Configure the PODS project
Modify the module’s configuration file, namely tcjDemospect.podSpec
- If you need to rely on third-party libraries, you need to configure them
s.dependency
s.dependency 'AFNetworking'# rely on AFNetworkingCopy the code
- If modules need to reference each other, this configuration is also required
s.dependency
In order toTCJBase
For example, a reference is requiredTCJDemoSpecs
1. Modify the podspec file
s.dependency 'TCJDemoSpecs'
//********2, modify podfile
pod 'TCJDemoSpecs', :path => '.. /.. /TCJServices'
Copy the code
- If resources need to be loaded, for example
Image, JSON, bundle
Documents etc.- Create images.xcassets to hold Images of TCJServices components
- In 2.
specs
To configure resource path (must be configured!! Otherwise the resource cannot be read. - 3. Specify the path of the resource file
So how do you get an image? We added it earlierTCJUtils
The class contains a class method:
Example: InExample
engineeringViewController
Direct import fromTCJUtils
Running results:
Similarly, the xiB and JSON files in the module are obtained in the same way
4. Commit to Git
The module submitted to Git should be the Pods project. In the case of TCJDemoSpecs, we just built a private library in Git: TCJDemoSpecs.
- Run the following terminal command
5. Verify the PodSpec file
Execute the terminal command pod spec lint –allow-warnings plus –allow-warnings to remove warnings
A POD spec is more accurate than a POD lib, which validates only one local repository. A POD spec validates both a local repository and a remote repository
6. Commit to a private repository
Run the following command:Pod repo push [local Spec repo name][PodSpec file path]
pod repo push TCJDemoSpecs TCJDemoSpecs.podspec --allow-warnings
7, use,
-
Create a new project
PodsTest
, in the projectpodfile
add -
perform
pod install
Can be -
Open the project after successful execution:
- in
PodsTest
In theViewController
Things that use components:
Now that we’ve componentized Cocoapods, let’s look at the communication between the componentization.
3. Componentized communication scheme
At present, the mainstream mainly has the following three ways:
- 1.
URL
routing - 2.
target-action
- 3.
protocol
matching
Protocol trial programming
Use protocol definition specifications at the compile level to implement in different places to achieve distributed management and maintenance of components. This approach also follows the principle of dependency inversion and is a good object-oriented programming practice.
But the plan is also obvious:
- Due to the
Protocol programming lacks a unified scheduling layer
, resulting inDifficult to centralize
, especiallyProjects get bigger
,Teams get bigger
In the case of,Architectural governance becomes more and more important
Protocol programming interface
Define patternToo specification
, thus making the architectureNot flexible enough
.When it is necessary to introduce aNew design patterns to develop
We will find outIt's hard to fit into the current architecture
.Lack of architectural uniformity
.
middle-borns
It uses the intermediary unified management to control the call relationship between components in the entire life cycle of the App. At the same time, iOS also needs to maintain consistency in the design of component interfaces, so as to facilitate the unified call of intermediaries.
The split components depend on the intermediary, but there is no interdependence between the groups. Communication between components is easier to manage because all other components depend on this intermediary, and communication between components is uniformly scheduled through the intermediary. New design patterns can also be easily added on top of the intermediate, making the architecture easier to extend
A good architecture is robust and flexible. Easy management of an intermediate architecture leads to a more stable architecture, and easy scalability leads to flexibility.
URL routing
This is also the communication scheme used by many iOS projects. It is based on route matching, or according to the naming convention, the Runtime method is used for dynamic invocation. The IDEA of URL routing adopts the middle-of-the-road mode.
The advantage of these dynamic schemes is that they are simple to implement, while the disadvantage is that they need to maintain string tables, or rely on naming conventions, and cannot expose all problems at compile time, requiring errors to be discovered at run time.
Advantages and disadvantages of URL routing
“Advantages”
Extremely dynamic
Suitable foroften
Carrying out operational activitiesapp
For example, e-commerceConvenient unified management
Multi-platform routing rulesEasy to adapt to URL schemes
“Defect”
The parameter transmission mode is limited
.And you can't use compile-time for parameter type checking
(All arguments are converted by string)This applies only to interface modules
.Does not apply to generic modules
The parameter format is not clear
It’s flexibledictionary
You also need a place to view the parameter formatDo not support the storyboard
Depends on string hardcoding
Difficult to manage,Mushroom Street specializes in that
I did a background management partThere is no guarantee that all modules used will exist
The decoupling capability is limited
.URL
The “register”, “implement”, “use” must be usedSame string rules
, changes made by either party will invalidate the code elsewhere, andRefactoring is difficult
The URL routing mode is mainly MGJRouter represented by Mogujie
MGJRouter
The realization idea is as follows:
The App is started
Instantiate the component modules, and then theseThe component registers the URL with MGJRouter
And, in some cases,No instantiation is required
To makeUse the Class registration
.- when
Component A
You need to callThe component B
whenModuleManager pass URL
, parameter followingURL
In order toGET
Way to pass, similaropenURL
And then byModuleManager
Responsible for schedulingThe component B
And finally finish the task.
In addition to the MGJRouter above, there are the following tripartite frameworks
- routable-ios
- JLRoutes
- HHRouter
target-action
This solution is based on the OC runtime/category feature to dynamically fetch modules, such as NSClassFromString to fetch classes and create instances, and NSInvocation to dynamically invoke methods via performSelector+NSInvocation
This method is mainly represented by CTMediator of Casatwy, and its realization idea is as follows:
- 1.
Use classification
forAdd a new interface to the route
In theinterface
throughString gets the corresponding class
- 2. By
runtime
Create an instance,Dynamically invoke the instance's methods
//******* 1. Classify new interfaces
public extension CTMediator{
@objc func A_showHome()->UIViewController? { let params = [ kCTMediatorParamsKeySwiftTargetModuleName:"TCJLHome"
]
if let vc = self.performTarget("A", action: "Extension_HomeViewController", params: params, shouldCacheTarget: false) as? UIViewController{
return vc
}
return nil}}//******* 2, the module provider provides the target-action method (external need to add public keyword).
class Target_A: NSObject {
@objc public func Action_Extension_HomeViewController(_ params: [String: Any])->UIViewController{
let home = HomeViewController()
return home
}
}
//******* 3
if let vc = CTMediator.sharedInstance().A_showHome() {
self.navigationController? .pushViewController(vc, animated:true)}Copy the code
The reference relationships between modules are as follows:
[Advantages] :
- using
classification
canThe statement interface
,Compile the inspection
- implementation
lightweight
【 Disadvantages 】:
- Need to be in
mediator
andtarget
In theTo add
eachinterface
.The code is cumbersome when modularizing
- in
category
Still want toHard coding of strings is introduced
For internal useDictionary and reference
And, to some extent, there are also alpha and betaURL routing same problem
There is no guarantee that the module being used exists
.Target after modification
, the user can only use theErrors can only be found at runtime
- create
Too much target
Class, which leads to theTarget class flood
CTMediator source code analysis
-
CTMediator
useURL Routing processing
: This method is mainly for remoteAPP
Of each other, throughopenURL
implementationAPP
Jump between, passURL
Data transfer -
CTMediator
Using theRuntime decoupling
, the decoupling core method is shown as follows:performTarget:action:params:shouldCacheTarget:
The method is mainly righttargetName
andactionName
Fault-tolerant processing, that is, processing that does not respond to the calling method.- This method encapsulates
safePerformAction:target:params
Method, enter the parametertargetName
Is the object that calls the interface,actionName
Is the name of the method called,params
Are the parameters. - And you can also see in the code that there is only satisfy
Object of the Target_ prefix class
andOnly the Action_ method can be used by CTMediator
At this time, we can see the advantage of the intermediary architecture, which is conducive to unified management and can easily control the rules formulated.
-
Enter the
safePerformAction:target:params:
Realization, mainly throughinvocation
forParameter passing + message forwarding
protocol class
Protocol matching is implemented as follows:
- 1. The
protocol
And the correspondingclass
forDictionary matching
- 2. By using
protocol
To obtainclass
Again,Dynamically creating an instance
A typical tripartite framework of Protocol is Alibaba’s BeeHive.BeeHive uses Spring Service and Apache DSO for reference and adopts AOP+ extended App life cycle API form. The business functions and basic function modules are used to solve complex problems in large applications, and the modules are called in the form of Service, and the complex problems are divided, and the services are modular in the way of AOP.
BeeHive core ideas
- 1.
Call between modules
fromCall the corresponding module directly
To becomeThe form of calling a Service
.Avoid direct dependencies
- 2.
App
Lifecycle distribution,Split the coupling logically in the AppDelegate
, each module starts withMicro application
The form of exists independently
“Advantages”
- 1.
Using an interface call
To realize parameter transferType safety
- 2. Direct use of modules
protocol
Interface,No need for repeated encapsulation
“Defect”
- 1. Use
Framework to create all objects
, which does not support external parameter transmission - 2. Use
OC
theruntime
Create an object,Do not support the Swift,
- 3. Only
protocol
andclass
The match,More complex creation methods and dependency injection are not supported
- 4.
There is no guarantee that the protocol used must have a corresponding module
Also,You can't tell directly
aprotocol
Whether it can be used to get modules.
In addition to BeeHive, there is Swinject
Register the BeeHive module
In BeeHive, the BHModuleManager is used to manage each module. Only registered modules are managed in the BHModuleManager
BeeHive provides three different call forms: static plist, dynamic registration, annotation.Module and Service are not associated. Each business Module can independently implement the functions of Module or Service.
Annotation registration
This way is mainly throughBeeHiveMod
Macros forAnnotation
tagHere for__attribute
The following points need to be made
- The first parameter
used
:Modify functions
,used
After modification, even if the function is not referenced, theRelease
It will not be optimized. ifWithout this embellishment
, thenSections that are not referenced are removed under the Release environment linker
. - Through the use of
__attribute__((section("name")))
toSpecify which paragraph
.data is used__attribute__((used))
To mark,Prevent linker from optimally removing unused segments
And then inject the module into__DATA
In the.
Now that the Module has been stored in a special section of the Mach-O file, how do I get it?
- Enter the
BHReadConfiguration
The method is mainly throughMach-O
Find the stored data segment,Pull it out and put it in an array
Read the local Pilst file
-
First, you need to set up the path
-
create
plist
File,plist
The file format is also an array containing multiple dictionaries. It’s in the dictionaryTwo key
, one is"moduleLevel"
And the other one is"moduleClass"
Attention.The name of the array at root
call"moduleClasses"
. -
Enter the
loadLocalModules
The method is mainly fromplist
You take the array, and you add the array toBHModuleInfos
In the array
Dynamic registration – load method registration
-
Method registration
Module
Is in theload
Method to registerModule
The class of -
Enter the
registerDynamicModule
implementation
Its underlying or the same as the first way, will eventually go addModuleFromObject: shouldTriggerInitEvent: method
load
Method can also be usedBH_EXPORT_MODULE
Macro instead of
The BH_EXPORT_MODULE macro can pass in a parameter that indicates whether the Module is loaded asynchronously, if YES, or synchronously if NO.
BeeHive module event
BeeHive will provide life cycle events for each module, used to interact with the BeeHive host environment necessary information, aware of the module life cycle changes.
BeeHive
Each module receives events. inBHModuleManager
All events are defined asBHModuleEventType
Enumeration. As shown below, whereTwo events are special
, one isBHMInitEvent
, one isBHMTearDownEvent
.
There are three main types of events:
- 1.
System events
: Mainly refers toApplication
Life cycle events
The general approach isAppDelegate
Inherit fromBHAppDelegate
-
2.
Application events
: Official flow chart, whichmodSetup
,modInit
Can be used forThe code realizes the setup and initialization of each plug-in module
. -
3. Customize events
All of the above events can be calledBHModuleManager
thetriggerEvent:
To deal with.
As can be seen from the above code, removeBHMInitEvent
Initialize the event andBHMTearDownEvent
demolitionModule
Events Except for these two special events, all events are calledhandleModuleEvent:forTarget:withSeletorStr:andCustomParam:
Method, whose internal implementation is primarily traversalmoduleInstances
Instance array, calledperformSelector:withObject:
Method implements the corresponding method callNote: all of thisModule
Must be followedBHModuleProtocol
Otherwise, messages for these events cannot be received
The BeeHive module is called
In BeeHive, the BHServiceManager manages each Protocol. BHServiceManager manages only registered protocols.
There are three ways to register Protocol, which correspond to Module registration.
Annotation registration
//****** 1. Use the BeeHiveService macro to annotate
BeeHiveService(HomeServiceProtocol,BHViewController)
//****** 2
#define BeeHiveService(servicename,impl) \
class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ \""#servicename"\" : \""#impl"\"}";
//****** 3, the converted format is also stored in a special segment
char * kHomeServiceProtocol_service __attribute((used, section("__DATA,""BeehiveServices"""=)))"{ \"""HomeServiceProtocol""\" : \"""BHViewController""\"}";
Copy the code
Read the local PList file
-
First of all, with
Module
Again, you need to set the path first -
Set the plist file
-
But also in the
setContext
When the registeredservices
Protocol registration
It’s basically callingBeeHive
The inside of thecreateService:
completeprotocol
The registration of
CreateService first checks if Protocol is registered. It then takes the corresponding Class from the dictionary and creates a singleton if the shareInstance method is implemented, or an instance if it is not. If singleton is implemented, we can further cache implInstance and serviceStr in the servicesByName dictionary of the BHContext. This can then be passed with context
Enter theserviceImplClass
Implementation, as you can see hereprotocol
andclass
Is through theThe dictionary
The binding,Protocol as a key
.ServiceImp (the name of the class) as value
.
Module & Protocol
A quick summary:
- for
Module
: Array storage - for
Protocol
Through:The dictionary
willProtocol binds to classes
.The key for the protocol
.The value for serviceImp
The name of the class.
Auxiliary Class description
-
BHConfig class: a singleton with an NSMutableDictionary config property that maintains dynamic environment variables as a complement to BHContext
-
BHContext
Class: YesThe singleton
And there are two insideNSMutableDictionary
Properties of, respectivelymodulesByName
和servicesByName
This class is mainly used to store context information. For example, inapplication:didFinishLaunchingWithOptions:
A large amount of context information can be initialized -
BHTimeProfiler class: A Profiler for computing time performance
-
The BHWatchDog class opens a thread and listens for the main thread to block.
Write in the back
Study harmoniously without being impatient. I’m still me, a different color of fireworks.
Refer to the link
BeeHive – an elegant but still improving decoupling framework
BeeHive, an iOS module decouple practice