编程知识 cdmana.com

The research of IOS plug-in architecture

Python The actual combat community

Java The actual combat community

Long press to identify the QR code below , Add as needed

Scan code, pay attention to add customer service

Into the Python community ▲

Scan code, pay attention to add customer service

Into the Java community

Author video team Dong Peipei

Source: Sohu technology products (ID:sohu-tech)

Preface

WWDC2014 The apple in iOS The dynamic library has been opened on , This gives us a lot of room to imagine .

Dynamic library is dynamic link library , yes Cocoa/Cocoa Touch A resource packaging method used in programs , You can put the code file 、 The header file 、 Resource file 、 Documents, etc. are gathered together , Convenient for developers . Dynamic libraries are not copied to the program's executable at compile time ( That is to say mach-o) in , When the program is running , The dynamic library is actually loaded .

Dynamic library runtime loading features , We can also replace the library at any time , Without recompiling the code . So we can do a lot of things , Such as plug-in application and dynamic update :

  • Plug in application

    At present, many application functions are being done more and more , Software is becoming more and more bloated , If the function modules of the software can also be loaded on demand like lazy loading , Let users download a feature from the Internet when they want to use it , Then manually load the dynamic library , Realize the plug-in function , You don't have to worry about the infinite increase of function points , What a wonderful thing it should be !

  • Application module dynamic update

    When a function point in the software appears serious bug, Or want to update a feature , At this time, you just need to download the new version of the dynamic library file from the server to the local , Then when the user restarts the application, the new function can be displayed .

Here's how to use dynamic Framework The way to achieve App Plug in and dynamic update of :

Realize the idea

take App The content of a module in is independent of a dynamic Framework In the form of , When a user wants to use a function , According to the configuration list, download the corresponding dynamic library file from the server to the sandbox , The dynamic library is then loaded and the principalClass Enter the independent function module , Realize plug-in dynamic loading of functions . And according to the version number of the configuration list , Compare and update the downloaded dynamic library , It can achieve the purpose of dynamic update .

If a user clicks on a module and then downloads it , There will be an obvious waiting process , In order to have a better user experience , You can choose a preload strategy , Or configure the default dynamic library in the project , This part can be selected according to the actual situation of the project , There is no discussion here .

The following figure shows the overall implementation process :

Project structures,

The project implementation is divided into two parts :1、 Create a dynamic library ;2、 Lord App Load maintenance dynamic library . Here, the project is divided into four parts , These are dynamic loading frameworks SVPCore and SVPRuntime、 Main project and other functional module plug-ins , The overall architecture design is shown in the figure below :

Plug in and dynamic loading framework design diagram

1. SVPCore

SVPCore The main role of is to parse the configuration information , Find the corresponding bundle object , And get the main entrance of the plug-in . contain SVPURI、SVPDispatch Class and a SVPBundleDelegate The agreement .

SVPURI: Provides a static initialization method , Parse the incoming address during initialization , Separately scheme( Dynamic library protocol name )、parameters( Dynamic library initialization parameters ) And resourcePath( Dynamic library path ) Parse it out and store it ;

SVPDispatch: Provides a SVPBundleProvider Is used to get the bundle object , And then through SVPBundleDelegate Provided by agreement resourceWithURI: Method to get the loaded plug-in main entry object .

SVPBundleDelegate: Provides a basis for SVPURI obtain UIViewController The agreement , From the plug-in dynamic library principalClass Implement the protocol , Returns the main entry object of the plug-in . meanwhile , The parameters in the configuration information of the main project can be changed , adopt SVPURI Of parameters To the main entry object , When the plug-in dynamic library is provided to multiple projects , It can realize the user-defined initialization conveniently and flexibly .

SVPURI The main code of is as follows :

