MagicalRecord源碼分析

MagicalRecord是一個簡化CoreData操作的工具庫,既然用到了這個庫幌缝,那就看一下源碼了解一下這個庫是怎么工作的,我閱讀的版本是2.3.2。

MagicalRecord的好處

  • 清理我的Core Data相關(guān)代碼
  • 支持清晰,簡單,一行代碼式的查詢
  • 當(dāng)需要優(yōu)化請求時,仍然可以修改 NSFetchRequest

MagicalRecord源碼

MagicalRecord的初始化

我們使用CoreData通常是這樣的:

  1. 根據(jù)momd路徑創(chuàng)建托管對象模型
  2. 根據(jù)托管對象模型創(chuàng)建持久化存儲協(xié)調(diào)器
  3. 創(chuàng)建并關(guān)聯(lián)SQLite數(shù)據(jù)庫文件
  4. 創(chuàng)建管理對象上下文并指定持久化存儲協(xié)調(diào)器
// 創(chuàng)建托管對象模型红竭,并使用CoreData.momd路徑當(dāng)做初始化參數(shù)
NSURL *modelPath = [[NSBundle mainBundle] URLForResource:@"CoreData" withExtension:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelPath];

// 根據(jù)托管對象模型創(chuàng)建持久化存儲協(xié)調(diào)器
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];

// 創(chuàng)建并關(guān)聯(lián)SQLite數(shù)據(jù)庫文件,如果已經(jīng)存在則不會重復(fù)創(chuàng)建
NSString *dataPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
dataPath = [dataPath stringByAppendingFormat:@"/%@.sqlite", @"CoreData"];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:dataPath] options:nil error:nil];

// 創(chuàng)建上下文對象喘落,并發(fā)隊列設(shè)置為主隊列
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

// 指定持久化存儲協(xié)調(diào)器
context.persistentStoreCoordinator = coordinator;

而使用MagicalRecord我們只需要調(diào)用[MagicalRecord setupCoreDataStack]
這個方法的具體實現(xiàn)是

+ (void) setupCoreDataStack
{
    [self setupCoreDataStackWithStoreNamed:[self defaultStoreName]];
}

+ (void) setupCoreDataStackWithStoreNamed:(NSString *)storeName
{
    //判斷默認持久化存儲協(xié)調(diào)器是否存在
    if ([NSPersistentStoreCoordinator MR_defaultStoreCoordinator] != nil) return;
    //不存在就創(chuàng)建持久化存儲協(xié)調(diào)器
    NSPersistentStoreCoordinator *coordinator = [NSPersistentStoreCoordinator MR_coordinatorWithSqliteStoreNamed:storeName];
    //設(shè)置默認持久化存儲協(xié)調(diào)器
    [NSPersistentStoreCoordinator MR_setDefaultStoreCoordinator:coordinator];
    //創(chuàng)建NSManagedObjectContext
    [NSManagedObjectContext MR_initializeDefaultContextWithCoordinator:coordinator];
}

創(chuàng)建持久化存儲協(xié)調(diào)器

+ (NSPersistentStoreCoordinator *) MR_coordinatorWithSqliteStoreNamed:(NSString *)storeFileName withOptions:(NSDictionary *)options
{
    //獲取默認托管對象模型并創(chuàng)建持久化存儲協(xié)調(diào)器
    NSManagedObjectModel *model = [NSManagedObjectModel MR_defaultManagedObjectModel];
    NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    //添加持久化存儲到協(xié)調(diào)器
    [psc MR_addSqliteStoreNamed:storeFileName withOptions:options];
    return psc;
}

添加SQLite存儲

