iOS 底层以及数据问题深入研究(1)

几周前有人问了我几个问题,我觉得自己能回答出来,但是深入的时候才发现自己还是浮在水表明,没有真正的去理解。所以将理解后易忽略的问题总结并记录下来


1 RunLoop --关于NStimer添加到NSRunLoopCommonModes的原因
2 Category --关于Category中无法创建实例变量的原因
3 CoreData -- 关于数据迁移以及预加载
4 总结

1 RunLoop

深入理解RunLoop
网上有很多关于RunLoop的文章,而这一篇是真正做到由浅入深的探讨。我不只一次看过这篇文章,但是和很多人一样,没有重点的看反而会忽略一些问题。所以下面我将以问答的形式将我从这篇文章中理解的知识点列举出来:

①NSTimer在添加到具有滚动效果的UIScrollView的页面时,如果解决滚动的时候NSTime不停止计时?

这个问题应该很简单,因为一个线程内一次只能存在一种mode。主线程的runLoop中有两个预置的mode:
kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。

kCFRunLoopDefaultMode是应用平时所处的状态。NSTimer默认加入kCFRunLoopDefaultMode中。
UITrackingRunLoopMode 是追踪 ScrollView 滑动时的状态,所以当滚动的时候会切换到该mode中。

所以当切换到UITrackingRunLoopMode中时,kCFRunLoopDefaultMode就无法同步到相应的变化了。这就解释了为什么NSTimer会在滚动的时候停止,通用的处理方法是:

 NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(updateCount) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

将NSTimer添加到NSRunLoopCommonModes中,这样就能在滚动的时候收到回调了。但是这时候会出现第二个问题:

① NSRunLoopCommonModes是mode吗?线程一次只能存在一种mode,为什么UITrackingRunLoopMode和kCFRunLoopDefaultMode都能收到事件回调?

首先,来看看NSRunLoopCommonModes的定义:

FOUNDATION_EXPORT NSRunLoopMode const NSRunLoopCommonModes API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

NSRunLoopCommonModes是NSRunLoopModel的一个常量.

typedef NSString * NSRunLoopMode NS_EXTENSIBLE_STRING_ENUM;

而NSRunLoopModel只是一个字符串而已。
所以明确的第一点是:使用的NSRunLoopCommonModes只是一个字符串标识符,并不是一个mode。
其次,来看看CFRunLoop和CFRunLoopMode结构体:

struct __CFRunLoopMode {
    CFStringRef _name;           
    CFMutableSetRef _sources0;   
    CFMutableSetRef _sources1;   
    CFMutableArrayRef _observers; 
    CFMutableArrayRef _timers; 
    ...
};
struct __CFRunLoop {
    CFMutableSetRef _commonModes; 
    CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes; 
    ...
};

CFRunLoop结构体中存在一个概念 ---commonModes。每当RunLoop的内容发生变化时,都会将commonModeItems中的Source/Observer/Timer同步到所有标记为“common”的mode中。而苹果提供的NSRunLoopCommonModes字符串就是用来操作 Common Items。

而在主线程中的UITrackingRunLoopMode和kCFRunLoopDefaultMode都已标记为“common” mode。

所以换个角度想就是:
将NSTimer添加到NSRunLoopCommonModes中等价于添加到commonModeItems中,然后commonModeItems会将Source/Observer/Timer同步到UITrackingRunLoopMode和kCFRunLoopDefaultMode中。

2 Categroy

深入理解Objective-C:Category
美团的这篇文章从结构体c++解析方面来揭露了category的底层。其实项目中很频繁的会使用到category,主要是为了将主类中不同模块的实现方法放在不同的类中,这样有减少单个文件的体积等好处。我相信不只我一个人有疑惑:

为什么Category中是无法添加实例变量?

首先,来看看Category的结构体:

typedef struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;  //实例方法
    struct method_list_t *classMethods;       //类方法
    struct protocol_list_t *protocols;              //协议
    struct property_list_t *instanceProperties;     //属性
} category_t;

从定义中可以明白category能创建实例方法、类方法和协议、属性,就是无法创建实例变量。
其次,来看看Runtime中关于类的结构体:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class   OBJC2_UNAVAILABLE;
    const char *name   OBJC2_UNAVAILABLE;
    long version     OBJC2_UNAVAILABLE;
    long info     OBJC2_UNAVAILABLE;
    long instance_size   OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars  OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists  OBJC2_UNAVAILABLE;
    struct objc_cache *cache    OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols   OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

在运行期间类的内存布局已经确定了,如果添加实例变量,就会破坏了类的结构。而能添加方法的原因是:
methodLists 是指向 objc_method_list 指针的指针,也就是说可以动态修改 *methodLists 的值来添加成员方法。