- (id)initWithURIString:(NSString *)uriString
{
    self = [super init];

    if (self)
    {
        _uriString = [uriString copy];

        NSURL *url = [NSURL URLWithString:_uriString];

        if (!url || !url.scheme) return nil;
      
      	// scheme Used to mark the dynamic library protocol name 
        _scheme = url.scheme;

        NSRange pathRange = NSMakeRange(_scheme.length + 3, _uriString.length - _scheme.length - 3);

        if (url.query)
        {
            NSArray *components = [url.query componentsSeparatedByString:@"&"];
            NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithCapacity:0];

            for (NSString *item in components)
            {
                NSArray *subItems = [item componentsSeparatedByString:@"="];
                if (subItems.count >= 2)
                {
                    parameters[subItems[0]] = subItems[1];
                }
            }

          	// parameters Used to mark dynamic library initialization parameters 
            _parameters = parameters;

            pathRange.length -= (url.query.length + 1);
        }

        if (pathRange.length > 0 && pathRange.location < uriString.length)
        {
          	// resourcePath Used to mark dynamic library paths 
            _resourcePath = [_uriString substringWithRange:pathRange];
        }
    }

    return self;
}

SVPDispatch The main codes are as follows :

//  according to URI Get the main library page 
- (id)resourceWithURI:(NSString *)uriString
{
    if (!uriString || !_bundleProvider) return nil;

    return [self resourceWithObject:[SVPURI URIWithString:uriString]];
}

- (id)resourceWithObject:(SVPURI *)uri
{
    if (!uri) return nil;

    id resource = nil;

  	// bundleProvider by SVPRuntime, The implementation proxy method returns URI Of the corresponding dynamic library principalObject
    if (_bundleProvider && [_bundleProvider respondsToSelector:@selector(bundleDelegateWithURI:)])
    {
        id<SVPBundleDelegate> delegate = [_bundleProvider bundleDelegateWithURI:uri];
      
      	// delegate For dynamic library principalObject, The implementation proxy method returns to the main entry page of the dynamic library 
        if (delegate && [delegate respondsToSelector:@selector(resourceWithURI:)])
        {
            resource = [delegate resourceWithURI:uri];
        }
    }

    return resource;
}

2. SVPRuntime

SVPRuntime The main function is to manage the function module plug-in , Including downloading / Decompress the plug-in and read the dynamic library of the extracted plug-in . contain SVPBundle、SVPBundleDownloadItem Class and SVPBundleManager Management category .

SVPBundle: Provide a pass through bundlePath To initialize the method , And provides a load Method , Read from the sandbox bundle Object and load , After loading, get bundle Of principalClass Object and initialize , Get the plug-in module entry ;

SVPBundleDownloadItem: Provides a method to initialize through configuration information , Download the plug-in according to the remote address in the configuration information , After the download is successful, according to the unique ID in the configuration information 、 Version number 、 The name of the dynamic library will be decompressed to the corresponding directory ;

SVPBundleManager: Realization SVPCore Provided SVPBundleProvider agreement , Will download 、 To load the plug-in and provide access SVPCore. After initialization, read the downloaded files locally bundles list , If the user clicks on a function module, check whether the plug-in has been installed from the list , If not installed, initialize one SVPBundleDownloadItem, And then call Item How to download , After that, decompress the downloaded dynamic library in the download callback and initialize its corresponding bundle.

Two things need to be noted here :

One is that there is no universal Class loadClass = [bundleclassNamed:className]; Get the plug-in main entry object in the form of , Because this implementation method must know the plug-in's main entrance in advance className, And you can't customize initialization parameters , So it's designed to be more flexible through SVPDispatch Unified scheduling transfer mode to achieve : adopt SVPDispatch Of resourceWithURI: Method , take SVPURI Inside parameters Initialization parameters are passed to the plug-in main entry object , The main entry object initializes the main page and returns .

The second is to realize version comparison and dynamic update of dynamic library , The version number of the dynamic library should be recorded when storing , And delete the old version data after the update .

SVPBundle The main code of is as follows :

- (BOOL)load
{
    if (self.status == SVPBundleLoaded) return YES;

    self.status = SVPBundleLoading;

  	//  Use the path to get a NSBundle object 
    self.bundle = [NSBundle bundleWithPath:self.bundlePath];

    NSError *error = nil;

    if (![self.bundle preflightAndReturnError:&error])
    {
        NSLog(@"%@", error);
    }

  	//  load NSBundle
    if (self.bundle && [self.bundle load])
    {
        self.status = SVPBundleLoaded;

      	//  obtain NSBundle Of principalObject
        self.principalObject = [[[self.bundle principalClass] alloc] init];

        if (self.principalObject && [self.principalObject respondsToSelector:@selector(bundleDidLoad)])
        {
            [self.principalObject performSelector:@selector(bundleDidLoad)];
        }
    }
    else
    {
        self.status = SVPBundleLoadFailed;
    }

    return self.status == SVPBundleLoaded;
}