- (NSPersistentStore *) MR_addSqliteStoreNamed:(id)storeFileName configuration:(NSString *)configuration withOptions:(__autoreleasing NSDictionary *)options
{
    //持久化存儲URL
    NSURL *url = [storeFileName isKindOfClass:[NSURL class]] ? storeFileName : [NSPersistentStore MR_urlForStoreName:storeFileName];
    NSError *error = nil;
    //創(chuàng)建持久化存儲文件
    [self MR_createPathToStoreFileIfNeccessary:url];
    
    NSPersistentStore *store = [self addPersistentStoreWithType:NSSQLiteStoreType
                                                  configuration:configuration
                                                            URL:url
                                                        options:options
                                                          error:&error];
    
    //數(shù)據(jù)庫不存在
    if (!store)
    {
        /*
          如果工程有DEBUG標記茵宪,則kMagicalRecordShouldDeleteStoreOnModelMismatch被設(shè)為YES
          此時使用默認的SQLite數(shù)據(jù)存儲,不創(chuàng)建新的版本的數(shù)據(jù)模型而是直接改變數(shù)據(jù)模型本身的方式,將會刪除舊的存儲并自動創(chuàng)建一個新的瘦棋。
          這會節(jié)省大量的時間 - 不再需要在改變數(shù)據(jù)模型后每次都重新卸載和安裝應(yīng)用
        */
        if ([MagicalRecord shouldDeleteStoreOnModelMismatch])
        {
            BOOL isMigrationError = (([error code] == NSPersistentStoreIncompatibleVersionHashError) || ([error code] == NSMigrationMissingSourceModelError) || ([error code] == NSMigrationError));
            if ([[error domain] isEqualToString:NSCocoaErrorDomain] && isMigrationError)
            {
                [[NSNotificationCenter defaultCenter] postNotificationName:kMagicalRecordPSCMismatchWillDeleteStore object:nil];
                
                NSError * deleteStoreError;
                // 無法打開數(shù)據(jù)庫就刪除
                NSString *rawURL = [url absoluteString];
                NSURL *shmSidecar = [NSURL URLWithString:[rawURL stringByAppendingString:@"-shm"]];
                NSURL *walSidecar = [NSURL URLWithString:[rawURL stringByAppendingString:@"-wal"]];
                [[NSFileManager defaultManager] removeItemAtURL:url error:&deleteStoreError];
                [[NSFileManager defaultManager] removeItemAtURL:shmSidecar error:nil];
                [[NSFileManager defaultManager] removeItemAtURL:walSidecar error:nil];
                
                MRLogWarn(@"Removed incompatible model version: %@", [url lastPathComponent]);
                if(deleteStoreError) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kMagicalRecordPSCMismatchCouldNotDeleteStore object:nil userInfo:@{@"Error":deleteStoreError}];
                }
                else {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kMagicalRecordPSCMismatchDidDeleteStore object:nil];
                }
                
                [[NSNotificationCenter defaultCenter] postNotificationName:kMagicalRecordPSCMismatchWillRecreateStore object:nil];
                // 再次創(chuàng)建持久化存儲
                store = [self addPersistentStoreWithType:NSSQLiteStoreType
                                           configuration:nil
                                                     URL:url
                                                 options:options
                                                   error:&error];
                if (store)
                {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kMagicalRecordPSCMismatchDidRecreateStore object:nil];
                    error = nil;
                }
                else {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kMagicalRecordPSCMismatchCouldNotRecreateStore object:nil userInfo:@{@"Error":error}];
                }
            }
        }
        [MagicalRecord handleErrors:error];
    }
    return store;
}

在平時開發(fā)過程中稀火,經(jīng)常會出現(xiàn)修改模型結(jié)構(gòu)的情況,需要把app卸載了然后重新安裝才能避免打不開數(shù)據(jù)庫導(dǎo)致崩潰的問題赌朋。MagicalRecord的處理方式是在DEBUG模式下將數(shù)據(jù)庫直接刪除重新創(chuàng)建凰狞,可以節(jié)省很多時間。

接著創(chuàng)建管理對象上下文

+ (void) MR_initializeDefaultContextWithCoordinator:(NSPersistentStoreCoordinator *)coordinator;
{
    if (MagicalRecordDefaultContext == nil)
    {
        NSManagedObjectContext *rootContext = [self MR_contextWithStoreCoordinator:coordinator];
        [self MR_setRootSavingContext:rootContext];

        NSManagedObjectContext *defaultContext = [self MR_newMainQueueContext];
        [self MR_setDefaultContext:defaultContext];

        [defaultContext setParentContext:rootContext];
    }
}

