MangoFix:iOS熱修復另闢蹊徑

今天向你們介紹的是iOS熱修復的另外一解決方案:MangoFix。介紹他的緣由是他和傳統的iOS熱修復使用JavaScript bridge 的方式徹底不一樣,MangoFix是一個語法和OC語法很是相似的DSL,其語言自己的設計目標就是爲了解決iOS熱修復問題,因此在使用的便捷程度和性能方面都要遠遠超過傳統的iOS 熱修復SDK,好比JSPatch。下面從如下幾點介紹MangoFix,更具體的請參考GitHub文檔和MangoFix單元測試html

一、如何加載一個MangoFix腳本

  • 1 首先經過CocoaPods安裝MangoFix :pod 'MangoFix'git

  • 2 引入MangoFix頭文件:#import <MangoFix/MangoFix.h>github

  • 3 建立MangoFix腳本執行上下文對象MFContext實例編程

  • 4 運行MangoFix腳本文件bash

示例代碼以下:app

NSString *path = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"mg"];
    NSURL *scriptUrl = [NSURL fileURLWithPath:path];
    MFContext *context = [[MFContext alloc] init];
    [context evalMangoScriptWithURL:scriptUrl];
複製代碼

二、MangoFix如何修復OC對象(類)方法

MangoFix能夠替換或建立任意OC對象實例方法或類方法,語法和OC相似,不過在類的定義上採用class關鍵字。下面示例:async

class MFInstanceMethodReplaceTest : NSObject {
  
- (BOOL)testInstanceMethodReplace{
    return YES;
}
    
}
複製代碼

對於類方法的替換隻需將方法返回值類型前的-修改成+便可。
須要注意的是:
一、繼承的父類不能夠省略。函數

三、MangoFix如何爲對象添加屬性

MangoFix中爲對象添加屬性和OC同樣,支持的修飾符有: weakstrongcopyassignnonatomicatomic。下面看一下示例代碼:性能

class MFObjectPropertyTest : NSObject{

@property(nonatomic, copy)NSString *propertyName;
  
- (NSString *)testObjectPropertyTest{
    return self.strTypeProperty;
}

}
複製代碼

須要注意的是:
一、屬性不支持class修飾符。
二、MangoFix是經過objc_setAssociatedObject實現屬性值的存儲,可是MangoFix在解析時候作了處理,訪問屬性值也能夠經過_propertyName這種方式進行訪問。單元測試

四、MangoFix中如何使用block

在MangoFix對OC中block類型聲明過於複雜作了簡化,用Block關鍵字表示block類型,block的定義則和OC相同,示例代碼以下:

class MFMethodParameterListAndReturnValueTest : NSObject{

- (Block)testMethodParameterListAndReturnValueWithString:(NSString *)str block:(Block)block{
    NSMutableDictionary *dic = @{}.mutableCopy();
    dic[@"param1"] = str + @"MangoFix";
    dic[@"param2"] = block(@"MangoFix");
    
    Block retBlock = ^NSDictionary *(/*不能加void*/){
        return dic;
    };
    return retBlock;
}

}
複製代碼

須要注意的是:
一、在無參block定義時,不能夠加void聲明。
二、Block關鍵字後面不須要加*運算符。

五、如何解決Block循環引用問題

MangoFix在1.1.7版本中添加__weak__strong變量修飾符,能夠像OC原生同樣解決Block循環引用問題,使用示例以下:

@interface MyController : UIViewController

@property(nonatomic,copy) id block;

@end
複製代碼
class MyController: UIViewController {
- (void)viewDidLoad {
        super.viewDidLoad();
        __weak id weakSelf = self;
        self.block = ^{
            __strong id strongSelf = weakSelf;
            NSLog(weakSelf);
        };
}

}
複製代碼

上部分是OC代碼,下部分是MangoFix代碼,須要注意的是,__weak__strong只能放在變量類型以前。

六、MangoFix中如何使用GCD

MangoFix中已經內置的GCD API,使用方法和 OC相同,對於須要擴展的C函數,能夠參考下面如何在MangoFix中注入全局對象的描述,GCD使用示例以下:

class MFGCDTest : NSObject {

- (void)testGCDWithCompletionBlock:(Block)completion{
    dispatch_queue_t queue = dispatch_queue_create("com.plliang19.mango", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        completion(@"success");
    });
}

- (void)testGCDAfterWithCompletionBlock:(Block)completion{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        completion(@"success");
    });
}
    
}