SVPBundleManager The main codes are as follows :

- (instancetype)init {
    self = [super init];
    if (self) {
      	//  follow SVPCore The agreement 
        [SVPAccessor defaultAccessor].bundleProvider = self;
        
      	//  Traverse the local folder , Load dynamic library 
        _installedBundles = [NSMutableDictionary dictionary];
        NSString *mainPath = [self bundleFolder];
        NSDirectoryEnumerator *directoryEnumerator = [self.fileManager enumeratorAtPath:mainPath];
        for (NSString *path in directoryEnumerator.allObjects) {
            NSString *subPath = [mainPath stringByAppendingPathComponent:path];
            NSArray *dirArray = [self.fileManager contentsOfDirectoryAtPath:subPath error:nil];
            if (dirArray.count > 0) {
                NSString *frameworkName = [dirArray firstObject];
                if ([frameworkName hasSuffix:@".framework"]) {
                    NSString *bundlePath = [subPath stringByAppendingPathComponent:frameworkName];
                    SVPBundle *bundle = [[SVPBundle alloc] initWithBundlePath:bundlePath];
                    
                    NSString *version = @"";
                    NSArray *strArray = [frameworkName componentsSeparatedByString:@"_"];
                    if (strArray.count > 0) {
                        version = [strArray firstObject];
                    }
                  	//  Dynamic library identification : Version number + Unique identification 
                    NSString *bundleKey = [NSString stringWithFormat:@"%@_%@", version, path];
                    _installedBundles[bundleKey] = bundle;
                }
            }
        }
    }
    return self;
}

#pragma mark - SVPBundleDownloadItemDelegate

//  Download complete , Unzip the downloaded dynamic library 
- (void)downloadBundleItem:(SVPBundleDownloadItem *)downloadItem finished:(BOOL)success {
    if (success) {
        [self unZipDownloadItem:downloadItem];
    } else {
        if (self.finishBlock) {
            self.finishBlock(NO);
            self.finishBlock = nil;
        }
    }
}

#pragma mark - SVPBundleProviderDelegate

//  Realization SVPCore The agreement , return URI Of the corresponding dynamic library principalObject
- (id)bundleDelegateWithURI:(SVPURI *)uri {
    if ([uri.scheme isEqual:@"scheme"] && uri.resourcePath.length > 0) {
        SVPBundle *bundle = _installedBundles[uri.resourcePath];
        if (bundle) {
            return bundle.principalObject;
        }
    }

    return nil;
}

3. Plug in module

First create a dynamic library , Select when creating a project Cocoa Touch Framework, Here's the picture :

Create a dynamic library

The following will SVPCore After the dynamic library is imported , Create a BundleDelegate Realization SVPCore Of SVPBundleDelegate agreement , The code is as follows :

//  Dynamic library implementation SVPCore The agreement , Return to the main entry page of the dynamic library 
- (UIViewController *)resourceWithURI:(SVPURI *)uri {
    if ([uri.scheme isEqual:@"scheme"]) {
        if ([uri.resourcePath isEqualToString:@"wechat"]) {
            SVPWechatViewController *wechatVC = [[SVPWechatViewController alloc] initWithParameters:uri.parameters];
            return wechatVC;
        }
    }
    
    return nil;
}

SVPWechatViewController, This is the main entry object of the plug-in , On this basis, it is OK to realize the independent function of plug-ins .

then , The most important step , Need to be in the dynamic library Info.plist File configuration Principal class, The function of this entry is through NSBundle Of principalClass Gets the object , The following figure will SVPWechatBundleDelegate After setting it in , After loading Bundle send out principalClass news , That's what I got . because SVPWechatBundleDelegate Realized SVPBundleDelegate Agreed resourceWithURI: Method , You can return the plug-in's entry controller to the caller .

Dynamic library

After that, compile the dynamic library of the plug-in and print it into a compressed package , Put it on the server and provide a download link .

4. Main works

The function of the main project is relatively simple , First from Plist Read the configuration information from the file and display ( The Plist Files can be downloaded from the Internet ):

Configuration information

