• Universal Framework for iOS


    Hello my friends,

    Due to some bugs and questions with the old tutorial, I'm creating this new one, much more simpler and less bugs than the another one. I'll not post the old link here because everything you need to know you can find right here.

    Nowadays, exist few alternatives to create a Framework to iOS, changing the default Xcode Script, which could not be a good choice if you want to publish the APPs constructed with your custom Framework. I'll treat here about how to construct an Universal Framework to iOS, using the default tools from Xcode.

    Let's start! 


    Here is a little list of contents to orient your reading: 

    List of Contents to this Tutorial

    You can download the template instead of doing it manually:

    Download Xcode Template 
    Download now 
    Xcode Template 

    Unzip it and place it at /Library/Developer/Xcode/Templates/Project Templates/ 
    (create the path it needed)


    FAQ

    top 
    First off, I want to make sure you understand what this Framework to iOS can do, this can safe your time reading this article: 

    1. Can I use this Framework as a Bundle to store my files, XIBs, images? A: Yes, you can and now it's very very easy to retrieve your files.
    2. Can I use this Framework to import other Frameworks, like import UIKit, CoreGraphics, OpenGL? A: No. There is no way to do that. Your code in this custom Framework can import classes from other frameworks normally, but it is just a reference, classes from another framework will not be compiled at this time. So you must import the referenced Framework on the new project as well.
    3. Will be my code visible to others? A: No. This Framework will export a compiled binary, so anyone can see inside it. You can make the same for some other files, like XIBs.
    4. Why I need this? A: This is for developers/teams that want to share their codes without shows the entire code (.m/.c/.cpp files). Besides this is for who want to organize compiled code + resources (images, videos, sounds, XIBs, plist, etc) into one single place. And this is also for that teams that want to work together above the same base (framework).


    Framework on iOS? Really?

    top 
    Ok buddies, let's make something clear, many people had said:"iOS doesn't support custom Frameworks!", "Custom Framework is not allowed at iOS!", "Doesn't exist custom Framework on iOS!" and many other discouraging things like these. Look, I've made many frameworks and worked with many others, I don't believe that is really impossible to use a Framework on iOS. According to my experience and knowledge about Frameworks, it's absolutely feasible a custom Framework on iOS Devices. If we think more about this issue we can find an elegant solution, right? First, let's understand what a Framework really is, here is the definition of framework by Apple's eyes:

    A framework is a hierarchical directory that encapsulates shared resources, such as a dynamic shared library, nib files, image files, localized strings, header files, and reference documentation in a single package.

    Doesn't make many sense something with this description not be allowed in iOS, thinking in architecture and structure. Apple also says:

    A framework is also a bundle and its contents can be accessed using Core Foundation Bundle Services or the Cocoa NSBundle class. However, unlike most bundles, a framework bundle does not appear in the Finder as an opaque file. A framework bundle is a standard directory that the user can navigate.

    Good, now thinking about iOS security, performance and size, the only thing in a Framework definition which doesn't fit in iOS technology is the "dynamic shared library". The words "dynamic" and "shared" are not welcome in the iOS architecture. So the Apple allows us to work and distribute something called "Static Library". I don't like it! It's not so easy as a Cocoa Framework, if a developer got your Static Library, he needs to set a header path too, or import the header files... it's not useful, that's a shit!

    Well, so a Framework concept is absolutely compatible with iOS, except by the "dynamic shared library", on the other hand Apple says that a "static library" is OK for iOS. So if we replace the "dynamic shared libraries" by a "static library" we could construct a Custom Framework to iOS, right?

    Right!!!!

    This is exactly what we'll make in this article, let's construct a Framework with Static Library, by the way, an Universal Framework.


    Understanding Universal, Dynamic and Static concepts

    top 
    Simple answer: 

    • Universal: Which works perfect on all architectures. iOS devices uses armv6 and armv7, iOS simulator on MacOS X uses i386.
    • Dynamic: The compiler doesn't include the target files directly. The classes/libraries are already pre-compiled (binary format) and lies on the system path. Besides, the dynamic libraries can be shared by many applications. This is exactly what Cocoa Framework is.
    • Static: It represents that classes/libraries which is compiled by the compiler at the build phase. These files can't be shared by other applications and relies on application path.

    Simple as that. If you need more informations about Dynamic VS Static libraries, try this Apple's Documentation.

    No more concepts, hands at work!


    Constructing a Framework Project

    top 

    1. Create the Project:

    top 
    I want to show you step by step of the entire process, so let's start with the most basic, create an iOS project. You can choose one application template in Xcode, this is not really important, but remember to choose one template which could test your Framework code before export it. 
    Create an application project.


    2. Framework Classes:

    top 
    Create your framework classes.

    Remember to create an "import header" to make everything simpler and organized to the user of your framework. Remember to write this header file with a framework notation, just as shown in the image bellow. Also remember to create your classes taking care to hide the classes which should not be visible to the other developers (users of your framework). We will set the public and private headers soon, but it's very important to you protect the "core" classes, I mean, that classes which you don't want to make visible to other developers.

    For those private classes, you could import their header inside the ".m" (or .mm, .cpp, etc) file of a public class, by doing this you protect the header of private classes. Well, I know you probably already know that, I'm saying just to reinforce.

    Remember that organization is 90% of a good framework, so try follow all the Apple advices to create your classes names, methods, properties, functions, etc.


    Creating the Framework

    top 

    3. Create a Framework Target:

    top 
    OK, let's create a target to compile our framework. Click on the icon of your project in the project navigator at the left and hit the button "Add Target". A new window will come up. Now is our first trick. Instead to create a "Cocoa Touch Static Library" or a "Cocoa Framework" we will create a "Bundle" target.

    A Bundle? Really? Yes! I can explain. A "Cocoa Framework" target can't be compiled to armv6/armv7 and Xcode doesn't allow us to use "Static Libraries" in a "Cocoa Framework", so we can't use this target. On the other hand, we can't use "Cocoa Touch Static Library" either, because it doesn't use the framework structure that we want.

    Now, the Bundle target could be the best choice. It can hold any file we want, we can compile source code inside it and... we can turn it into a framework. To say the truth, almost all "Framework & Library" targets could be turned into a framework too, even the "Cocoa Touch Static Library", throughout this article you probably will figure out how. For now, let's create a Bundle target.

    Create a Bundle target rather than Cocoa Touch Static Library.


    4. Bundle Setup:

    top 
    It's time to make all the necessary changes to the Bundle target. Different than the old tutorial. You don't need to clean up anything. Just know that everything else will be ignored (linked frameworks .plist files, .pch etc...).

    I'm sure you already know this, but just to reinforce, here is the Build Setting screen, you can find it by clicking on the project icon in the left project navigator and then clicking in the "Build Setting" tab. 
    You must make a special Build Setting to turn a Bundle into a framework.

    Here is our second great trick, or should be better to say "tricks". Let's change the "Build Setting" following this list: 

    • Base SDK: Latest iOS (iOS X.X) (in the X.X will appear the number of the lastest iOS SDK installed on your machine).
    • Architectures: $(ARCHS_STANDARD_32_BIT) armv6 (it’s very important to be exactly this value including the space before "armv6") This setting is valid to Xcode 4.2, if you are using an old version, use the "Standard (armv6 armv7)" option. (the values for this property depend on the value of the item bellow, so set that first).
    • Build Active Architecture Only: NO (otherwise we can't compile to armv6 and armv7 at the same time).
    • Valid Architecture: $(ARCHS_STANDARD_32_BIT) (it's very important to be exactly this value). If your Xcode is showing two lines with armv6 and armv7, delete then and insert this value in one single line.
    • Dead Code Stripping: NO.
    • Link With Standard Libraries: NO.
    • Mach-O Type: Relocatable Object File. This is the most important change. Here, we instruct the compiler to treat the Bundle as a relocatable file, by doing this, we can turn it into a framework with the wrapper setting.
    • Other Linker Flags: This setting is not mandatory, but if you are planning to use any kind of C++ code (.cpp or .mm) on this framework, Chris Moore (on the comments) advises to use the "-lstdc++" option. In this case could be a good idea to use "-ObjC" too, to avoid conflicts in old compilers.
    • Wrapper Extension: framework. Here we change the Bundle to a Framework. To Xcode, frameworks is just a folder with the extension .framework, which has inside one or more compiled binary sources, resources and some folders, a folder, usually called Headers, contains all the public headers.
    • Generate Debug Symbols: NO (this is a very important setting, otherwise the framework will not work on other computers/profiles).
    • Precompile Prefix Header: NO.
    • Prefix Header: "". (Leave it blank).

    IMPORTANT: Since the Xcode 4.x the architectures armv6 has no longer support. So, to create a real Universal Framework we must make a small "hack":

    1. After change the settings above close the Xcode, find the .xcodeproj (the project file) in Finder and then "Show Package Contents".
    2. Open the file "project.pbxproj" into a text editor.
    3. Delete all the lines with VALID_ARCHS = “$(ARCHS_STANDARD_32_BIT)”.


    5. Adding code and resources to the Bundle (Framework)

    top 
    It's time to place the content in our framework and define the public headers. To do that, with the Bundle target selected, click on the "Build Phase" tab. At bottom, hit the button "Add Phase" and then "Add Copy Headers".

    Open the recently created "Copy Headers" section and separate your public headers from the private or project headers. The difference here is: 

    • Public: Headers that other developers must know in order to work with your framework. In the final framework product, these headers will be visible even to Xcode.
    • Private: Headers that is not necessary to other developers, but is good for consult or for reference. These headers will not be visible to Xcode, but will be in the framework folder.
    • Project: Headers that the other developers nor Xcode have access. In reality these headers will not be placed in the final product, this is just to instruct the compiler to create your custom framework.

    Now, open the "Compile Source" section and put there all your .m, .c, .mm, .cpp or any other compilable source file.

    If your framework include not compilable files, like images, sounds and other resources, you can place them in the "Copy Bundle Resources" section. Later, when we generate the final framework, all your resources will be placed in a folder called "Resources", but you can change it. That folder is very important, because it will be part of the path to retrieve your resources from the framework product.

    Tip: To add many files at once, click on the “+” button and write the files’ extension on the search field. For example “.m”, “.c”, “.cpp”, “.h”, etc. This can save a lot of time.

    This is how your "Build Phase" will looks like: 
    Define your compilable source and the headers.


    Building the Universal Framework

    top 

    6. Creating Universal Target:

    top 
    To join both architectures products into one, we must to use the Lipo Tool. It's a tool which comes with iOS SDK, just to know, it is in "/Platforms/iPhoneOS.platform/Developer/usr/bin", its file name is "lipo". But we don't need to know this path, Xcode can deal with it to us.

    Add a new target, hit the "Add Target" button, just as you did with Bundle Target. At this time a good choice is the "Aggregate" target. It doesn't create any product directly, its purposes is just to aggregate another targets and/or run some scripts, exactly what we want! To use the Lipo Tool we'll need to create a "Run Script" at the "Build Phase".

    Use the "Aggregate" target to construct a run script.


    7. Lipo Tool Script:

    top 
    This will be our greatest trick. The following script will make everything we need. It will compile the Framework Target to iOS Device and Simulator at once, will merge them with Lipo tool and will organize a good Framework Bundle structure.

    Copy and paste this on your "Run Script" phase:

    Xcode Script to Lipo Tool
      
    # Sets the target folders and the final framework product.
    FMK_NAME="FI"  
    FMK_VERSION="A"
    
    # Install dir will be the final output to the framework.
    # The following line create it in the root folder of the current project.
    INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework
    
    # Working dir will be deleted after the framework creation.
    WRK_DIR=build  
    DEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework  
    SIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework
    
    # Building both architectures.
    xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphoneos  
    xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphonesimulator
    
    # Cleaning the oldest.
    if [ -d "${INSTALL_DIR}" ]  
    then  
    rm -rf "${INSTALL_DIR}"  
    fi
    
    # Creates and renews the final product folder.
    mkdir -p "${INSTALL_DIR}"  
    mkdir -p "${INSTALL_DIR}/Versions"  
    mkdir -p "${INSTALL_DIR}/Versions/${FMK_VERSION}"  
    mkdir -p "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources"  
    mkdir -p "${INSTALL_DIR}/Versions/${FMK_VERSION}/Headers"
    
    # Creates the internal links.
    # It MUST uses relative path, otherwise will not work when the folder is copied/moved.
    ln -s "${FMK_VERSION}" "${INSTALL_DIR}/Versions/Current"  
    ln -s "Versions/Current/Headers" "${INSTALL_DIR}/Headers"  
    ln -s "Versions/Current/Resources" "${INSTALL_DIR}/Resources"  
    ln -s "Versions/Current/${FMK_NAME}" "${INSTALL_DIR}/${FMK_NAME}"
    
    # Copies the headers and resources files to the final product folder.
    cp -R "${DEVICE_DIR}/Headers/" "${INSTALL_DIR}/Versions/${FMK_VERSION}/Headers/"  
    cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources/"
    
    # Removes the binary and header from the resources folder.
    rm -r "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources/Headers" "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources/${FMK_NAME}"
    
    # Uses the Lipo Tool to merge both binary files (i386 + armv6/armv7) into one Universal final product.
    lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/Versions/${FMK_VERSION}/${FMK_NAME}"
    
    rm -r "${WRK_DIR}"  
    

    Now build the Aggregate target. Doesn't matter you build for iOS Device or Simulator, this script will create a working folder, compile the framework target twice in there (device + simulator) and will output a folder called "Products" located in the project root folder. There is your Universal Framework to iOS!

    Congratulations!


    Importing your Universal Framework

    top 

    8. Importing:

    top 
    To test your Universal Framework, create a new Xcode project, select the Application target and go to "Build Phase" tab. Open the section "Link Binary With Libraries" and hit the "+" to add a new Framework. Click the "Add Other..." button and select your Universal Framework. Remember, you must to select the ".framework" folder. Remember to import your Framework Principal Header as a framework notation. Xcode will use your public headers in the Code Completion.

    Let's understand what happens until here: 
    When we set the "Mach-O Type" to "Relocatable Object File" the Xcode understand that everything related to that package will be like a "Binary Archive" like a ZIP. But that archive must be compiled again in new projects. 
     
    Then, when we create a Framework Bundle structure, Xcode understand that everything inside it is organized in folders like "Headers" and . But, as any other external bundle, to retrieve the resources you must load the external bundle. To iOS this could be an annoying step, however our Framework structure can help. Just Click and Drag on your Framework icon from "Project Navigator" to the "Copy Bundle Resources". By doing this all the resources in your Framework will be copied to your Application Main Bundle.

    Now, to retrieve the resources, make use of the main bundle, just as you are used to: 

    Framework Bundle
      
    [[NSBundle mainBundle] pathForResource:@"FI.framework/Resources/FileName"
                                    ofType:@"fileExtension"];
    


    Conclusion

    top 
    Well done, my friends! As we are used, let's make a final review and take care with some possible problems. 

    • In a common Xcode project, create a Bundle target.
    • Make the necessary setup, place your sources, headers and resources in it.
    • Create an Aggreate target and place a Run Script in it.

    One last advice: Take care with your classes structure. If you set, for example, the ClassB.h as a Project or Private header, but in your code you import it into a Public header, this will cause conflicts.

    And one last tip: Notice in the sample project I removed the scheme for the "Bundle Target". We don't need that scheme any more, because the new script will manage the compilation to us.

    That's all, buddies. 
    Enjoy your Framework to iOS!

    Thanks for reading,

  • 相关阅读:
    java:transient是什么,有什么作用
    如何阅读java源码
    java里面list是引用的好例子
    sort给文件按照大小排序
    HBase的rowkey排序和scan输出顺序
    记录一次事故——idea,sbt,scala
    一个简单的synchronized多线程问题、梳理与思考
    Android TextView文字描边的实现!!
    android中include标签的使用
    layout_weight 的解释及使用
  • 原文地址:https://www.cnblogs.com/zhaoguowen/p/4489506.html
Copyright © 2020-2023  润新知