iOS开发之Designated Initializer(指定初始化方法)
序言
NS_DESIGNATED_INITIALIZER 是官方定义的一个宏。可以在编译的时候帮我们找出潜在的问题。
如果一个类中的初始化方法没有用 NS_DESIGNATED_INITIALIZER 修饰,说明这个方法是便利初始化方法(Convenience Initializer)。
实现
1 | #ifndef NS_DESIGNATED_INITIALIZER |
使用
看看官方的接口是怎么使用的。
1 | // UIViewController |
两个指定初始化方法分别对应纯代码方式和文件(Xib、Storyboard)方式创建。
理解
在OC中它想表达的意思大概就是:当你alloc了一个类的时候,必须直接或间接调用这个类的其中一个带有 NS_DESIGNATED_INITIALIZER 后缀的初始化方法。不然初始化不完全,编译器会报出一个警告!下图是官方给的逻辑图。
这里有两个约束
约束1、便利初始化方法只能调用本类的初始化方法,最终一定会调用本类的指定初始化方法。
约束2、子类的指定初始化方法一定会调用父类的指定初始化方法。
当存在多个初始化方法的时候,流程如下图。这里的 Secondary initializer 就是 Convenience (便利初始化方法)
这里便利初始化方法最终调用了本类的指定初始化方法,本类的指定初始化方法调用了父类的指定初始化方法。满足了约束1和约束2。
子类要先初始化父类,然后再初始化实例变量。如下图
子类的指定初始化方法调用了父类的指定初始化方法。满足了约束2。
Reference
Object Initialization
Concepts in Objective-C Programming: Object Initialization
iOS开发之多线程(NSThread、NSOperation、GCD)
序言
整理一些多线程相关的知识。
并行 & 并发
1、并行:并行是相对于多核而言的,几个任务同时执行。
2、并发:并发是相对于单核而言的,几个任务之间快速切换运行,看起来像是“同时”发生的一样
NSThread
优点:轻量级
缺点:需要手动管理线程活动,如生命周期、线程同步、睡眠等。
搭配runloop实现常驻线程
1 | //NSThread |
NSOperation & NSOperationQueue
NSOperation
NSOperation 是一个抽象类,只能使用它的自类来进行操作。系统为我们创建了两个子类NSInvocationOperation & NSBlockOperation。
直接使用这两个类执行任务,系统不会创建子线程,而是在当前线程执行任务。NSBlockOperation 使用 addExecutionBlock方法的任务是在多线程执行的
1 | //NSInvocationOperation |
NSOperationQueue
//主队列,任务在主线程执行,一般用于更新UI的时候使用。
NSOperationQueue *queue = [NSOperationQueue mainQueue];
//子队列,任务在子线程执行,用于处理耗时任务。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
1 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; |
也可以直接添加block
1 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; |
可以通过 maxConcurrentOperationCount 来控制最大并发数,当最大并发数为1时,就相当于串行队列
NSOperation添加依赖关系
1 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; |
注意:NSOperationQueue 是非线程安全的,多个线程访问统一资源时,需要加锁。
1 | self.lock = [[NSLock alloc] init]; |
GCD
创建队列,然后再往队列添加任务。也可以直接使用系统创建的队列。
1 | //创建串行队列 |
创建同步、异步任务
1 | dispatch_sync(queue, ^{ |
GCD常用方法
dispatch_barrier
只有当添加在dispatch_barrier前面的任务完成了,才开始执行dispatch_barrier任务。
有两种方式:dispatch_barrier_sync 和 dispatch_barrier_async
dispatch_barrier_sync
1 | dispatch_queue_t queue = dispatch_queue_create("com.vhuichen.queue", DISPATCH_QUEUE_CONCURRENT); |
dispatch_barrier_async
1 | dispatch_queue_t queue = dispatch_queue_create("com.vhuichen.queue", DISPATCH_QUEUE_CONCURRENT); |
dispatch_after
1 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ |
dispatch_once
一般会以这种形式出现
1 | static dispatch_once_t onceToken; |
dispatch_apply
1 | dispatch_queue_t queue = dispatch_queue_create("com.vhuichen.queue", DISPATCH_QUEUE_CONCURRENT); |
dispatch_semaphore
可以用于线程同步、线程加锁、控制并发线程数量
1 | //实现同步请求 |
dispatch_group
当需要同时执行多个耗时任务,并且当所有任务执行完后更新UI。那么这时可以使用 dispatch_group 来实现。
1 | dispatch_group_t group = dispatch_group_create(); |
总结
当遇到相应场景的时候,知道使用哪种方法比较合理就行了。
iOS开发之封装FMDB
序言
之前在项目中使用过,写出来记录一下。
常用类 FMDatabaseQueue FMDatabase
FMDatabase
FMDatabase 可以直接操作数据库,使用时需要注意,它不是线程安全的。
FMDatabaseQueue
使用 FMDatabaseQueue 操作数据库则是线程安全的,看源码可以发现这是一个串行队列。
考虑到线程安全可以使用 FMDatabaseQueue 代替 FMDatabase
方法如下:
1 | [self.databaseQueue inDatabase:^(FMDatabase * _Nonnull db) { |
如果要处理事务,则使用下面的方法
1 | [self.databaseQueue inDeferredTransaction:^(FMDatabase * _Nonnull db, BOOL * _Nonnull rollback) { |
不管是使用 inDatabase 还是 inDeferredTransaction 都是同步的,所以在这里不需要任何处理。
FMDatabaseQueue 内部已经打开了数据库,所以不需要再使用 open 、close。但是当使用 FMResultSet 这个类的时候,记得一定要在block内处理完 FMResultSet 相关的逻辑,然后调用 close 方法。不要在block之外使用FMResultSet,不然会报错。
1 | __block BOOL isOK = NO; |
备注: 在block内处理完FMResultSet的相关逻辑,就算不调用 [results close] ,也不会报错,可能是由于block处理完,就自动释放了这个类。但注意不要在block外面使用FMResultSet。
Demo
Demo中目前没有加入keyid,操作不当会出现多个相同的数据,只能以后再改了。。。。
GitHub Demo
iOS基础之约束
###前言
记录下来,有好几次在这里花时间了
####一、相对约束
上图表示的意思是
Superview.Trailing = Button.Trailing * (5 / 4) - 50
Superview.Trailing 表示Superview右边相对左边的距离。
####二、固有约束
Content Hugging Priority: 抗拉伸优先级,优先级越高,越难拉伸。
Content Compression Resistance Priority: 抗拉压缩先级,优先级越高,越难压缩。
iOS开发之单元测试
序言
单元测试的基本操作。
新建单元测试文件
一般项目会默认建立一个单元测试文件,如果没有或者需要创建新的单元测试文件,可以创建一个新的文件,在选择文件类型为Unit Testing 类型。创建好文件后就可以开始了。
基本方法
setup
开始测试的时候会调用这个方法。可以在这个位置初始化一些对象、数值等。
tearDown
测试结束的时候会调用这个方法。一般在这里释放对象。
testExample
需要进行单元测试的方法,可以自定义方法名,方法名必须用test开头,返回类型是void。
testPerformanceExample
这里可以进行一下性能测试,可以测试代码的执行时间。
异步单元测试
1 | - (void)testExample { |
时间性能测试
当 Baseline *(1 - Max STDDEV)大于 Average 时,测试通过,小于则不通过。
常用断言
// 如果 expression 不为 true,则提示后面的错误信息。
XCTAssertTrue(expression, format…)
// 如果 expression 不为 nil,则提示后面的错误信息。
XCTAssertNil(expression, …)
// 如果 expression1 expression2 不相等,则提示后面的错误信息。
XCTAssertEqual(expression1, expression2, format…)
总结
单元测试不需要搭建环境,却可以为我们节省一定的时间,不需要每次调试功能都要一个一个点进去。
如果可以将单元测试集成到CI中,将更方便。
iOS开发之APP默认语言
问题
测试人员在测试的时候,将系统语言设置为没有本地化的语言,然后APP显示的是中文!居然是中文,可我的“Base”语言是英文来的。
### 验证经过测试后发现原来当将系统语言设置为没有本地化的语言的时候,APP会显示上一次的语言,而不是APP里面设置的“Base”语言。
网上搜索一下,大概得到了答案。iOS系统会从语言和地区栏里面按照优先级顺序查找,如下图,系统会检测APP有没有本地化“English”,如果有直接使用,如果没有则检查“简体中文”有没有本地化,最后检测“French”。如果都没有本地化,则使用“Base”语言。如果“Base”也没有则使用默认值(比如“key”值)。
上图是手机里面的语言和时区界面的截图。
APP语言一共有3种,地区为中国。
解决方案
系统方法:NSLocalizedString(key, comment) 的宏定义如下
1 | #define NSLocalizedString(key, comment) \ |
只要将bundle更换为指定的语言就行了。
这里可以参考第三方库iVersion。
大概步骤如下:
先获取到当前语言,然后判断APP是否有本地化该语言,如果有则直接使用,如果没有则使用APP默认语言(一般就是英语)。而不是根据语言优先级来设置语言。
写一段简单的代码,获取所有的系统语言。
1 | //两种方法得到的结果相同 |
1 | //输出 |
输出的语言里面刚好对应上面截图的三种语言,结尾的CN对应地区中国。
将地区改为美国后,输出如下:
1 | AppleLanguages = ( |
然而系统创建的文件名如下
所以需要将系统创建的lproj文件夹与APP获取到的第一语言对应起来。
疑问:每一个lproj文件夹相当于一个bundle !?
代码实现
.h文件
1 | // |
.m文件
1 | // |
iOS开发之使用SVN管理代码(Cornerstone)
SVN相关概念
主干(trunk)、分支(branch)、标记(tag)
trunk:有两种说法:
1、主方向开发,版本迭代,一个新的需求,一般都是放在trunk里面开发的。
2、也有一种做法是把所有开发的工作都拉到branch上去完成,也就是trunk和tag只负责版本记录,brank负责所有的开发工作。当brank完成某一部分功能的时候合并到trunk,其他人便可从trunk拉最新的代码。
branch:和trunk并行开发。有几种情况下需要创建branch:
1、当某个功能不确定能否实现或者想先看看效果的时候,可以拉一个branch,在这个branch上开发,开发完成,再考虑是否合并到trunk。
2、当trunk正在开发某个最新版本的时候,线上版本出现一个急需修复的bug,由于时间紧,不能等到新版本开发完成的时候才修复这个bug,这时候就应该在线上版本对应的tag下拉一个branch,在这个branch下修复bug,测试,然后上线。
tag:记录每一个milestone(里程碑),一般每一个线上版本都会打一个tag,一旦出现急需修复的bug,可以在对应的tag下拉一个branch进行修复。
branch是动态的,一直往前走。tag是静态的,只做版本记录,相当于只读。
至于trunk我理解的是功能的集合,多人协同开发时,每个人每当完成一个功能都将合并到trunk中,也可以将trunk中最新的代码合并到自己的branch中
Cornerstonr操作
SVN搭建、配置
参考另一篇文章:
iOS基础之搭建SVN服务器
Check Out (working copy)
选中对应的项目,点击左上角的Check Out,将项目copy到本地
Merge
选中需要Merge的项目(也就是合并后的项目),点击Merge,出现如下图所示的界面:
在Merge from里选择被合并的项目。点击 Merge Changes ,完成。
Commit
将本地代码提交带远程仓库。