When the user clicks on the icon, first get the icon information and check whether the plug-in dynamic library has been loaded , If not loaded, call SVPBundleManager Of downloadItem Method to download , If loaded, call SVPDispatch Of resourceWithURI: Method to get the plug-in entry , Do the next operation , The main codes are as follows :

//  The user clicks on the plug-in 
- (void)onItemView:(UIButton *)sender {
    NSInteger itemIndex = sender.tag - 1000;
    if (itemIndex >= 0 && itemIndex < self.pluginArray.count) {
      	//  The configuration list information corresponding to the plug-in you click 
        PluginItem *pluginItem = [self.pluginArray objectAtIndex:itemIndex];
				
      	//  Dynamic library identification : Version number + Unique identification , In order to achieve the purpose of dynamic update 
        NSString *bundleKey = [NSString stringWithFormat:@"%@_%@", pluginItem.version, pluginItem.identifier];
        if (![[SVPBundleManager defaultManager] isInstalledBundleWithBundleKey:bundleKey])
        {
          	//  Local not loaded , First download the dynamic library from the server 
            __weak __typeof(self)weakSelf = self;
            __weak __typeof(PluginItem *)weakItem = pluginItem;
            __weak __typeof(UIButton *)weakSender = sender;
            [[SVPBundleManager defaultManager] downloadItem:[pluginItem toJSONDictionary] finished:^(BOOL success) {
                __strong __typeof(weakSelf)strongSelf = weakSelf;
                __strong __typeof(weakItem)strongItem = weakItem;
                __strong __typeof(weakSender)strongSender = weakSender;
                if (success) {
                    dispatch_sync(dispatch_get_main_queue(), ^{
                        [strongSelf pushBundleVC:itemIndex];
                    });
                } else {
                    //  Prompt download failure 
                }
                dispatch_sync(dispatch_get_main_queue(), ^{
                    [strongSender setTitle:strongItem.name forState:UIControlStateNormal];
                });
            }];
            [sender setTitle:@" In the download ..." forState:UIControlStateNormal];
        }
        else
        {
          	//  Locally loaded ,push The main entrance page of dynamic library 
            [self pushBundleVC:itemIndex];
        }
    }
}

- (void)pushBundleVC:(NSInteger)index {
    if (index >= 0 && index < self.pluginArray.count) {
        PluginItem *pluginItem = [self.pluginArray objectAtIndex:index];
        NSString *uriString = [NSString stringWithFormat:@"scheme://%@_%@", pluginItem.version, pluginItem.resource];
        UIViewController *vc = [[SVPAccessor defaultAccessor] resourceWithURI:uriString];
        if (vc)
        {
            [self.navigationController pushViewController:vc animated:YES];
        }
    }
}

When the plug-in module needs to be updated , Just modify the configuration list and plug-in dynamic library package on the server , The master project updates the local configuration list at the appropriate time , When the user clicks on the plug-in function , You can find and update the local dynamic library according to the version number , To achieve the purpose of dynamic update .

matters needing attention

When the system loads the dynamic library , Will check the Framework The signature of the , The signature must contain TeamIdentifier, also Framework And the Lord App Of TeamIdentifier It has to be consistent .

If it's not consistent , The following mistakes will be reported :

Error loading /path/to/framework: dlopen(/path/to/framework, 265): no suitable image found. Did find:/path/to/framework:code signature in (/path/to/framework) not valid for use in process using Library Validation: mapped file has no cdhash, completely unsigned? Code has to be at least ad-hoc signed.


summary

The above is the use of Framework Dynamic library for plug-in loading and dynamic update of all the implementation , For now ,Apple You don't want developers to bypass App Store To update App, Therefore, the use of hot updates should be treated with caution , For enterprise applications that don't need to be on the shelf , It can be used . With the continuous development of Apple's open environment , Will Apple surprise us developers , It's not known .

 Programmer column   Scan code and pay attention to customer service   Press and hold to recognize the QR code below to enter the group 

Recent highlights are recommended :  

  My girlfriend thinks the annual salary is 50 Ten thousand is the average level , What do I do ?

  The sexy goddess of the biggest straight men forum in China overturned

 IntelliJ IDEA Fully optimized settings , Efficiency bars !

  Very useful Python skill


Here's a look Good articles to share with more people ↓↓

版权声明
本文为[Osu sdkczhvw]所创,转载请带上原文链接,感谢

Scroll to Top