Core Data有很多種設(shè)置方式箕慧,常用有3種

  • 兩個上下文服球,一個協(xié)調(diào)器
    一個位于主線程的上下文,一個位于后臺線程的上下文颠焦,共用一個協(xié)調(diào)器斩熊。UI相關(guān)的操作在主線程的上下文上進行,后臺操作放入后臺線程執(zhí)行伐庭。當(dāng)后臺線程上下文保存后粉渠,通過監(jiān)聽NSManagedObjectContextDidSaveNotification來更新主線程的上下文中的數(shù)據(jù)。把獲取和保存等操作放入后臺線程來避免阻塞主線程圾另。由于使用同一個協(xié)調(diào)器霸株,兩個上下文可以共用同一個行緩存,避免了一些對數(shù)據(jù)庫不必要的訪問集乔,提高了性能去件。但是由于共享一個協(xié)調(diào)器,所以在同一時間只有一個上下文能使用協(xié)調(diào)器扰路。并且要設(shè)置合適的合并策略來解決多個上下文都有更改是造成的沖突尤溜。

  • 兩個協(xié)調(diào)器
    兩個獨立的協(xié)調(diào)器,共享同一個SQLite數(shù)據(jù)庫汗唱。創(chuàng)建一個主線程上下文宫莱,連接一個協(xié)調(diào)器,一個后臺線程上下文哩罪,連接一個協(xié)調(diào)器授霸。這樣的設(shè)置可以減少對協(xié)調(diào)器的競爭問題巡验,提供了更好的并發(fā)處理能力,因為SQLite支持多讀單寫碘耳,在不進行多個寫操作時显设,不會產(chǎn)生競爭問題。但是由于行緩存位于協(xié)調(diào)器層藏畅,所以行緩存不能共享敷硅。也就是說如果某一個上下文修改了數(shù)據(jù),另一個上下文需要獲取到這個數(shù)據(jù)需要下降到數(shù)據(jù)庫層愉阎。訪問數(shù)據(jù)庫相比于訪問內(nèi)存效率要低绞蹦。

  • 嵌套上下文
    一個位于主線程的上下文,一個位于后臺線程的上下文榜旦,將后臺線程上下文直接與協(xié)調(diào)器相連幽七,主線程上下文的父上下文設(shè)置為后臺線程上下文。使用這種設(shè)置溅呢,主線程上下文的操作完全在內(nèi)存中進行澡屡,因為所有的操作只會被push到父上下文中,當(dāng)父上下文進行保存操作時咐旧,才會通過持久化存儲協(xié)調(diào)器訪問數(shù)據(jù)庫驶鹉,由于父上下文是一個私有隊列上下文,這些操作不會阻塞UI線程铣墨。

MagicalRecord使用的是嵌套上下文的設(shè)計室埋。原因可能是相比于其他兩種設(shè)計,嵌套上下文使用簡單伊约,管理方便姚淆,并且也能很好的分離UI操作和后臺數(shù)據(jù)操作,雖然性能不是最高的屡律,但是適用于大部分APP腌逢。

MagicalRecord保存

MagicalRecord提供了defaultContext讓我們在主線程處理有關(guān)UI的數(shù)據(jù)。如果我們想在后臺操作數(shù)據(jù)超埋,可以使用+ saveWithBlock:completion:函數(shù)搏讶。這個函數(shù)用于更改實體的Block永遠不會在主線程執(zhí)行,并且提供一個rootContext的子context霍殴。當(dāng)Block中的操作執(zhí)行完畢窍蓝,會回調(diào)completion block,這個block在主線程中調(diào)用繁成,所以可以在此block里安全觸發(fā)UI更新。

+ (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;
{
    //創(chuàng)建一個父上下文是rootContext的上下文淑玫,類型是NSPrivateQueueConcurrencyType
    NSManagedObjectContext *savingContext  = [NSManagedObjectContext MR_rootSavingContext];
    NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextWithParent:savingContext];

    //子上下文通過performBlock回調(diào)到自己的線程中執(zhí)行block
    [localContext performBlock:^{
        [localContext MR_setWorkingName:NSStringFromSelector(_cmd)];

        if (block) {
            block(localContext);
        }
        //保存數(shù)據(jù)
        [localContext MR_saveWithOptions:MRSaveParentContexts completion:completion];
    }];
}

而要進行同步保存的話使用MR_saveToPersistentStoreAndWait函數(shù)

- (void) MR_saveToPersistentStoreAndWait
{
    [self MR_saveWithOptions:MRSaveParentContexts | MRSaveSynchronously completion:nil];
}

在保存選項中增加了同步保存的選項巾腕,如何進行同步和后臺的存儲面睛,讓我們看看保存數(shù)據(jù)的具體代碼