class MFDispatchSourceTest : NSObject{

- (NSInteger)testDispatchSource{
    NSInteger count = 0;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{
        count++;
        if (count == 10) {
            dispatch_suspend(timer);
            dispatch_semaphore_signal(semaphore);
        }
    });
    dispatch_resume(timer);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    return count;
}

}

複製代碼

七、static變量支持

MangoFix1.2.0版本中增長了對static變量進行了支持,MangoFix 的static變量和C語言中static變量特性基本一致。MangoFix中經過一張全局表對static變量進行管理,static變量只會初始化一致,static變量生命週期爲從第一次初始化到App退出,static變量做用域和自動變量做用域一致,因此能夠在不一樣做用域範圍內,建立變量名相同的static變量也是不會衝突的。

八、取地址運算符

MangoFix1.2.0版本中,增長了對取地址運算符&的支持,利用取地址運算符和static變量,MangoFix便能對GCD中的dispatch_once函數作很好的支持,好比下面的MangoFix示例代碼:

class MFGetAddressOperatorTest : NSObject{

- (NSInteger)testGetAddressOperator{
    static int i = 0;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        i++;
    });
    return i;
}

}
複製代碼

九、如何在MangoFix中注入全局對象

MangoFix中MFContext對象提供了 - (void)setObject:(MFValue *)value forKeyedSubscript:(NSObject <NSCopying> *)key;方法,便於用戶向執行上下文中注入全局對象,好比在OC代碼中執行下面代碼:

context[@"globalVar"] = [MFValue valueInstanceWithBOOL:YES];
context[@"MyLog"] = [MFValue valueInstanceWithBlock:^void (id obj){
        NSLog(@"%@",obj);
    }];
複製代碼

分別表示向context注入全局的BOOL變量globalVar和名爲MyLog的block。

十、MangoFix中如何針對不一樣App版本作不一樣的熱修復處理

MangoFix提供了條件註解#If(conditionExpr),能夠在運行時作判斷註解所做用的類、屬性、方法是否使能,先看一下示例代碼:

class MFConditionalReplaceTest : NSObject{

#If($systemVersion.doubleValue() >= 10.0 )
- (BOOL)testConditionalReplace{
    return NO;
}

}
複製代碼

上面代碼表示只有當$systemVersion.doubleValue()值大於10.0纔會對 - (BOOL)testConditionalReplace方法進行替換。 MangoFxi中已經內置了$systemVersion$appVersion$buildVersion等和版本相關的全局變量,分別表示:[UIDevice currentDevice].systemVersionCFBundleShortVersionStringCFBundleVersion,固然若是用戶以爲不夠還能夠本身向MangoFix執行上下文中注入自定義的全局變量。

十一、C函數變量