3 CoreData

在应用上线前,怎么修改CoreData中的表结构以及数据的增减都是没有任何问题的,最怕的是:
当应用版本更新,之前的CoreData数据库中已经存在数据了,而现在需要修改表中的一个字段或者添加一张新表等,该如何做?

①更改model中的实体之后,如何进行数据迁移

举例说明:
当前版本的数据库中comicC(C公司的漫画)


数据结构字段

现在修改的2.0版本中C公司的漫画全都导入JP公司的漫画库了,这时候不能直接修改字段名,也不能丢弃之前用户存在本地关于C公司的漫画,所以:

第一步 创建2.0版本的model

点击 Editor --> add Model Version,基于当前的model创建一个2.0版本的model。

添加2.0版本的model

在添加了2.0版本的model之后,根据自己的需求设置model内的实体结构,这样才能保证在不更改之前版本的model结构上添加新的字段或者实体。

设置2.0版本model的结构

设置完成之后,一定要注意,这个时候项目中使用的model还未切换到model 2,必须切换model的版本。

将model 2设置为项目使用model

然后就是代码处理了,根据自己的需求可以分别使用以下的方法:

如果两个版本中的字段相近,可以设置为系统自动判断
  NSDictionary *options = @{
                                          //设置为YES,coredata会试着把低版本的数据存储区切换到最新的版本中model 2
                                          NSMigratePersistentStoresAutomaticallyOption : @YES,
                                          //试着以最合适的方式自动推断出原型实体model里的某个属性到底对于model2中的哪一个属性
                                          NSInferMappingModelAutomaticallyOption : @YES
                                          };
如果两个版本中的字段相差较远,手动设置:
第一步 将系统自动判断的选项关闭
 NSInferMappingModelAutomaticallyOption : @NO
第二步 创建model 到 model2之间的Mapping model
创建MappingModel

并且将 model ------>设置为source Data Model(数据资源来源的Model)
model2 ---->设置为Target Data Model(数据迁移的目标Model)

创建MappingModel结果

这就手动的告知了系统关于各个字段之间的匹配关系。其实就相当于切换了model,将数据库中的字段或者结构关系改变了。


② 如何处理数据的预加载问题

这是我刚学会的一种实现思路:用户在多次进入应用的时候会留下响应的信息存储,这将加快应用的开启,但是当用户第一次进入应用时:

步骤一 在本地设置一个默认的plist文件(xml或sqlite)
设置一个本地默认数据文件

进入文件结构中:

数据文件结构

最重要的一点就是对版本versionNumber的判断。

步骤二 判断用户是否第一次进入应用

[[self filePath] checkPromisedItemIsReachableAndReturnError:nil]

如果用户第一次安全该版本的应用,需要加载成本地的默认数据;其他情况 --->非该版本或者非第一次进入都直接加载用户保存的数据:

 NSDictionary *dicPlist = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ComicList" ofType:@"plist"] ];
        NSLog(@"plist ---%@",dicPlist);
        
        if ([self checkIfDataNeedsImport:[self filePath] ofType:NSSQLiteStoreType withCurrentVersion:[dicPlist objectForKey:@"versionNumber"]]) {
            [self setDataAsImportForStore:_store withVersion:[dicPlist objectForKey:@"versionNumber"]];
            
            NSArray *array = [dicPlist objectForKey:@"items"];
            //选择插入
            for (NSDictionary *item in array) {
                NSString *entity = [item objectForKey:@"Entity"];
                NSDictionary  *comic = [item objectForKey:@"Comic"];
                BOOL existing = [self existingObjectInContext:_context object:entity attributes:comic];
                //如果不存在的话
                if (existing) {
                    NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:entity inManagedObjectContext:_context];
                    
                    [newObject setValuesForKeysWithDictionary:comic];
                    NSLog(@"插入成功");
                } else {
                    NSLog(@"已经存在了,不用管了");
                }
            }
            [self saveContext];
        }

其实对一些复杂点的数据,直接建立一个默认的sqlite存在本地是更好的选择。


3 总结

其实都是基于一个机会发现很多底层的东西都不是特别明白,所以将一些容易搞混的东西记录下来,以供日后参考。接下来还会在项目之余更新一些底层的参考资料和推荐的处理思路。
如果有更好的思路可以和我交流。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,302评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,563评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,433评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,628评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,467评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,354评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,777评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,419评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,725评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,768评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,543评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,387评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,794评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,032评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,305评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,741评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,946评论 2 336

推荐阅读更多精彩内容