- (void) MR_saveWithOptions:(MRSaveOptions)saveOptions completion:(MRSaveCompletionHandler)completion;
{
    __block BOOL hasChanges = NO;
    
    //判斷上下文是否存在更改,不存在直接返回
    if ([self concurrencyType] == NSConfinementConcurrencyType)
    {
        hasChanges = [self hasChanges];
    }
    else
    {
        [self performBlockAndWait:^{
            hasChanges = [self hasChanges];
        }];
    }

    if (!hasChanges)
    {
        MRLogVerbose(@"NO CHANGES IN ** %@ ** CONTEXT - NOT SAVING", [self MR_workingName]);

        if (completion)
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(NO, nil);
            });
        }

        return;
    }
    //父上下文是否應(yīng)該保存
    BOOL shouldSaveParentContexts = ((saveOptions & MRSaveParentContexts) == MRSaveParentContexts);
    //是否應(yīng)該同步保存
    BOOL shouldSaveSynchronously = ((saveOptions & MRSaveSynchronously) == MRSaveSynchronously);
    //是否應(yīng)該同步保存除了rootContext
    BOOL shouldSaveSynchronouslyExceptRoot = ((saveOptions & MRSaveSynchronouslyExceptRootContext) == MRSaveSynchronouslyExceptRootContext);
    
    //判斷是否同步保存
    BOOL saveSynchronously = (shouldSaveSynchronously && !shouldSaveSynchronouslyExceptRoot) ||
                             (shouldSaveSynchronouslyExceptRoot && (self != [[self class] MR_rootSavingContext]));

    //保存block
    id saveBlock = ^{
        MRLogInfo(@"→ Saving %@", [self MR_description]);
        MRLogVerbose(@"→ Save Parents? %@", shouldSaveParentContexts ? @"YES" : @"NO");
        MRLogVerbose(@"→ Save Synchronously? %@", saveSynchronously ? @"YES" : @"NO");

        BOOL saveResult = NO;
        NSError *error = nil;

        @try
        {
            saveResult = [self save:&error];
        }
        @catch(NSException *exception)
        {
            MRLogError(@"Unable to perform save: %@", (id)[exception userInfo] ?: (id)[exception reason]);
        }
        @finally
        {
            [MagicalRecord handleErrors:error];
            //判斷父上下文是否需要保存
            if (saveResult && shouldSaveParentContexts && [self parentContext])
            {
                // 添加或移除同步選項
                MRSaveOptions modifiedOptions = saveOptions;

                if (saveSynchronously)
                {
                    modifiedOptions |= MRSaveSynchronously;
                }
                else
                {
                    modifiedOptions &= ~MRSaveSynchronously;
                }

                // 父上下文遞歸保存
                [[self parentContext] MR_saveWithOptions:modifiedOptions completion:completion];
            }
            else
            {
                if (saveResult)
                {
                    MRLogVerbose(@"→ Finished saving: %@", [self MR_description]);
                }

                if (completion)
                {
                    //成功回調(diào)主線程
                    dispatch_async(dispatch_get_main_queue(), ^{
                        completion(saveResult, error);
                    });
                }
            }
        }
    };

    if (saveSynchronously)
    {
        //通過performBlockAndWait:調(diào)度到自己的線程中保存并阻塞線程直到block處理結(jié)束
        [self performBlockAndWait:saveBlock];
    }
    else
    {
        //調(diào)度到自己的線程中保存尊搬,不阻塞線程
        [self performBlock:saveBlock];
    }
}

保存數(shù)據(jù)時根據(jù)saveOptions決定是否同步以及父上下文是否保存叁鉴。如果同步,調(diào)用performBlockAndWait:函數(shù)佛寿,否則調(diào)用performBlock:函數(shù)幌墓。通過遞歸調(diào)用的方式進行父上下文的保存直到rootContext將數(shù)據(jù)通過 Persistent Store Coordinator存入數(shù)據(jù)庫。在其它線程保存完畢后冀泻,默認上下文如何更新數(shù)據(jù)呢常侣?在設(shè)置默認上下文時已經(jīng)監(jiān)聽了NSManagedObjectContextDidSaveNotification事件。

if ((MagicalRecordDefaultContext != nil) && ([self MR_rootSavingContext] != nil)) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(rootContextDidSave:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:[self MR_rootSavingContext]];
    }
