BeeHive —— 一個優(yōu)雅但還在完善中的解耦框架

前言

BeeHive是阿里巴巴公司開源的一個iOS框架夫啊,這個框架是App模塊化編程的框架一種實現(xiàn)方案恶导,吸收了Spring框架Service的理念來實現(xiàn)模塊間的API解耦。

BeeHive這個名字靈感來源于蜂窩。蜂窩是世界上高度模塊化的工程結(jié)構归敬,六邊形的設計能帶來無限擴張的可能。所以就用了這個名字作為開源項目的名字鄙早。

在前一篇文章iOS 組件化 —— 路由設計思路分析中汪茧,我們分析了App組件之間可以通過路由來解除耦合。那么這篇文章就來看看利用模塊化的思想如何解除耦合的限番。

(看到這里一定會很多人有疑問舱污,那就看看這篇文章組件和模塊的區(qū)別)

說明:本文是基于BeeHive v1.2.0版本進行解析的。

目錄

  • 1.BeeHive概述
  • 2.BeeHive模塊注冊
  • 3.BeeHive模塊事件
  • 4.BeeHive模塊調(diào)用
  • 5.其他的一些輔助類
  • 6.可能還在完善中的功能

一. BeeHive概述

由于BeeHive是基于Spring的Service理念弥虐,雖然可以使模塊間的具體實現(xiàn)與接口解耦扩灯,但無法避免模塊對接口類的依賴關系。

暫時BeeHive沒有采用invoke和performSelector:action withObject: params的方法霜瘪。主要原因還是考慮學習成本難度以及動態(tài)調(diào)用實現(xiàn)無法在編譯檢查階段檢測接口參數(shù)變更等問題珠插。

目前BeeHive v1.2.0 全部是利用Protocol的方式,實現(xiàn)了模塊間解耦的目的:

1.各個模塊以插件的形式存在颖对。每個都可獨立捻撑,相互解耦。
2.各個模塊具體實現(xiàn)與接口調(diào)用分離
3.各個模塊也有生命周期缤底,也可以進行管理顾患。

官方也給出了一個架構圖:

接下來就依次分析模塊注冊,模塊事件个唧,模塊調(diào)用是如何實現(xiàn)解耦的江解。

二. BeeHive模塊注冊

先從模塊的注冊開始分析,來看看BeeHive是如何給各個模塊進行注冊的徙歼。

在BeeHive中是通過BHModuleManager來管理各個模塊的犁河。BHModuleManager中只會管理已經(jīng)被注冊過的模塊。

注冊Module的方式總共有三種:

1. Annotation方式注冊

通過BeeHiveMod宏進行Annotation標記鲁沥。


BeeHiveMod(ShopModule)

BeeHiveMod宏定義如下:



#define BeeHiveMod(name) \
char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";


BeeHiveDATA又是一個宏:


#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))


最終BeeHiveMod宏會在預編譯結(jié)束會完全展開成下面的樣子:



