Transferred from: http://mp.weixin.qq.com/s?__biz=MzA3ODg4MDk0Ng==&mid=2651112856&idx=1&sn= b2c74c62a10b4c9a4e7538d1ad7eb739
The weight of the iOS package is not the primary goal of optimization for the general team, but it is critical for some teams that have already overrun the installation package. and Ali Mobile security have shared the relevant content, the latter is to remove the idea of useless code, interested students can read:
And this is the idea to play the ultimate, welcome to read:
Intro
Bao body, pack thin body, pack thin body, important thing say three times.
A recent iOS app (this article only discusses iOS packages developed with Objective C) has been slimming down and our team's apps have grown larger. To solve this problem, the main idea is to focus on two directions, resources and code. Resources mainly in the picture, methods include the removal of unreferenced images, only a set of pictures (2x or 3x), image stretching, etc. the main ideas of code level include refactoring to eliminate redundancy, selector citation analysis in Linkmap. Besides, is there any other path?
As we all know, there is a call relationship between code. Assuming that the main portal for iOS is-[uiapplication main], the source code of all developers (including the third party libraries) can be divided into two categories: there is a call path, so that the code can be finally called by the main portal (called such code as the final call); there is no call path. So that the code can ultimately not be called by the main portal (called such code is not finally called).
Suppose there is a source-level profiling tool (or compiler) that can assist in parsing the call relationship between the code, making it possible for the analysis to eventually be called code, leaving the code that is not ultimately called.
Is this tool currently available in maturity? The answer is yes, it is the clang plugin. Clang can also assist in discovering duplicate code, in addition to being used for parsing that is not the final calling code.
LLVM and Clang plugins
The LLVM project contains a set of modular, reusable editors and toolchain. Unlike its original name, LLVM is not an acronym, but a project's name. The main sub-projects currently included in LLVM include:
-
LLVM Core: Contains a current source/target device independent optimizer, one set for many mainstream (even some non-mainstream ) The assembly code generation support for the CPU.
-
Clang: A c/c++/objective-c compiler that is dedicated to providing surprisingly fast compilation, extremely useful error and warning information, and provides a tool that can be used to build great source code levels.
The
-
DRAGONEGG:GCC plug-in replaces GCC optimizations and code generators with the appropriate tools for LLVM.
-
Lldb: Excellent local debugger built on LLVM-provided libraries and clang.
-
libc++, libc++ ABI: Standards-compliant, high-performance C + + standard library implementation, and full support for c++11.
-
Compiler-rt: When there is a short native instruction sequence corresponding to __FIXUNSDFDI
and no core IR (intermediate representation) on other target machines , providing support for highly tuned, low-level code generation. Runtime support for multi-platform parallel programming in the
-
Openmp:clang.
-
Vmkit: LLVM-based Java and. NET virtual machine real
-
Polly: Supports the LLVM framework for high-level loop and data localization optimization support.
-
LIBCLC:OPENCL Implementation of the standard library
-
Klee: Symbolic virtual machines based on LLVM compiled infrastructure
-
Safecode: Memory safe Compiler
-
LLD:CLANG/LLVM built-in linker
As the compiler frontend provided by LLVM, Clang can compile the user's source code (C/C++/OBJECTIVE-C) into a language/target device independent IR (intermediate representation) implementation. It provides good plug-in support, allowing users to run extra custom actions at compile time.
Our goal is to use the clang plugin to reduce the packet size. The principle is that, for Target engineering, clang-based plug-in features allow developers to write plug-ins to analyze all source code. During compilation, the plug-in is loaded as a clang parameter and produces various intermediate files. After compiling, it is necessary to write a tool to analyze all the methods that contain the source code (including the user writing, and the third-party library source), and examine which of these methods can eventually be called by the main entrance of the program, and the remainder is the suspected useless code. A simple review that removes unused code and compiles it effectively removes useless code and reduces packet size.
The relevant contents of this article are as follows:
How to write a clang plugin and integrate it into Xcode
How to implement code-level package Slimming
Limitations and customisation
Other
How to write a clang plugin and integrate it into Xcode
Clone clang source and compile the installation
cd /opt
sudo mkdir llvm
sudo chown `whoami` llvm
cd llvm
export LLVM_HOME=`pwd`
git clone -b release_39 [email protected]:llvm-mirror/llvm.git llvm
git clone -b release_39 [email protected]:llvm-mirror/clang.git llvm/tools/clang
git clone -b release_39 [email protected]:llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra
git clone -b release_39 [email protected]:llvm-mirror/compiler-rt.git llvm/projects/compiler-rt
mkdir llvm_build
cd llvm_build
cmake ../llvm -DCMAKE_BUILD_TYPE:STRING=Release
make -j`sysctl -n hw.logicalcpu`
writing clang plugins
To implement a custom Clang plugin (in the case of the C + + API), follow these steps:
Custom inherits from
clang::PluginASTAction
(abstract Syntax tree/ast) front-end action abstract base class based on the consumer abstraction syntax tree
clang::ASTConsumer
(The abstract base class for the client to read the abstract syntax tree),
clang::RecursiveASTVisitor
A base class such as a pre-order or subsequent depth-first search of the entire abstract syntax tree, and access to the base class for each node.
Overload according to your own needs
PluginASTAction::CreateASTConsumer
PluginASTAction::ParseArgs
ASTConsumer::HandleTranslationUnit
RecursiveASTVisitor::VisitDecl
RecursiveASTVisitor::VisitStmt
and other methods to implement custom analytic logic.
Registering plugins
static FrontendPluginRegistry::Add<MyPlugin> X("my-plugin- name", "my-plugin-description");
More Clang plugins: http://clang.llvm.org/docs/ExternalClangExamples.html
Compile Build plugin (dylib)
The
assumes that your clang plug-in source file is Your-clang-plugin-source.cpp, and the plug-in name you want to generate is your-clang-plugin-name.dylib, and you can use the following command (loaded in LLVM, Clang include path, generated related lib, etc.) generated:
Clang-std=c++11-stdlib=libc++-l/opt/local/lib-l/opt/llvm/llvm_build/lib-i/opt/llvm/llvm_build/tools/clang/ Include-i/opt/llvm/llvm_build/include-i/opt/llvm/llvm/tools/clang/include-i/opt/llvm/llvm/include-dynamiclib- Wl,-headerpad_max_install_names-lclang-lclangfrontend-lclangast-lclanganalysis-lclangbasic-lclangcodegen- Lclangdriver-lclangfrontendtool-lclanglex-lclangparse-lclangsema-lclangedit-lclangserialization- Lclangstaticanalyzercheckers-lclangstaticanalyzercore-lclangstaticanalyzerfrontend-lllvmx86codegen- Lllvmx86asmparser-lllvmx86disassembler-lllvmexecutionengine-lllvmasmprinter-lllvmselectiondag- Lllvmx86asmprinter-lllvmx86info-lllvmmcparser-lllvmcodegen-lllvmx86utils-lllvmscalaropts-lllvminstcombine- Lllvmtransformutils-lllvmanalysis-lllvmtarget-lllvmcore-lllvmmc-lllvmsupport-lllvmbitreader-lllvmoption- Lllvmprofiledata-lpthread-lcurses-lz-lstdc++-fpic-fno-common-woverloaded-virtual-wcast-qual- Fno-strict-aliasing-pedantic-wNo-long-long-wall-wno-unused-parameter-wwrite-strings-fno-rtti-fpic Your-clang-plugin-source.cpp-o Your-clang-plugin-name.dylib
Integration with Xcode
Download Xcodehacking.zip:https://raw.githubusercontent.com/kangwang1988/kangwang1988.github.io/master/others/xcodehacking.zip
When compiling with the command line, you can load the plug-in as follows:
clang++ *** -Xclang -load -Xclang path-of-your-plugin.dylib -Xclang -add-plugin -Xclang your-pluginName -Xclang -plugin-arg-your-pluginName -Xclang your-pluginName-param
To use the Clang plugin in Xcode, you need to hack Xcode as follows.
sudo mv HackedClang.xcplugin xcode-select -print-path/../PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins
sudo mv HackedBuildSystem.xcspec xcode-select -print-path/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications
In Xcode->target-build settings->build options->compiler for c/c++/objective-c select Clang LLVM The trunk allows Xcode to compile using the clang generated above. Other command-line parameters can be set through the compilation options in Xcode.
How to implement code-level package Slimming
The code in this article refers to the form in OC, which is -/+[Class method:\*]
typically called the following:
@interface ViewController : UIViewController
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view setBackgroundColor:[UIColor redColor]];
}
@end
It says: -[ViewController viewDidLoad]
called:
-[UIViewController viewDidLoad]
-[ViewController view]
(Grammar sugar)
+[UIColor redColor]
-[UIView setBackgroundColor:]
This invocation relationship can be obtained when the clang iterates through the abstract syntax tree. Because of a nested relationship when the compiler accesses the abstract syntax tree, as in the previous example: when the compiler accesses the class implementation Viewcontroller, the access method implementation is nested, -[ViewController viewDidLoad]
and when the access -[ViewController viewDidLoad]
method is implemented, the access message is sent -[UIViewController viewDidLoad]
(corresponding source code [super viewDidLoad]
) , (corresponding source code), (corresponding source), -[ViewController view]
self.view
(corresponding to the +[UIColor redColor]
[UIColor redColor]
-[UIView setBackgroundColor:]
Source code [self.view setBackgroundColor:[UIColor redColor]]
), etc., so that through the recording of relevant information to understand our focus on the call relationship between the methods.
Data Structure
In order to parse the call relationship, the intermediate data structure used is as follows:
class interface and inheritance System (Clsinterfhierachy)
This data structure records all the interface content that is located on the abstract syntax tree, and the final parsing results are as follows:
In the case of Appdelegate, Interfs represents the interface it provides (note: The getter and setter for its property window is also considered part of the Interf); Isinsrcdir represents whether this class is located in the user directory ( The workspace root directory is passed as a parameter to clang), Protos represents the protocol it adheres to, and superclass represents the parent class of the interface.
This information gets the overloaded functions that the portal is located in VisitDecl(Decl \*decl)
, and the associated decl are:
ObjCInterfaceDecl
(Interface Declaration)
ObjCCategoryDecl
(Categorical Declaration)
ObjCPropertyDecl
(attribute declaration)
ObjCMethodDecl
(Method Declaration)
Interface method Invocation (Clsmethod)
This data structure records all the OC methods that contain the source code, and the final parsing results are as follows:
As an -[AppDelegate application:didFinishLaunchingWithOptions:]
example, callee represents the interface to which it is called (here is the explicit type, for the shape as id\<XXXDelegate\>
described later), filename for this method, range is the scope of the method, and SourceCode is the specific implementation source code for the method.
This information gets the overloaded function where the entry is located VisitDecl(Decl \*decl)
and VisitStmt(Stmt \*stmt)
the associated Decl ObjCMethodDecl
(method declaration), stmt ObjCMessageExpr
(message expression)
There are a number of other scenarios that need to be considered in addition to normal, and the -/+[Class method:\*]
known and supported analysis includes:
Performselector method cluster for NSObject protocol
[obj performSelector:@selector(XXX)]
Not only contains [obj performSelector:]
also contains [obj XXX]
. ( Same below
Gesture/button Event handling Selector
addTarget:action:/initWithTarget:action:/addTarget:action:forControlEvents:
Nsnotificationcener adding notification processing selector
addObserver:selector:name:object:
Uibarbuttonitem adding event handling Selector
initWithImage:style:target:action:/initWithImage:landscapeImagePhone:style:target:action:/initWithTitle:style:target:action:/initWithBarButtonSystemItem:target:action:
Timer
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:/timerWithTimeInterval:target:selector:userInfo:repeats:/initWithFireDate:interval:target:selector:userInfo:repeats:
Nsthread
detachNewThreadSelector:toTarget:withObject:/initWithTarget:selector:object:
Cadisplaylink
displayLinkWithTarget:selector:
KVO mechanism
addObserver:forKeyPath:options:context:
, different from the other to deal with the method itself calls and corresponding Target:selector calls, here Kvo Addobserver is implied observeValueForKeyPath:ofObject:change:context:
.
Ibaction mechanism
As in the Xib/storyboard-based Viewcontroller -(IBAction)onBtnPressed:(id)sender
method, it is assumed that +[ViewController的 alloc]
+[ViewController的 onBtnPressed:]
the invocation relationship is implicit.
[XXX new]
Contains +[XXX alloc]
and -[XXX init]
.
Protocol interface and inheritance system (Protointerfhierachy)
This data structure records all the protocol content that is located on the abstract syntax tree, and the final parsing results are as follows:
Each of these fields is defined with Clsinterfhierachy.
This information gets the overloaded functions that the portal is located in VisitDecl(Decl \*decl)
, and the associated decl are:
ObjCProtocolDecl
(Agreement statement)
ObjCPropertyDecl
(attribute declaration)
ObjCMethodDecl
(Method Declaration)
Invocation of the Protocol method (Protointerfcall)
This data structure records all the -[ViewController func1]
forms that have been called -[id\<ViewControllerDelegate\> viewController:execFunc:]
, and the final result is as follows:
This information gets the overloaded function where the entry is located VisitStmt(Stmt \*stmt)
, and the associated stmt is ObjCMessageExpr
.
Add notification
Take the first record as an example, which means to say-[appdelegate Onviewcontrollerdidloadnotification:] As notification Knotificationviewcontrollerdidload, selector is added in-[appdelegate application:didfinishlaunchingwithoptions:].
Send Notifications
The first record, as a system-level notification, is considered to be called by the app's main portal.
The second record indicates that the -[ViewController viewDidLoad]
Knotificationviewcontrollerdidload was sent.
Called if it -[AppDelegate application:didFinishLaunchingWithOptions:]
is called -[UIApplication main]
(assuming the primary portal) and is called -[ViewController viewDidLoad]
-[AppDelegate onViewControllerDidLoadNotification:]
. Where notification is a system notification, it is only required to -[AppDelegate application:didFinishLaunchingWithOptions:]
be called.
This information gets the overloaded functions that the portal is located in VisitStmt(Stmt \*stmt)
, and the related stmt have ObjCMessageExpr
. For simple processing, this is only the addObserver:self
case (and most common), otherwise the Argu Expr\*
will be complex to analyze. PS. The difference between system notification and local notification uses a match on the name (System notifications often end with ns,ui,av beginning with notification).
Repeating Code Analysis
The duplicate code here is exactly the same for a two (or more than two) -/+[Class method:\*]
implementation. Refer to the above mentioned Clsmethod in the SourceCode, you can get each method implementation of the source code. At the same time in order to eliminate such as the format of the difference (such as a space, less a space, etc.) caused by the difference, based on the format provided by Clang, according to a certain style (GOOGLE/LLVM, etc.) all methods to implement the source format, and then analysis can be.
Use the LLVM style to format the code:
find $prjDir -type f -name "\*.m" | xargs /opt/llvm/llvm_build/bin/clang-format -i -style=LLVM
The results of a duplicate code for the sample project in this article are as follows:
not being parsed by the final call code
The object of analysis is all the keys inside the Clsmethod.json, that is, all the methods that actually own the source code.
Initializes the default call relationship Usedclsmethodjson: {-[AppDelegate alloc],"-[UIApplication main]","-[UIApplication main]","-[UIApplication main]","+[NSObject alloc]","-[UIApplication main]"}
, where appdelegate is passed to analyzer by the user.
Analysis of the existence of a path for all source-containing methods can be called by a key that has been called Usedclsmethodjson.
For a clsmethod, it needs to check the path including three, class inheritance system, protocol system and notification system.
For class-inheriting systems, the current class is traced upward (until it is found to be called or nsobject), and each base class corresponds to whether it is called by an -/+[Class method:*]
implicit call relationship, such as -[ViewController viewDidLoad]
being implicitly invoked, which is -[ViewController alloc]
also considered to be called when it has been -[ViewController alloc]
called -[ViewController viewDidLoad]
. It is important to note that you need to write an implicit call relationship table for querying, as follows:
For the protocol system, it is necessary to refer to a similar protocol reference system up-tracing (until the discovery has been called or NSObject
protocol), for a particular protocol judgment, need to distinguish between two, one is the system-level protocol, UIApplicationDelegate
for example, for -[AppDelegate application:didFinishLaunchingWithOptions:]
this, for reference, is called if it is called AppDelegate<UIApplicationDelegate>
-[AppDelegate alloc]
-[AppDelegate application:didFinishLaunchingWithOptions:]
. For user-defined protocol, ViewControllerDelegate
for example, for -[AppDelegate viewController:execFunc:]
not only the need to -[AppDelegate alloc]
be called and the -[ViewControllerDelegate viewController:execFunc:]
corresponding callers in Protointerfcall.json have already existed in Usedclsmethodjson caller .
For the notification system, the previous article has been analyzed.
The Clsmethod results used in this example analysis are as follows:
This example analyses the Clsmethod results that are not used:
View Sample project: Https://github.com/kangwang1988/XcodeZombieCode.git
Comparison of application effect of Zulip-ios
In view of the small size of the sample project, another open source Zulip-ios project, where the original project archive generated an executable file size of 3.4MB, combined with the method described in this article to remove the code that was not finally called (including business code, third party libraries), the executable file becomes 3MB. For such a well-designed project, the pure Code of the slimming effect is relatively considerable.
Zulip-ios Project: Https://github.com/zulip/zulip-ios
Limitations and customisation
This static analysis is suitable for situations where the receiver type of the message can be judged, and is not available when the runtime type is inconsistent with the static analysis type, or when the static analysis does not come out of the type. This analysis requires code writing specifications. For example, if a class implements a protocol, it must be stated in the Declaration, or it must be noted when delegate is in id<XXXDelegate>
the property.
Although this project has given a complete duplicate code and Useless Code analysis tool, but also has its limitations (mainly dynamic characteristics). The specific analysis is as follows:
OpenURL mechanism
Assuming that the project setup used openUrl:"XXX://XXViewController"
to open a VC, the clang plug-in needs to analyze the OpenURL parameters, if the parameters are Xxviewcontroller, then implied +[XXViewController alloc]
and -[XXViewController init]
.
Model conversions
If the Mtlmodel modelOfClass:[XXXModel class] fromJSONDictionary:error:
is used, it implies the +[XXXModel alloc]
and +[XXXModel init]
.
Message Swizzle
Assuming that the user Swizzle -[UIViewController viewDidLoad]
and -[UIViewController XXviewDidLoad]
, it needs to be added in Implicitcallstackjson -[UIViewController XXviewDidLoad]
-[UIViewController viewDidLoad]
.
Logic implied by the third-party framework
such as the annotationview of the gold map, need to add Implicitcallstackjson "-[MAAnnotationView prepareForReuse:]","+[MAAnnotationView alloc]"
and so on. Including some protocol in the third-party framework, it may also be necessary to refer to the uiapplicationdelegate referred to in the previous article to be handled at the system level protocol.
Some of the missing overloaded methods
If -[XXDerivedManager sharedInstance]
not implemented, Xxderivedmanager's base class Xxbasemanager sharedinstance is called -[self alloc]
, but because self static analysis is identified as Xxbasemanager, this results -[XXDerivedManager sharedManager]
in Although it is called by Usedclsmethod.json, -[XXDerivedManager alloc]
it cannot be called. In this case, it can be added at the time of Usedclsmethodjson initialization "+[XXDerivedManager alloc]","-[UIApplication main]"
.
Similar to cell Class
We often use dynamic methods to [[[XXX cellClassWithCellModel:] alloc] initWithStyle:reuseIdentifier:]
construct cells, in which case the call should be added to the cellClassWithCellModel
Implicitcallstackjson for the various things that are contained inside return [XXXCell class]
[[XXXCell alloc] initWithStyle:reuseIdentifier:],-[XXX cellClassWithCellModel:]
.
Xib/storyboard implies some alloc methods or invocation relationships for UI elements (Controller,table,button,cell,view, etc.).
Other implicit logic or dynamic attributes cause a missing call relationship.
other
For the package size, you can refer to the following ideas to thin body code:
Extracting refactoring of duplicate code
Removal of useless code
Low usage of third-party library processing (this article can not only find duplicate, useless code, further analysis clsmethod.json/ Unusedclsmethod.json more can get to each of the framework of how many methods, how many code, how many methods are -[UIApplication main]
called to, in the face of low usage of the library, you need to consider whether to introduce or rewrite all.
Repeated reference to third-party library processing (once found that the project of the team project is referencing the library of other teams, but because there is a copy of the library's own zip implementation, in the face of this situation, you can consider the need to abstract out a common framework to deal with, others refer to this project, Or simply use the libz that comes with the system itself to handle it better).
Because it can be analyzed at the source level, there are many things you can do with the clang plugin. I also use the clang plug-in to implement the code style check, API validation, the relevant example project is as follows:
Code style check: Https://github.com/kangwang1988/XcodeCodingStyle.git
API Validation: Https://github.com/kangwang1988/XcodeValidAPI.git
[Go] An iOS package size slimming scheme based on the clang plugin