+ (void)rootContextDidSave:(NSNotification *)notification
{
    //判斷是否是rootContext存儲完畢的通知
    if ([notification object] != [self MR_rootSavingContext])
    {
        return;
    }

    if ([NSThread isMainThread] == NO)
    {
        //不是主線程重新在主線程中調(diào)用rootContextDidSave:
        dispatch_async(dispatch_get_main_queue(), ^{
            [self rootContextDidSave:notification];
        });

        return;
    }

    for (NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey])
    {
        [[[self MR_defaultContext] objectWithID:[object objectID]] willAccessValueForKey:nil];
    }

    [[self MR_defaultContext] mergeChangesFromContextDidSaveNotification:notification];
}

當(dāng)后臺線程操作完數(shù)據(jù)弹渔,默認上下文接到通知合并更改胳施。

MagicalRecord增刪改查

創(chuàng)建實體對象

+ (id) MR_createEntityInContext:(NSManagedObjectContext *)context
{
    if ([self respondsToSelector:@selector(insertInManagedObjectContext:)] && context != nil)
    {
        //判斷是否實現(xiàn)了`insertInManagedObjectContext:`函數(shù),如果可以通過這個函數(shù)創(chuàng)建實體
        id entity = [self performSelector:@selector(insertInManagedObjectContext:) withObject:context];
        return entity;
    }
    else
    {
        //通過entityName生成NSEntityDescription
        NSEntityDescription *entity = nil;
        if (context == nil)
        {
            entity = [self MR_entityDescription];
        }
        else
        {
            entity  = [self MR_entityDescriptionInContext:context];
        }
        
        if (entity == nil)
        {
            return nil;
        }
        //創(chuàng)建entity并返回
        return [[self alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
    }
}

刪除實體

- (BOOL) MR_deleteEntityInContext:(NSManagedObjectContext *)context
{
    NSError *error = nil;
    //跨上下文不能直接訪問實體對象肢专,通過實體對象的ID判斷當(dāng)前上下文中是否存在這個實體對象
    NSManagedObject *entityInContext = [context existingObjectWithID:[self objectID] error:&error];
    [MagicalRecord handleErrors:error];
    //如果實體對象存在就刪除
    if (entityInContext) {
        [context deleteObject:entityInContext];
    }

    return YES;
}

刪除全部實體

+ (BOOL) MR_truncateAllInContext:(NSManagedObjectContext *)context
{
    //創(chuàng)建獲取所有實體的請求
    NSFetchRequest *request = [self MR_requestAllInContext:context];
    //設(shè)置返回值為惰值
    [request setReturnsObjectsAsFaults:YES];
    //只獲取ObjectID
    [request setIncludesPropertyValues:NO];

    //查找要刪除的數(shù)據(jù)
    NSArray *objectsToDelete = [self MR_executeFetchRequest:request inContext:context];
    for (NSManagedObject *objectToDelete in objectsToDelete)
    {
        //刪除數(shù)據(jù)
        [objectToDelete MR_deleteEntityInContext:context];
    }
    return YES;
}

刪除全部實體時舞肆,通過使用惰值和ObjectID減少對內(nèi)存的占用。也可以使用MR_deleteAllMatchingPredicate:inContext:通過指定謂詞來刪除符合條件的數(shù)據(jù)博杖。

MagicalRecord的查找

+ MR_findAllInContext:context
+ MR_findAllSortedBy:ascending:inContext:
+ MR_findAllSortedBy:ascending:withPredicate:inContext:
+ MR_findAllWithPredicate:inContext:

這些查找方法都會調(diào)用MR_executeFetchRequest:inContext:

+ (NSArray *) MR_executeFetchRequest:(NSFetchRequest *)request inContext:(NSManagedObjectContext *)context
{
    __block NSArray *results = nil;
    [context performBlockAndWait:^{

        NSError *error = nil;
        
        results = [context executeFetchRequest:request error:&error];
        
        if (results == nil) 
        {
            [MagicalRecord handleErrors:error];
        }

    }];
    return results; 
}

最后都是通過executeFetchRequest:error:函數(shù)返回查詢結(jié)果椿胯。

MagicalRecord還提供了短方法名,比如用 findAll 代替 MR_findAll剃根,調(diào)用[MagicalRecord enableShorthandMethods]開啟哩盲。通過Runtime的方法交換和動態(tài)方法解析來實現(xiàn)。

+ (void)enableShorthandMethods
{
    if (kMagicalRecordShorthandMethodsSwizzled == NO)
    {
        NSArray *classes = [self classesToSwizzle];
        //將數(shù)組中的類的resolveClassMethod:和resolveInstanceMethod:方法替換
        [classes enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
            Class objectClass = (Class)object;

            [self updateResolveMethodsForClass:objectClass];
        }];

        kMagicalRecordShorthandMethodsSwizzled = YES;
    }
}

+ (NSArray *)classesToSwizzle
{
    return @[ [NSManagedObject class],
              [NSManagedObjectContext class],
              [NSManagedObjectModel class],
              [NSPersistentStore class],
              [NSPersistentStoreCoordinator class] ];
}
+ (void)updateResolveMethodsForClass:(Class)objectClass
{
    MRReplaceSelectorForTargetWithSourceImplementation(self, @selector(MR_resolveClassMethod:), objectClass, @selector(resolveClassMethod:));
    MRReplaceSelectorForTargetWithSourceImplementation(self, @selector(MR_resolveInstanceMethod:), objectClass, @selector(resolveInstanceMethod:));
}

static void MRReplaceSelectorForTargetWithSourceImplementation(Class sourceClass, SEL sourceSelector, Class targetClass, SEL targetSelector)
{

    // 獲取MR_resolveClassMethod:方法
    Method sourceClassMethod = class_getClassMethod(sourceClass, sourceSelector);

    // 獲取resolveInstanceMethod:方法
    Method targetClassMethod = class_getClassMethod(targetClass, targetSelector);

    // 獲取目標類的源類
    Class targetMetaClass = objc_getMetaClass([NSStringFromClass(targetClass) cStringUsingEncoding:NSUTF8StringEncoding]);

    //向目標源類中添加一個方法跟继,SEL為MR_resolveClassMethod:方法的SEL种冬,IMP為resolveInstanceMethod:的IMP
    BOOL methodWasAdded = class_addMethod(targetMetaClass, sourceSelector,
                                          method_getImplementation(targetClassMethod),
                                          method_getTypeEncoding(targetClassMethod));

    if (methodWasAdded)
    {
        //把目標源類中的resolveInstanceMethod:方法的IMP替換為MR_resolveClassMethod:方法的IMP
        class_replaceMethod(targetMetaClass, targetSelector,
                            method_getImplementation(sourceClassMethod),
                            method_getTypeEncoding(sourceClassMethod));
    }
}
static NSString *const kMagicalRecordCategoryPrefix = @"MR_";

+ (BOOL)MR_resolveClassMethod:(SEL)originalSelector
{
    
    BOOL resolvedClassMethod = [self MR_resolveClassMethod:originalSelector];
    if (!resolvedClassMethod)
    {
        // 如果原resolveClassMethod:實現(xiàn)無法解析SEL,向本類添加短方法名的實現(xiàn)
        resolvedClassMethod = MRAddShortHandMethodForPrefixedClassMethod(self, originalSelector, kMagicalRecordCategoryPrefix);
    }
    // 返回YES重新發(fā)送消息舔糖,返回NO則進入消息轉(zhuǎn)發(fā)
    return resolvedClassMethod;
}

+ (BOOL)MR_resolveInstanceMethod:(SEL)originalSelector
{
    BOOL resolvedClassMethod = [self MR_resolveInstanceMethod:originalSelector];
    if (!resolvedClassMethod)
    {
        // 同上
        resolvedClassMethod = MRAddShorthandMethodForPrefixedInstanceMethod(self, originalSelector, kMagicalRecordCategoryPrefix);
    }
    return resolvedClassMethod;
}
static BOOL MRAddShorthandMethodForPrefixedInstanceMethod(Class objectClass, SEL originalSelector, NSString *prefix)
{
    NSString *originalSelectorString = NSStringFromSelector(originalSelector);

    // 根據(jù)名稱判斷是否添加實現(xiàn)
    if ([originalSelectorString hasPrefix:prefix] == NO &&
        ([originalSelectorString hasPrefix:@"_"] || [originalSelectorString hasPrefix:@"init"]))
    {
        // 在短方法名前添加MR_
        NSString *prefixedSelector = [prefix stringByAppendingString:originalSelectorString];

        // 獲取帶MR_的方法
        Method existingMethod = class_getInstanceMethod(objectClass, NSSelectorFromString(prefixedSelector));

        if (existingMethod)
        {
            // 向類中添加SEL為短方法名娱两,IMP為帶MR_的方法實現(xiàn)
            BOOL methodWasAdded = class_addMethod(objectClass,
                                                  originalSelector,
                                                  method_getImplementation(existingMethod),
                                                  method_getTypeEncoding(existingMethod));

            // 返回添加結(jié)果
            return methodWasAdded;
        }
    }
    return NO;
}

static BOOL MRAddShortHandMethodForPrefixedClassMethod(Class objectClass, SEL originalSelector, NSString *prefix)
{
    NSString *originalSelectorString = NSStringFromSelector(originalSelector);

    // 根據(jù)名稱判斷是否添加實現(xiàn)
    if ([originalSelectorString hasPrefix:prefix] == NO &&
        [originalSelectorString hasSuffix:@"entityName"] == NO)
    {
        // 在短方法名前添加MR_
        NSString *prefixedSelector = [prefix stringByAppendingString:originalSelectorString];

        // 獲取帶MR_的方法
        Method existingMethod = class_getClassMethod(objectClass, NSSelectorFromString(prefixedSelector));

        if (existingMethod)
        {
            // 獲取本類的源類。
            // 因為類對象的方法列表在源類中金吗,調(diào)用類方法時十兢,通過類對象指向源類的isa指針找到源類,并在源類的類方法列表中查找方法摇庙,所以要向源類中添加方法旱物。
            Class metaClass = objc_getMetaClass([NSStringFromClass(objectClass) cStringUsingEncoding:NSUTF8StringEncoding]);

            // 向源類中添加SEL為短方法名,IMP為帶MR_的方法實現(xiàn)
            BOOL methodWasAdded = class_addMethod(metaClass,
                                                  originalSelector,
                                                  method_getImplementation(existingMethod),
                                                  method_getTypeEncoding(existingMethod));

            // 返回添加結(jié)果
            return methodWasAdded;
        }
    }
    return NO;
}

MagicalRecord替換了動態(tài)方法解析方法resolveClassMethod :resolveInstanceMethod:卫袒。由于使用短方法名找不到方法實現(xiàn)而進入動態(tài)解析過程宵呛,動態(tài)解析方法已被替換,進入自己寫的解析方法夕凝。根據(jù)方法名判斷是否是自己的方法宝穗,如果是户秤,在短方法名前添加MR_獲取方法實現(xiàn),通過class_addMethod向類中添加一個方法逮矛。如果添加成功鸡号,返回YES,會重新尋找短方法名的方法實現(xiàn)须鼎,這時就能找到我們剛才添加的短方法名的方法鲸伴,從而實現(xiàn)了短方法名。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晋控,一起剝皮案震驚了整個濱河市汞窗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌糖荒,老刑警劉巖杉辙,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捶朵,居然都是意外死亡蜘矢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門综看,熙熙樓的掌柜王于貴愁眉苦臉地迎上來品腹,“玉大人,你說我怎么就攤上這事红碑∥杩裕” “怎么了?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵析珊,是天一觀的道長羡鸥。 經(jīng)常有香客問我,道長忠寻,這世上最難降的妖魔是什么惧浴? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮奕剃,結(jié)果婚禮上衷旅,老公的妹妹穿的比我還像新娘。我一直安慰自己纵朋,他們只是感情好柿顶,可當(dāng)我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著操软,像睡著了一般嘁锯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天猪钮,我揣著相機與錄音品山,去河邊找鬼。 笑死烤低,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的笆载。 我是一名探鬼主播扑馁,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼凉驻!你這毒婦竟也來了腻要?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤涝登,失蹤者是張志新(化名)和其女友劉穎雄家,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胀滚,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡趟济,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了咽笼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顷编。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖剑刑,靈堂內(nèi)的尸體忽然破棺而出媳纬,到底是詐尸還是另有隱情,我是刑警寧澤施掏,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布钮惠,位于F島的核電站,受9級特大地震影響七芭,放射性物質(zhì)發(fā)生泄漏素挽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一抖苦、第九天 我趴在偏房一處隱蔽的房頂上張望毁菱。 院中可真熱鬧,春花似錦锌历、人聲如沸贮庞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窗慎。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間遮斥,已是汗流浹背峦失。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留术吗,地道東北人尉辑。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像较屿,于是被迫代替她去往敵國和親隧魄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,876評論 2 361

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