char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"" "))) = """ShopModule""";



注意雙引號的總對數(shù)呼股。

到這里__attribute((used,section("segmentname,sectionname")))就需要先說明2個地方。

__attribute第一個參數(shù)used很有用画恰。這個關鍵字是用來修飾函數(shù)的彭谁。被used修飾以后,意味著即使函數(shù)沒有被引用允扇,在Release下也不會被優(yōu)化缠局。如果不加這個修飾则奥,那么Release環(huán)境鏈接器下會去掉沒有被引用的段。具體的描述可以看這個gun的官方文檔狭园。

Static靜態(tài)變量會按照他們申明的順序读处,放到一個單獨的段中。我們通過使用__attribute__((section("name")))來指明哪個段唱矛。數(shù)據(jù)則用__attribute__((used))來標記罚舱,防止鏈接器會優(yōu)化刪除未被使用的段。

再來具體說說section的作用绎谦。

編譯器編譯源代碼后生成的文件叫目標文件管闷,從文件結(jié)構上來說,它已經(jīng)是編譯后可執(zhí)行的文件格式窃肠,只是還沒有經(jīng)過鏈接的過程包个。可執(zhí)行文件(Executable)主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format)冤留,它們也都是COFF(Common file format)格式的變種碧囊。程序源程序代碼被編譯之后會主要分成兩個段:程序指令和程序數(shù)據(jù)。代碼段屬于程序指令纤怒,數(shù)據(jù)段和.bss段屬于數(shù)據(jù)段糯而。

具體的例子見上圖,可見.data數(shù)據(jù)段里面保存的都是初始化過的全局靜態(tài)變量和局部靜態(tài)變量泊窘。.rodata段存放的是只讀數(shù)據(jù)歧蒋,一般都是const修飾的變量和字符串常量。.bss段存放的是未初始化的全局變量和局部靜態(tài)變量州既。代碼段就在.text段。

有時候我們需要指定一個特殊的段萝映,來存放我們想要的數(shù)據(jù)吴叶。這里我們就把數(shù)據(jù)存在data數(shù)據(jù)段里面的"BeehiveMods"段中。

當然還有其他的Attributes的修飾關鍵字序臂,詳情見官方文檔

回到代碼上來:


char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"" "))) = """ShopModule""";

也就相當于:



char * kShopModule_mod = """ShopModule""";

只不過是把kShopModule_mod字符串放到了特殊的段里面蚌卤。

Module被這樣存到了特殊的段中,那怎么取出來的呢奥秆?



static NSArray<NSString *>* BHReadConfiguration(char *section)
{
    NSMutableArray *configs = [NSMutableArray array];
    
    Dl_info info;
    dladdr(BHReadConfiguration, &info);
    
#ifndef __LP64__
    // const struct mach_header *mhp = _dyld_get_image_header(0); // both works as below line
    const struct mach_header *mhp = (struct mach_header*)info.dli_fbase;
    unsigned long size = 0;
    // 找到之前存儲的數(shù)據(jù)段(Module找BeehiveMods段 和 Service找BeehiveServices段)的一片內(nèi)存
    uint32_t *memory = (uint32_t*)getsectiondata(mhp, "__DATA", section, & size);
#else /* defined(__LP64__) */
    const struct mach_header_64 *mhp = (struct mach_header_64*)info.dli_fbase;
    unsigned long size = 0;
    uint64_t *memory = (uint64_t*)getsectiondata(mhp, "__DATA", section, & size);
#endif /* defined(__LP64__) */
    
    // 把特殊段里面的數(shù)據(jù)都轉(zhuǎn)換成字符串存入數(shù)組中
    for(int idx = 0; idx < size/sizeof(void*); ++idx){
        char *string = (char*)memory[idx];
        
        NSString *str = [NSString stringWithUTF8String:string];
        if(!str)continue;
        
        BHLog(@"config = %@", str);
        if(str) [configs addObject:str];
    }
    
    return configs;
}


Dl_info是一個Mach-O里面的一個數(shù)據(jù)結(jié)構逊彭。


typedef struct dl_info {
        const char      *dli_fname;     /* Pathname of shared object */
        void            *dli_fbase;     /* Base address of shared object */
        const char      *dli_sname;     /* Name of nearest symbol */
        void            *dli_saddr;     /* Address of nearest symbol */
} Dl_info;

這個數(shù)據(jù)結(jié)構的數(shù)據(jù)默認就是通過


extern int dladdr(const void *, Dl_info *);


dladdr這個函數(shù)來獲取Dl_info里面的數(shù)據(jù)。

dli_fname:路徑名构订,例如


/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation

dli_fbase:共享對象的的起始地址(Base address of shared object侮叮,比如上面的 CoreFoundation)

dli_saddr :符號的地址
dli_sname:符號的名字,即下面的第四列的函數(shù)信息


Thread 0:
0     libsystem_kernel.dylib          0x11135810a __semwait_signal + 94474
1     libsystem_c.dylib               0x1110dab0b sleep + 518923
2     QYPerformanceMonitor            0x10dda4f1b -[ViewController tableView:cellForRowAtIndexPath:] + 7963
3     UIKit                           0x10ed4d4f4 -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 1586420

通過調(diào)用這個static函數(shù)BHReadConfiguration悼瘾,我們就可以拿到之前注冊到BeehiveMods特殊段里面的各個Module的類名囊榜,都用字符串裝在數(shù)據(jù)里审胸。



+ (NSArray<NSString *> *)AnnotationModules
{
    static NSArray<NSString *> *mods = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mods = BHReadConfiguration(BeehiveModSectName);
    });
    return mods;
}


這是一個單例數(shù)組雅镊,里面裝的都是之前放在特殊段里面的Module名字對應的字符串數(shù)組毫胜。

拿到這個數(shù)組以后蓉坎,就可以注冊所有的Module了脾还。



- (void)registedAnnotationModules
{
    
    NSArray<NSString *>*mods = [BHAnnotation AnnotationModules];
    for (NSString *modName in mods) {
        Class cls;
        if (modName) {
            cls = NSClassFromString(modName);
            
            if (cls) {
                [self registerDynamicModule:cls];
            }
        }
    }
}


- (void)registerDynamicModule:(Class)moduleClass
{
    [self addModuleFromObject:moduleClass];
 
}


最后還需要把所有已經(jīng)注冊的Module添加到BHModuleManager里面捌显。


- (void)addModuleFromObject:(id)object
{
    Class class;
    NSString *moduleName = nil;
    
    if (object) {
        class = object;
        moduleName = NSStringFromClass(class);
    } else {
        return ;
    }
    
    if ([class conformsToProtocol:@protocol(BHModuleProtocol)]) {
        NSMutableDictionary *moduleInfo = [NSMutableDictionary dictionary];
        
        // basicModuleLevel 這個方法如果默認不實現(xiàn)氮块,Level默認是Normal
        BOOL responseBasicLevel = [class instancesRespondToSelector:@selector(basicModuleLevel)];

        // Level是BHModuleNormal了赌,就是1
        int levelInt = 1;
        
        // 如果實現(xiàn)了basicModuleLevel方法割按,那么Level就是BHModuleBasic
        if (responseBasicLevel) {
            // Level是Basic悟狱,BHModuleBasic就是0
            levelInt = 0;
        }
        
        // @"moduleLevel" 為Key静浴,Level為Value
        [moduleInfo setObject:@(levelInt) forKey:kModuleInfoLevelKey];
        if (moduleName) {
            // @"moduleClass"為Key,moduleName為Value
            [moduleInfo setObject:moduleName forKey:kModuleInfoNameKey];
        }

        [self.BHModules addObject:moduleInfo];
    }
}

一些需要說明已經(jīng)在上述代碼里面添加了注釋芽淡。BHModules是一個NSMutableArray马绝,里面存的都是一個個的字典,字典里面有兩個Key挣菲,一個是@"moduleLevel"富稻,另一個是@"moduleClass"。存儲已經(jīng)注冊的Module的時候都要判斷Level白胀。還有一點需要說明的椭赋,所有需要注冊的Module必須遵循BHModuleProtocol協(xié)議,否則不能被存儲或杠。

2. 讀取本地Pilst文件

要讀取本地的Plist文件之前哪怔,需要先設置好路徑。


    [BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/BeeHive";//可選向抢,默認為BeeHive.bundle/BeeHive.plist

BeeHive所有的配置都可以寫在BHContext進行傳遞认境。

Plist文件的格式也要是數(shù)組里面包一個個的字典。字典里面有兩個Key挟鸠,一個是@"moduleLevel"叉信,另一個是@"moduleClass"。注意根的數(shù)組的名字叫@“moduleClasses”艘希。



- (void)loadLocalModules
{
    
    NSString *plistPath = [[NSBundle mainBundle] pathForResource:[BHContext shareInstance].moduleConfigName ofType:@"plist"];
    if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) {
        return;
    }

    NSDictionary *moduleList = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
    
    NSArray *modulesArray = [moduleList objectForKey:kModuleArrayKey];
    
    [self.BHModules addObjectsFromArray:modulesArray];
    
}


從Plist里面取出數(shù)組硼身,然后把數(shù)組加入到BHModules數(shù)組里面。

3. Load方法注冊

最后一種注冊Module的方法就是在Load方法里面注冊Module的類覆享。


+ (void)load
{
    [BeeHive registerDynamicModule:[self class]];
}


調(diào)用BeeHive里面的registerDynamicModule:完成Module的注冊佳遂。



+ (void)registerDynamicModule:(Class)moduleClass
{
    [[BHModuleManager sharedManager] registerDynamicModule:moduleClass];
}



BeeHive里面的registerDynamicModule:的實現(xiàn)還是調(diào)用的BHModuleManager的注冊方法registerDynamicModule:



- (void)registerDynamicModule:(Class)moduleClass
{
    [self addModuleFromObject:moduleClass];
 
}


最后還是調(diào)用到了BHModuleManager里面的addModuleFromObject:方法,這個方法上面分析過了撒顿,不再贅述丑罪。

Load方法還可以用一個宏BH_EXPORT_MODULE來完成。




#define BH_EXPORT_MODULE(isAsync) \
+ (void)load { [BeeHive registerDynamicModule:[self class]]; } \
-(BOOL)async { return [[NSString stringWithUTF8String:#isAsync] boolValue];}


BH_EXPORT_MODULE宏里面可以傳入一個參數(shù),代表是否異步加載Module模塊巍糯,如果是YES就是異步加載啸驯,如果是NO就是同步加載。

注冊的三種方式就完成了祟峦。最后BeeHive還會對這些Module的Class進行一下操作罚斗。

首先在BeeHive初始化setContext:的時候,會分別加載Modules和Services宅楞。這里先談Modules针姿。


-(void)setContext:(BHContext *)context
{
    _context = context;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self loadStaticServices];
        [self loadStaticModules];
    });
}


看看loadStaticModules方法里面做了什么事情。



- (void)loadStaticModules
{
    // 讀取本地plist文件里面的Module厌衙,并注冊到BHModuleManager的BHModules數(shù)組中
    [[BHModuleManager sharedManager] loadLocalModules];
    
    // 讀取特殊段里面的標記數(shù)據(jù)距淫,并注冊到BHModuleManager的BHModules數(shù)組中
    [[BHModuleManager sharedManager] registedAnnotationModules];

    [[BHModuleManager sharedManager] registedAllModules];
    
}


這里雖然我們只看到了兩種方式,但是實際上BHModules數(shù)組里面還會包括通過Load方法注冊進來的Module婶希。那么BHModules數(shù)組實際上是包含了3種注冊方式加進來的Module榕暇。

最后一步,registedAllModules比較關鍵喻杈。


- (void)registedAllModules
{

    // 根絕優(yōu)先級從大到小進行排序
    [self.BHModules sortUsingComparator:^NSComparisonResult(NSDictionary *module1, NSDictionary *module2) {
      NSNumber *module1Level = (NSNumber *)[module1 objectForKey:kModuleInfoLevelKey];
      NSNumber *module2Level =  (NSNumber *)[module2 objectForKey:kModuleInfoLevelKey];
        
        return [module1Level intValue] > [module2Level intValue];
    }];
    
    NSMutableArray *tmpArray = [NSMutableArray array];
    
    //module init
    [self.BHModules enumerateObjectsUsingBlock:^(NSDictionary *module, NSUInteger idx, BOOL * _Nonnull stop) {
        
        NSString *classStr = [module objectForKey:kModuleInfoNameKey];
        
        Class moduleClass = NSClassFromString(classStr);
        
        if (NSStringFromClass(moduleClass)) {
            
            // 初始化所有的Module
            id<BHModuleProtocol> moduleInstance = [[moduleClass alloc] init];
            [tmpArray addObject:moduleInstance];
        }
        
    }];
    
    [self.BHModules removeAllObjects];

    [self.BHModules addObjectsFromArray:tmpArray];
    
}


BHModules數(shù)組在進行registedAllModules方法之前彤枢,裝的都是一個個的字典,再執(zhí)行完registedAllModules方法之后筒饰,里面裝的就都是一個個的Module的實例了缴啡。

registedAllModules方法會先按照Level的優(yōu)先級從大到小進行排序,然后再按照這個順序依次初始化所有的Module的實例瓷们,存入數(shù)組中业栅。最終BHModules數(shù)組里面裝的是所有的Module實例對象。

注意谬晕,這里有兩點需要額外說明:

  1. 限制住了所有的Module的對象都要是遵守BHModuleProtocol協(xié)議的碘裕。至于為何要遵守BHModuleProtocol協(xié)議,下一章節(jié)會有詳細說明攒钳。
  2. Module不能在任何其他地方alloc創(chuàng)建出來娘汞,即使創(chuàng)建一個新的Module實例出來,它也并不在BHModuleManager的管理下夕玩,是無法接收BHModuleManager分發(fā)的系統(tǒng)事件,創(chuàng)建出來是沒有任何意義的惊豺。

三. BeeHive模塊事件

BeeHive會給每個模塊提供生命周期事件燎孟,用于與BeeHive宿主環(huán)境進行必要信息交互,感知模塊生命周期的變化尸昧。

BeeHive各個模塊會收到一些事件揩页。在BHModuleManager中,所有的事件被定義成了BHModuleEventType枚舉烹俗。



typedef NS_ENUM(NSInteger, BHModuleEventType)
{
    BHMSetupEvent = 0,
    BHMInitEvent,
    BHMTearDownEvent,
    BHMSplashEvent,
    BHMQuickActionEvent,
    BHMWillResignActiveEvent,
    BHMDidEnterBackgroundEvent,
    BHMWillEnterForegroundEvent,
    BHMDidBecomeActiveEvent,
    BHMWillTerminateEvent,
    BHMUnmountEvent,
    BHMOpenURLEvent,
    BHMDidReceiveMemoryWarningEvent,
    BHMDidFailToRegisterForRemoteNotificationsEvent,
    BHMDidRegisterForRemoteNotificationsEvent,
    BHMDidReceiveRemoteNotificationEvent,
    BHMDidReceiveLocalNotificationEvent,
    BHMWillContinueUserActivityEvent,
    BHMContinueUserActivityEvent,
    BHMDidFailToContinueUserActivityEvent,
    BHMDidUpdateUserActivityEvent,
    BHMDidCustomEvent = 1000
    
};


上面BHModuleEventType枚舉主要分為三種爆侣,一種是系統(tǒng)事件萍程,另外一種是應用事件,最后一種是業(yè)務自定義事件兔仰。

1. 系統(tǒng)事件茫负。

上圖是官方給出的一個系統(tǒng)事件基本工作流。

系統(tǒng)事件通常是Application生命周期事件乎赴,例如DidBecomeActive忍法、WillEnterBackground等。

一般做法是把BHAppDelegate接管原來的AppDelegate榕吼。



- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    
    [[BHModuleManager sharedManager] triggerEvent:BHMSetupEvent];
    [[BHModuleManager sharedManager] triggerEvent:BHMInitEvent];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [[BHModuleManager sharedManager] triggerEvent:BHMSplashEvent];
    });
    
    return YES;
}


#if __IPHONE_OS_VERSION_MAX_ALLOWED > 80400 

-(void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler
{
    [[BHModuleManager sharedManager] triggerEvent:BHMQuickActionEvent];
}
#endif

- (void)applicationWillResignActive:(UIApplication *)application
{
    [[BHModuleManager sharedManager] triggerEvent:BHMWillResignActiveEvent];
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidEnterBackgroundEvent];
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    [[BHModuleManager sharedManager] triggerEvent:BHMWillEnterForegroundEvent];
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidBecomeActiveEvent];
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    [[BHModuleManager sharedManager] triggerEvent:BHMWillTerminateEvent];
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
    [[BHModuleManager sharedManager] triggerEvent:BHMOpenURLEvent];
    return YES;
}

#if __IPHONE_OS_VERSION_MAX_ALLOWED > 80400
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options
{
    [[BHModuleManager sharedManager] triggerEvent:BHMOpenURLEvent];
    return YES;
}
#endif


- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveMemoryWarningEvent];
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidFailToRegisterForRemoteNotificationsEvent];
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidRegisterForRemoteNotificationsEvent];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveRemoteNotificationEvent];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveRemoteNotificationEvent];
}

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveLocalNotificationEvent];
}

#if __IPHONE_OS_VERSION_MAX_ALLOWED > 80000
- (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity
{
    if([UIDevice currentDevice].systemVersion.floatValue > 8.0f){
        [[BHModuleManager sharedManager] triggerEvent:BHMDidUpdateUserActivityEvent];
    }
}

- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error
{
    if([UIDevice currentDevice].systemVersion.floatValue > 8.0f){
        [[BHModuleManager sharedManager] triggerEvent:BHMDidFailToContinueUserActivityEvent];
    }
}

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
{
    if([UIDevice currentDevice].systemVersion.floatValue > 8.0f){
        [[BHModuleManager sharedManager] triggerEvent:BHMContinueUserActivityEvent];
    }
    return YES;
}

- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType
{
    if([UIDevice currentDevice].systemVersion.floatValue > 8.0f){
        [[BHModuleManager sharedManager] triggerEvent:BHMWillContinueUserActivityEvent];
    }
    return YES;
}




這樣所有的系統(tǒng)事件都可以通過調(diào)用BHModuleManager的triggerEvent:來處理饿序。

在BHModuleManager中有2個事件很特殊,一個是BHMInitEvent羹蚣,一個是BHMTearDownEvent原探。

先來說說BHMInitEvent事件。



- (void)handleModulesInitEvent
{
    
    [self.BHModules enumerateObjectsUsingBlock:^(id<BHModuleProtocol> moduleInstance, NSUInteger idx, BOOL * _Nonnull stop) {
        __weak typeof(&*self) wself = self;
        void ( ^ bk )();
        bk = ^(){
            __strong typeof(&*self) sself = wself;
            if (sself) {
                if ([moduleInstance respondsToSelector:@selector(modInit:)]) {
                    [moduleInstance modInit:[BHContext shareInstance]];
                }
            }
        };

        [[BHTimeProfiler sharedTimeProfiler] recordEventTime:[NSString stringWithFormat:@"%@ --- modInit:", [moduleInstance class]]];
        
        if ([moduleInstance respondsToSelector:@selector(async)]) {
            BOOL async = [moduleInstance async];
            
            if (async) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    bk();
                });
                
            } else {
                bk();
            }
        } else {
            bk();
        }
    }];
}



Init事件就是初始化Module模塊的事件顽素。遍歷BHModules數(shù)組咽弦,依次對每個Module實例調(diào)用modInit:方法。這里會有異步加載的問題戈抄。如果moduleInstance重寫了async方法离唬,那么就會根據(jù)這個方法返回的值來進行是否異步加載的判斷。

modInit:方法里面干很多事情划鸽。比如說對環(huán)境的判斷输莺,根據(jù)環(huán)境的不同初始化不同的方法。




-(void)modInit:(BHContext *)context
{
    switch (context.env) {
        case BHEnvironmentDev:
            //....初始化開發(fā)環(huán)境
            break;
        case BHEnvironmentProd:
            //....初始化生產(chǎn)環(huán)境
        default:
            break;
    }
}


再比如在初始化的時候注冊一些協(xié)議:



-(void)modInit:(BHContext *)context
{
  [[BeeHive shareInstance] registerService:@protocol(UserTrackServiceProtocol) service:[BHUserTrackViewController class]];
}

總之這里可以干一些初始化需要做的事情裸诽。

再來說說BHMTearDownEvent事件嫂用。這個事件是拆除Module的。



- (void)handleModulesTearDownEvent
{
    //Reverse Order to unload
    for (int i = (int)self.BHModules.count - 1; i >= 0; i--) {
        id<BHModuleProtocol> moduleInstance = [self.BHModules objectAtIndex:i];
        if (moduleInstance && [moduleInstance respondsToSelector:@selector(modTearDown:)]) {
            [moduleInstance modTearDown:[BHContext shareInstance]];
        }
    }
}


由于Module是有優(yōu)先級Level丈冬,所以拆除的時候需要從低優(yōu)先級開始拆嘱函,即數(shù)組逆序循環(huán)。對每個Module實例發(fā)送modTearDown:事件即可埂蕊。

2. 應用事件

官方給出的應用事件工作流如上:

在系統(tǒng)事件的基礎之上往弓,擴展了應用的通用事件,例如modSetup蓄氧、modInit等函似,可以用于編碼實現(xiàn)各插件模塊的設置與初始化。

所有的事件都可以通過調(diào)用BHModuleManager的triggerEvent:來處理喉童。


- (void)triggerEvent:(BHModuleEventType)eventType
{
    switch (eventType) {
        case BHMSetupEvent:
            [self handleModuleEvent:kSetupSelector];
            break;
        case BHMInitEvent:
            //special
            [self handleModulesInitEvent];
            break;
        case BHMTearDownEvent:
            //special
            [self handleModulesTearDownEvent];
            break;
        case BHMSplashEvent:
            [self handleModuleEvent:kSplashSeletor];
            break;
        case BHMWillResignActiveEvent:
            [self handleModuleEvent:kWillResignActiveSelector];
            break;
        case BHMDidEnterBackgroundEvent:
            [self handleModuleEvent:kDidEnterBackgroundSelector];
            break;
        case BHMWillEnterForegroundEvent:
            [self handleModuleEvent:kWillEnterForegroundSelector];
            break;
        case BHMDidBecomeActiveEvent:
            [self handleModuleEvent:kDidBecomeActiveSelector];
            break;
        case BHMWillTerminateEvent:
            [self handleModuleEvent:kWillTerminateSelector];
            break;
        case BHMUnmountEvent:
            [self handleModuleEvent:kUnmountEventSelector];
            break;
        case BHMOpenURLEvent:
            [self handleModuleEvent:kOpenURLSelector];
            break;
        case BHMDidReceiveMemoryWarningEvent:
            [self handleModuleEvent:kDidReceiveMemoryWarningSelector];
            break;
            
        case BHMDidReceiveRemoteNotificationEvent:
            [self handleModuleEvent:kDidReceiveRemoteNotificationsSelector];
            break;

        case BHMDidFailToRegisterForRemoteNotificationsEvent:
            [self handleModuleEvent:kFailToRegisterForRemoteNotificationsSelector];
            break;
        case BHMDidRegisterForRemoteNotificationsEvent:
            [self handleModuleEvent:kDidRegisterForRemoteNotificationsSelector];
            break;
            
        case BHMDidReceiveLocalNotificationEvent:
            [self handleModuleEvent:kDidReceiveLocalNotificationsSelector];
            break;
            
        case BHMWillContinueUserActivityEvent:
            [self handleModuleEvent:kWillContinueUserActivitySelector];
            break;
            
        case BHMContinueUserActivityEvent:
            [self handleModuleEvent:kContinueUserActivitySelector];
            break;
            
        case BHMDidFailToContinueUserActivityEvent:
            [self handleModuleEvent:kFailToContinueUserActivitySelector];
            break;
            
        case BHMDidUpdateUserActivityEvent:
            [self handleModuleEvent:kDidUpdateContinueUserActivitySelector];
            break;
            
        case BHMQuickActionEvent:
            [self handleModuleEvent:kQuickActionSelector];
            break;
            
        default:
            [BHContext shareInstance].customEvent = eventType;
            [self handleModuleEvent:kAppCustomSelector];
            break;
    }
}



從上述代碼可以看出撇寞,除去BHMInitEvent初始化事件和BHMTearDownEvent拆除Module事件這兩個特殊事件以外,所有的事件都是調(diào)用的handleModuleEvent:方法。上述的switch-case里面蔑担,除去系統(tǒng)事件以外的牌废,和default里面的customEvent以外,剩下的事件都是應用事件啤握。


- (void)handleModuleEvent:(NSString *)selectorStr
{
    SEL seletor = NSSelectorFromString(selectorStr);
    [self.BHModules enumerateObjectsUsingBlock:^(id<BHModuleProtocol> moduleInstance, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([moduleInstance respondsToSelector:seletor]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            [moduleInstance performSelector:seletor withObject:[BHContext shareInstance]];
#pragma clang diagnostic pop

        [[BHTimeProfiler sharedTimeProfiler] recordEventTime:[NSString stringWithFormat:@"%@ --- %@", [moduleInstance class], NSStringFromSelector(seletor)]];

        }
    }];
}


handleModuleEvent:方法的實現(xiàn)就是遍歷BHModules數(shù)組鸟缕,調(diào)用performSelector:withObject:方法實現(xiàn)對應方法調(diào)用。

注意這里所有的Module必須是遵循BHModuleProtocol的恨统,否則無法接收到這些事件的消息叁扫。

3. 業(yè)務自定義事件

如果覺得系統(tǒng)事件、通用事件不足以滿足需要畜埋,我們還將事件封裝簡化成BHAppdelgate莫绣,你可以通過繼承 BHAppdelegate來擴展自己的事件。

自定義的事件的type就是BHMDidCustomEvent = 1000 悠鞍。

在BeeHive里面有一個tiggerCustomEvent:方法就是用來處理這些事件的对室,尤其是處理自定義事件的。


- (void)tiggerCustomEvent:(NSInteger)eventType
{
    if(eventType < 1000) {
        return;
    }
    
    [[BHModuleManager sharedManager] triggerEvent:eventType];
}

這個方法只會把自定義事件透傳給BHModuleManager進行處理咖祭,其他一切的事件都不會做任何相應掩宜。

四. BeeHive模塊調(diào)用

在BeeHive中是通過BHServiceManager來管理各個Protocol的。BHServiceManager中只會管理已經(jīng)被注冊過的Protocol么翰。

注冊Protocol的方式總共有三種牺汤,和注冊Module是一樣一一對應的:

1. Annotation方式注冊

通過BeeHiveService宏進行Annotation標記。


BeeHiveService(HomeServiceProtocol,BHViewController)

BeeHiveService宏定義如下:



#define BeeHiveService(servicename,impl) \
char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ \""#servicename"\" : \""#impl"\"}";


BeeHiveDATA又是一個宏:


#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))


最終BeeHiveService宏會在預編譯結(jié)束會完全展開成下面的樣子:


char * kHomeServiceProtocol_service __attribute((used, section("__DATA,""BeehiveServices"" "))) = "{ \"""HomeServiceProtocol""\" : \"""BHViewController""\"}";

這里類比注冊Module浩嫌,也是把數(shù)據(jù)存在特殊的段內(nèi)檐迟,具體原理上面已經(jīng)分析過了,這里不再贅述码耐。

同理追迟,通過調(diào)用static函數(shù)BHReadConfiguration,我們就可以拿到之前注冊到BeehiveServices特殊段里面的各個Protocol協(xié)議對應Class字典的字符串骚腥。


    "{ \"HomeServiceProtocol\" : \"BHViewController\"}"


數(shù)組里面存的都是這樣的一些Json字符串敦间。


+ (NSArray<NSString *> *)AnnotationServices
{
    static NSArray<NSString *> *services = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        services = BHReadConfiguration(BeehiveServiceSectName);
    });
    return services;
}


這是一個單例數(shù)組,里面裝的都是之前放在特殊段里面的Protocol協(xié)議對應Class字典的字符串數(shù)組束铭,即為Json字符串數(shù)組廓块。

拿到這個數(shù)組以后,就可以注冊所有的Protocol協(xié)議了契沫。



- (void)registerAnnotationServices
{
    NSArray<NSString *>*services = [BHAnnotation AnnotationServices];
    
    for (NSString *map in services) {
        NSData *jsonData =  [map dataUsingEncoding:NSUTF8StringEncoding];
        NSError *error = nil;
        id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
        if (!error) {
            if ([json isKindOfClass:[NSDictionary class]] && [json allKeys].count) {
                
                NSString *protocol = [json allKeys][0];
                NSString *clsName  = [json allValues][0];
                
                if (protocol && clsName) {
                    [self registerService:NSProtocolFromString(protocol) implClass:NSClassFromString(clsName)];
                }
                
            }
        }
    }

}

由于services數(shù)組里面存的都是Json字符串带猴,所以先轉(zhuǎn)換成字典,然后再依次取出protocol和className埠褪。最后調(diào)用registerService:implClass:方法。



- (void)registerService:(Protocol *)service implClass:(Class)implClass
{
    NSParameterAssert(service != nil);
    NSParameterAssert(implClass != nil);
    
    // impClass 是否遵循了 Protocol 協(xié)議
    if (![implClass conformsToProtocol:service] && self.enableException) {
        @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ module does not comply with %@ protocol", NSStringFromClass(implClass), NSStringFromProtocol(service)] userInfo:nil];
    }
    
    // Protocol 協(xié)議是否已經(jīng)注冊過了
    if ([self checkValidService:service] && self.enableException) {
        @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ protocol has been registed", NSStringFromProtocol(service)] userInfo:nil];
    }
    
    NSMutableDictionary *serviceInfo = [NSMutableDictionary dictionary];
    [serviceInfo setObject:NSStringFromProtocol(service) forKey:kService];
    [serviceInfo setObject:NSStringFromClass(implClass) forKey:kImpl];
    
    [self.lock lock];
    [self.allServices addObject:serviceInfo];
    [self.lock unlock];
}

在注冊registerService:implClass:之前會有2個檢查,一是檢查impClass 是否遵循了 Protocol 協(xié)議钞速,二是檢查Protocol 協(xié)議是否已經(jīng)注冊過了贷掖。如果有一個檢查出現(xiàn)問題,都會拋出異常渴语。

如果檢查都過了苹威,那么就加入Key為@"service"的,Value為Protocol的名字驾凶,和Key為@“impl”的牙甫,Value為Class名字的兩個鍵值對。最后把這個字典存入allServices數(shù)組中调违。

在存儲allServices數(shù)組的時候窟哺,是要加鎖的。這里的lock是NSRecursiveLock技肩。防止出現(xiàn)遞歸引起的線程安全問題且轨。

2. 讀取本地Pilst文件

要讀取本地的Plist文件之前,需要先設置好路徑虚婿。


[BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/BHService";

BeeHive所有的配置都可以寫在BHContext進行傳遞旋奢。

Plist文件的格式也要是數(shù)組里面包一個個的字典。字典里面有兩個Key然痊,一個是@"service"至朗,另一個是@"impl"。



- (void)registerLocalServices
{
    NSString *serviceConfigName = [BHContext shareInstance].serviceConfigName;
    
    NSString *plistPath = [[NSBundle mainBundle] pathForResource:serviceConfigName ofType:@"plist"];
    if (!plistPath) {
        return;
    }
    
    NSArray *serviceList = [[NSArray alloc] initWithContentsOfFile:plistPath];
    
    [self.lock lock];
    [self.allServices addObjectsFromArray:serviceList];
    [self.lock unlock];
}


從Plist里面取出數(shù)組锹引,然后把數(shù)組加入到allServices數(shù)組里面。

3. Load方法注冊

最后一種注冊Protocol的方法就是在Load方法里面注冊Protocol協(xié)議辛蚊。



+ (void)load
{
   [[BeeHive shareInstance] registerService:@protocol(UserTrackServiceProtocol) service:[BHUserTrackViewController class]];
}

調(diào)用BeeHive里面的registerService:service:完成Module的注冊粤蝎。



- (void)registerService:(Protocol *)proto service:(Class) serviceClass
{
    [[BHServiceManager sharedManager] registerService:proto implClass:serviceClass];
}

BeeHive里面的registerService:service:的實現(xiàn)還是調(diào)用的BHServiceManager的注冊方法registerService:implClass:。這個方法上面分析過了袋马,不再贅述初澎。

至此,3種注冊Protocol的方式就完成了虑凛。

在之前分析注冊Module的時候碑宴,我們知道在BeeHive在setContext:的時候會調(diào)用loadStaticServices方法。



-(void)loadStaticServices
{
    // 是否開啟異常檢測
    [BHServiceManager sharedManager].enableException = self.enableException;
    
    // 讀取本地plist文件里面的Protocol桑谍,并注冊到BHServiceManager的allServices數(shù)組中
    [[BHServiceManager sharedManager] registerLocalServices];
    
    // 讀取特殊段里面的標記數(shù)據(jù)延柠,并注冊到BHServiceManager的allServices數(shù)組中
    [[BHServiceManager sharedManager] registerAnnotationServices];
    
}

這里雖然我們只看到了兩種方式,但是實際上allServices數(shù)組里面還會包括通過Load方法注冊進來的Protocol锣披。那么allServices數(shù)組實際上是包含了3種注冊方式加進來的Protocol贞间。

這里就沒有注冊Module的最后一步初始化實例的過程贿条。

但是Protocol比Module多一個方法,返回能相應Protocol實例對象的方法增热。

在BeeHive中有這樣一個方法整以,調(diào)用這個方法就可以返回一個能相應Protocol的實例對象。


- (id)createService:(Protocol *)proto;

- (id)createService:(Protocol *)proto;
{
    return [[BHServiceManager sharedManager] createService:proto];
}


實質(zhì)是調(diào)用了BHServiceManager的createService:方法峻仇。createService:方法具體實現(xiàn)如下:


- (id)createService:(Protocol *)service
{
    id implInstance = nil;
    
    // Protocol 協(xié)議是否已經(jīng)注冊過了
    if (![self checkValidService:service] && self.enableException) {
        @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ protocol does not been registed", NSStringFromProtocol(service)] userInfo:nil];
    }
    
    Class implClass = [self serviceImplClass:service];
    
    if ([[implClass class] respondsToSelector:@selector(shareInstance)])
        implInstance = [[implClass class] shareInstance];
    else
        implInstance = [[implClass alloc] init];
    
    if (![implInstance respondsToSelector:@selector(singleton)]) {
        return implInstance;
    }
    
    NSString *serviceStr = NSStringFromProtocol(service);
    
    // 是否需要緩存
    if ([implInstance singleton]) {
        id protocol = [[BHContext shareInstance] getServiceInstanceFromServiceName:serviceStr];
        
        if (protocol) {
            return protocol;
        } else {
            [[BHContext shareInstance] addServiceWithImplInstance:implInstance serviceName:serviceStr];
        }
        
    } else {
        [[BHContext shareInstance] addServiceWithImplInstance:implInstance serviceName:serviceStr];
    }
    
    return implInstance;
}

這個方法也會先檢查Protocol協(xié)議是否是注冊過的公黑。然后接著取出字典里面對應的Class,如果實現(xiàn)了shareInstance方法摄咆,那么就生成一個單例出來凡蚜,如果沒有,那么就隨便生成一個對象出來吭从。如果還實現(xiàn)了singleton朝蜘,就能進一步的把implInstance和serviceStr對應的加到BHContext的servicesByName字典里面緩存起來。這樣就可以隨著上下文傳遞了影锈。


id<UserTrackServiceProtocol> v4 = [[BeeHive shareInstance] createService:@protocol(UserTrackServiceProtocol)];
if ([v4 isKindOfClass:[UIViewController class]]) {
    [self registerViewController:(UIViewController *)v4 title:@"埋點3" iconName:nil];
}


上面是官方給的例子芹务,Module之間的調(diào)用就用這種方式,就可以得到很好的解耦了鸭廷。

五. 其他的一些輔助類

還有一些輔助類枣抱,在上面沒有提到的,這里就來一個匯總辆床,一起分析了佳晶。

BHConfig這也是一個單例,里面保存了一個config的NSMutableDictionary字典讼载。字典維護了一些動態(tài)的環(huán)境變量轿秧,作為BHContext的補充存在。

BHContext也是一個單例咨堤,里面有2個NSMutableDictionary字典菇篡,一個是modulesByName,另一個是servicesByName一喘。BHContext主要就是用來保存各種上下文環(huán)境的驱还。


@interface BHContext : NSObject

//global env
@property(nonatomic, assign) BHEnvironmentType env;

//global config
@property(nonatomic, strong) BHConfig *config;

//application appkey
@property(nonatomic, strong) NSString *appkey;
//customEvent>=1000
@property(nonatomic, assign) NSInteger customEvent;

@property(nonatomic, strong) UIApplication *application;

@property(nonatomic, strong) NSDictionary *launchOptions;

@property(nonatomic, strong) NSString *moduleConfigName;

@property(nonatomic, strong) NSString *serviceConfigName;

//3D-Touch model
#if __IPHONE_OS_VERSION_MAX_ALLOWED > 80400
@property (nonatomic, strong) BHShortcutItem *touchShortcutItem;
#endif

//OpenURL model
@property (nonatomic, strong) BHOpenURLItem *openURLItem;

//Notifications Remote or Local
@property (nonatomic, strong) BHNotificationsItem *notificationsItem;

//user Activity Model
@property (nonatomic, strong) BHUserActivityItem *userActivityItem;

@end



在application:didFinishLaunchingWithOptions:的時候,就可以初始化大量的上下文信息凸克。


    [BHContext shareInstance].application = application;
    [BHContext shareInstance].launchOptions = launchOptions;
    [BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/BeeHive";//可選议蟆,默認為BeeHive.bundle/BeeHive.plist
    [BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/BHService";

BHTimeProfiler就是用來進行計算時間性能方面的Profiler。

BHWatchDog是可以開一個線程萎战,設置好handler咐容,每隔一段時間就執(zhí)行一個handler。

六. 可能還在完善中的功能

BeeHive通過處理Event編寫各個業(yè)務模塊可以實現(xiàn)插件化編程蚂维,各業(yè)務模塊之間沒有任何依賴戳粒,core與module之間通過event交互路狮,實現(xiàn)了插件隔離。但有時候需要模塊間的相互調(diào)用某些功能來協(xié)同完成功能蔚约。

1. 功能還有待完善

通常會有三種形式的接口訪問形式:

  1. 基于接口的實現(xiàn)Service訪問方式(Java spring框架實現(xiàn))
  2. 基于函數(shù)調(diào)用約定實現(xiàn)的Export Method(PHP的extension览祖,ReactNative的擴展機制)
  3. 基于跨應用實現(xiàn)的URL Route模式(iPhone App之間的互訪)

BeeHive目前只實現(xiàn)了第一種方式,后兩種方式還需要繼續(xù)完善炊琉。

2. 解耦還不夠徹底

基于接口Service訪問的優(yōu)點是可以編譯時檢查發(fā)現(xiàn)接口的變更,從而及時修正接口問題又活。缺點是需要依賴接口定義的頭文件苔咪,通過模塊增加得越多,維護接口定義的也有一定工作量柳骄。

3. 設計思路還可以繼續(xù)改進和優(yōu)化

BHServiceManager內(nèi)部維護了一個數(shù)組团赏,數(shù)組中的一個個字典,Key為@"service"的耐薯,Value為Protocol的名字舔清,和Key為@“impl”的,Value為Class名字的兩個鍵值對曲初。與其這樣設計体谒,還不如直接使用NSMutableDictionary,Key使用Protocol臼婆,Value為Class呢抒痒?搜索的時候減少了手動循環(huán)過程。

結(jié)尾

BeeHive作為阿里開源的一套模塊間的解耦方案颁褂,思路還是很值得我們學習的故响。目前版本是v1.2.0,相信在后面的版本迭代更新中颁独,功能會更加的完善彩届,做法會更加的優(yōu)雅,值得期待誓酒!

更新:

有些同學問樟蠕,宏展開怎么有那么多雙引號的問題:


#define BeeHiveMod(name) \
class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";

#define BeeHiveService(servicename,impl) \
class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ \""#servicename"\" : \""#impl"\"}";

首先 #在宏里面代表的是把后面連接的轉(zhuǎn)換為字符串。例如丰捷,#servicename 就是把 servicename 代替的轉(zhuǎn)換成字符串坯墨。再外面一層雙引號,是起到了類似占位符的作用病往,是為了分離開前面雙引號捣染。如果直接寫 "#servicename" ,你會發(fā)現(xiàn)宏進行預編譯的時候停巷,并不會把 #servicename 整個當初替換量進行替換耍攘,而是把整個雙引號括起來的當做一個整的字符串榕栏。那這樣就達不到我們要替換的目的了。所以需要再加一層雙引號蕾各,即 "#servicename" 扒磁。最后標準的 Json ,每個 key 和 value 都是帶雙引號的式曲,所以我們外層再加上一層經(jīng)過轉(zhuǎn)義字符轉(zhuǎn)義后的雙引號妨托。于是就得到了最終的樣子—— ""#servicename"" 。

于是乎吝羞,經(jīng)過預編譯的替換兰伤,就會產(chǎn)生下列的結(jié)果,很多個雙引號钧排。



char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"" "))) = """ShopModule""";




char * kHomeServiceProtocol_service __attribute((used, section("__DATA,""BeehiveServices"" "))) = "{ \"""HomeServiceProtocol""\" : \"""BHViewController""\"}";

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末敦腔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子恨溜,更是在濱河造成了極大的恐慌符衔,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糟袁,死亡現(xiàn)場離奇詭異判族,居然都是意外死亡,警方通過查閱死者的電腦和手機项戴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門五嫂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肯尺,你說我怎么就攤上這事沃缘。” “怎么了则吟?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵槐臀,是天一觀的道長。 經(jīng)常有香客問我氓仲,道長水慨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任敬扛,我火速辦了婚禮晰洒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘啥箭。我一直安慰自己谍珊,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布急侥。 她就那樣靜靜地躺著砌滞,像睡著了一般侮邀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贝润,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天绊茧,我揣著相機與錄音,去河邊找鬼打掘。 笑死华畏,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的尊蚁。 我是一名探鬼主播唯绍,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼枝誊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起惜纸,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤叶撒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后耐版,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祠够,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年粪牲,在試婚紗的時候發(fā)現(xiàn)自己被綠了古瓤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡腺阳,死狀恐怖落君,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情亭引,我是刑警寧澤绎速,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站焙蚓,受9級特大地震影響纹冤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜购公,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一萌京、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宏浩,春花似錦知残、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽较坛。三九已至,卻和暖如春扒最,著一層夾襖步出監(jiān)牢的瞬間丑勤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工吧趣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留法竞,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓强挫,卻偏偏與公主長得像岔霸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子俯渤,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,070評論 25 707
  • 路由是實現(xiàn)模塊間解耦的一個有效工具呆细。如果要進行組件化開發(fā),路由是必不可少的一部分八匠。目前iOS上絕大部分的路由工具都...
    黑超熊貓zuik閱讀 3,925評論 8 52
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理絮爷,服務發(fā)現(xiàn),斷路器梨树,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 過去的總是會過去今天我只是莫名其妙的想到了這一句話 馬上就要走了馬上又要走了但是走去哪里我現(xiàn)在還不知道 就像以前的...
    西海有蘭舟閱讀 551評論 0 2
  • 踩踩踩
    干蟄閱讀 126評論 0 0