早期MangoFix版本中已經將一些經常使用的C函數進行預埋,用戶也能夠自定義進行預埋,可是總有一些須要調用的C函數沒有預埋到,因此MangoFix 1.3.0版本開始支持C函數變量,能夠作到C函數聲明即用無需預埋,C函數變量的定義和其餘語言中的泛型很相似,格式如: CFunction<returnType,arg1Type,arg2Type,...> func, 尖括號中第一個type是函數返回值類型,其餘的爲函數形參類型,如今支持的類型有:voidBOOLintlongint8_tint16_tint32_tint64_tu_intu_longu_int8_t```、u_int16_tu_int32_tu_int64_tsize_tfloatdoubleCGFloatchar *void *idSELClassstruct structName,對於其餘數據類型,要根據數據類型的大小選擇上面一種數據類型,而C函數變量的值用CFunction("function_name")獲取,對於dlsymdlopen這兩個函數已被禁止動態調用,另外要注意的是CFunction("function_name")``只支持獲取動態連接的C函數。 下面咱們看一段示例代碼:

int NSDocumentDirectory = 9;
int NSUserDomainMask = 1;

int  O_WRONLY = 0x0001;
uint S_IRWXU  = 0000700;


CFunction<id, int, int, BOOL> NSSearchPathForDirectoriesInDomains = CFunction("NSSearchPathForDirectoriesInDomains");
CFunction<int, char *, int, int> open = CFunction("open");
CFunction<size_t, int, void *, size_t> write = CFunction("write");
CFunction<int, int> close = CFunction("close");



class  MFFuncDeclareTest : NSObject{

- (void)testFuncDeclare{
    NSString *doc = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;

    NSString *path = doc.stringByAppendingPathComponent:(@"MangoFxi.html");
    NSFileManager *fileManager = NSFileManager.defaultManager();
    if (!fileManager.fileExistsAtPath:(path)) {
        BOOL ret = fileManager.createFileAtPath:contents:attributes:(path, nil, nil);
        if (!ret) {
            NSLog(@"建立文件失敗");
            return;
        }
    }
    NSLog(path);
    int fd = open(path.UTF8String,O_WRONLY, S_IRWXU);
    if (fd < 0) {
        NSLog(@"打開文件失敗");
        return;
    }
    NSURL *url = NSURL.URLWithString:(@"https://github.com/YPLiang19/Mango");
    NSData *data = NSData.dataWithContentsOfURL:(url);
    write(fd, data.bytes, data.length);
    close(fd);
}

}

複製代碼

十二、類型別名

MangoFix 1.3.0版本開始支持 typedef功能,格式爲:typedef existingType newType; 好比:

typedef long alias_long;
alias_long var = 10;
複製代碼

1三、MangoFix中自定義結構體的使用要注意什麼

MangoFix腳本中使用結構體,原則上是要先對結構體使用declare struct進行聲明,可是MangoFix已經對經常使用的結構已經內置聲明,已內置聲明的結構以下: CGPointCGSizeCGRectCGAffineTransformCGVectorNSRangeUIOffsetUIEdgeInsetsCATransform3D

在MangoFix中使用未聲明的結構體,須要作以下聲明:

declare struct MFCustomStruct {
    typeEncoding:"{MFCustomStruct=dd}",//@encode(struct MFCustomStruct)
    keys:x,y
}
複製代碼

特別須要注意的是:
一、在定義一個結構體變量時,須要在前面加入struct關鍵字:

struct  UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
複製代碼

1四、Masonry鏈式編程方式在MangoFix中如何編寫

有同窗疑問對Masonry中的鏈式編程在MangoFix如何編寫呢?其實這個寫起來也是大同小異。須要注意的是,在MangoFix中對調用的方法若是是無參的,那麼能夠省去調用後面的一對括號,可是若是方法返回的是一個block對象,那麼這對括號就不能省略,應爲此時若是省略了方法調用括號,那麼MangoFix解析器就沒法知道,此時用戶是想調用OC的對象方法,仍是調用方法返回的block。下面是一個OC和MangoFix分別調用Masonry官方示例代碼的對比:

UIView *superview = self.view;
    UIView *view1 = [[UIView alloc] init];
    view1.translatesAutoresizingMaskIntoConstraints = NO;
    view1.backgroundColor = [UIColor greenColor];
    [superview addSubview:view1];
    UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
    [view1 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
        make.left.equalTo(superview.mas_left).with.offset(padding.left);
        make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
        make.right.equalTo(superview.mas_right).with.offset(-padding.right);
    }];
複製代碼
UIView *superview = self.view;
    UIView *view1 = UIView.alloc().init();
    view1.translatesAutoresizingMaskIntoConstraints = NO;
    view1.backgroundColor = UIColor.greenColor();
    superview.addSubview:(view1);
    struct  UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
    view1.mas_makeConstraints:(^(MASConstraintMaker *make) {
        make.top.equalTo()(superview.mas_top).with.offset()(padding.top); //with is an optional semantic filler
        make.left.equalTo()(superview.mas_left).with.offset()(padding.left);
        make.bottom.equalTo()(superview.mas_bottom).with.offset()(-padding.bottom);
        make.right.equalTo()(superview.mas_right).with.offset()(-padding.right);
    });
複製代碼

上面部分是OC代碼,下面部分是MangoFix代碼,主要區別就是MangoFix代碼在equalTooffset後面多了一對括號,就是避免MangoFix解析器產生歧義。再者就是MangoFix中UIEdgeInsets前的struct關鍵字不能省略。

1五、MangoFix性能的如何

根據本人測試,MangoFix的初始化速度是JSPatch的10倍左右,運行速度是JSPatch的2~5倍,內存佔用方面並沒有太大區別。

1六、MangoFix還有哪些不足

  • MangoFix不支持可變參數方法的調用和替換。
  • MangoFix調用C函數,須要預先經過注入全局對象方式,經過block將C函數預先埋入(1.3.0版本已支持C函數變量聲明即用無需預埋)。
  • MangoFix不支持替換C函數。

轉載於做者:知水爲命
連接:www.jianshu.com/p/7ae91a2da…

給你們推薦一個優秀的iOS交流羣,羣裏的夥伴們都是很是優秀的iOS開發人員,咱們專一於技術的分享與技巧的交流,你們能夠在羣討論技術,交流學習。歡迎你們的加入761407670(密碼123)。

相關文章
相關標籤/搜索