0%

有时候想要实现类似系统的弹窗效果,但是又需要自定义弹窗视图时,往往都会自定义一个view类,放到window上,其实还可以使用自定义一个背景色透明或半透明的controller来实现,这样可以使加载和销毁视图更方便。

那怎样present一个半透明的controller呢?

在当前controller中要present一个半透明的TestViewController:

1
2
3
4
5
TestViewController * testVC = [TestViewController new];
self.definesPresentationContext = YES; //设置当前控制器为present上下文
testVC.view.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:.4];
testVC.modalPresentationStyle = UIModalPresentationOverCurrentContext;//或者UIModalPresentationOverFullScreen 这两个都是全屏效果,其他Style会遮盖当前控制器内容
[self presentViewController:testVC animated:YES completion:nil];

这时,present动画效果是系统默认的自下而上的动画效果,还可以修改动画效果,

阅读全文 »

需求:

整体Style 为lightContent样式,这些页面通常是navigationcontroller的子控制器,通常使用push方式;个别页面为default样式,这些页面使用present或push方法弹出。

App页面中的StatusBarStyle整体一个样式,个别页面一个样式时,实现方式有两种:

方法一

  1. plist中设置Status bar stylelightContent,设置View controller-based status bar appearanceYES

  2. 扩展或在自定义navigationBarController中实现两个方法如下:

    阅读全文 »

更新到Swift4.2后,项目里调用UIApplication.shared.statusBarStyle = .light的地方报警告:Setter for 'statusBarStyle' was deprecated in iOS 9.0: Use -[UIViewController preferredStatusBarStyle]

解决办法(默认info.plist已设置View controller-based status bar appearance为NO):

  1. 如果使用了自定义的UINavigationController,在自定义的UINavigationController中重写childForStatusBarHiddenchildForStatusBarStyle两个属性:

    1
    2
    3
    4
    5
    6
    7
    override var childForStatusBarHidden: UIViewController? {
    return self.topViewController
    }

    override var childForStatusBarStyle: UIViewController? {
    return self.topViewController
    }

    如果没有自定义UINavigationController也没关系,扩展UINavigationController就可以了:

    阅读全文 »

在获取通讯录联系人相关信息时,要特别注意的是必须在获取前指定要获取的具体字段,另外,在添加到数组或字典中的联系人信息要注意提供默认值,防止数据为nil时崩溃。

指定要获取的数据:

1
2
3
4
5
6
7
8
9
//姓:CNContactFamilyNameKey
//名:CNContactGivenNameKey
//手机号:CNContactPhoneNumbersKey
//邮箱:CNContactEmailAddressesKey
//备注:CNContactNoteKey
//公司名称:CNContactOrganizationNameKey
NSArray *keysToFetch = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey,CNContactEmailAddressesKey,CNContactNoteKey,CNContactOrganizationNameKey];

CNContactFetchRequest *fetchRequest = [[CNContactFetchRequest alloc] initWithKeysToFetch:keysToFetch];

开始获取数据:

阅读全文 »

nil

  • 表示空对象
  • 所有retain相关的操作都会引起程序崩溃
1
2
3
4
5
6
NSMutableArray *array = [NSMutableArray array];
NSMutableDictionary *dic = [NSMutableDictionary dictionary];

id obj = nil;
[array addObject: obj]; // crash
[dic setObject:obj forKey:@"name"]; // crash

Nil

  • 完全等同于nil
  • 常用于类置空,表示一个类的空指针
1
2
3
4
5
6
7
8
9
id obj = nil;
if (obj == nil) {
NSLog(@" obj 为空");
}

Class classA = Nil;
if (classA == Nil) {
NSLog(@"classA 为空类");
}

NULL

  • 指向基本数据类型和c类型的空指针
1
int *point = NULL;

NSNull

  • 通常表示集合中的类型
  • [NSNull null]是一个对象,拥有有效的内存地址
1
2
3
4
5
6
7
8
9
10
NSMutableArray *array = [NSMutableArray array];
NSMutableDictionary *dic = [NSMutableDictionary dictionary];

id obj = nil;
if (obj == nil) {
obj = [NSNull null];
}

[array adObject: obj]; // it's OK
[dic setObject:obj forKey:@"name"]; // it's OK

在Swift4.0中,无法直接#import CommonCtypto,所以需要桥接OC的CommonCtypto。但是我发现XCode9系统不会自动创建BridgeHeader文件,所以正确的姿势是这样的:

  1. 在项目中创建文件夹CommonCtypto

  2. 在文件夹中新建module.modulemap文件和CommonCryptoHeader.h文件

  3. .modulemap文件中添加代码:

    1
    2
    3
    4
    5
    module CommonCrypto [system] {
    header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/CommonCrypto/CommonCrypto.h"
    header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/CommonCrypto/CommonRandom.h"
    export *
    }
  4. 在CommonCryptoHeader.h中导入CommonCrypto:

    阅读全文 »

事件传递:

  1. 当触摸到屏幕时会生成一个Touch Event(触摸事件),添加到UIApplication管理的事件队列中;
  2. UIApplication会从事件队列中依次取出事件来分发到应响应的视图去处理;
  3. 当触摸事件被UIApplication发出后,会从程序的keyWindow开始,然后依次向上传递,包括各种视图控制器以及视图,最后找到合适的处理该事件的视图来响应。

涉及的两个方法

1
2
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
-(BOOL)pointInSide:(CGPoint)point withEvent:(UIEvent *)event;

传递过程中的函数调用

UIApplication发送事件到keyWindow时,keyWindow会调用-hitTest:withEvent:方法来寻找最适合处理事件的视图,当事件传递到某个视图时:

阅读全文 »

NSOperation

NSOperation是基于GCD实现的。相对GCD来说,可控性更强,并且可以添加操作依赖。

NSOperation是一个抽象基类,可以为子类提供线程安全的建立状态、优先级、依赖和取消等操作。

默认情况下,NSOperation单独使用时系统在当前线程同步执行操作

使用NSOperation的方式

  1. 使用系统子类:NSBlockOperationNSInvocationOperation(因为ARC下不是类型安全的,swift中废弃该类)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // NSInvocationOperation
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"zh"];
    /* 调用start会马上执行封装好的操作 */
    [operation1 start];

    // NSBlockOperation
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){
    NSLog(@"执行了一个操作");
    }];
    /*开始执行任务*/
    [operation2 start];
  2. 自定义NSOperation

    一般通过重写main或者start方法来定义自己的NSOperation对象。

    重写main方法比较简单,我们不需要管理操作的状态属性isExecutingisFinished。当main() 执行完返回的时候,这个操作就结束了。

    1
    2
    3
    4
    5
    6
    7
    class ImageFilterOperation: Operation {
    var inputImage:UIImage?
    var outputImage:UIImage?
    override func main(){
    outputImage = filter(image:inputImage)
    }
    }
阅读全文 »

基本概念

  1. 什么是队列?

    • 队列是执行任务的等待队列,用来存放任务。
    • 它是一种特殊的线性表,采用FIFO(先进先出)的原则,新任务总是被插入到队列的末尾;
    • 读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
    • 队列负责管理多个任务,并拥有一个线程池,线程池中有一个或者多个线程。它按要求将每个任务调度到某个线程中执行。
  2. 串行队列 VS 并发队列

    队列类型决定了任务的执行方式(并发或者串行)

    串行队列:一个任务执行完毕后,继续执行下一个任务

    并发队列:多个任务同时执行

  3. 同步 VS 异步

    同步和异步决定是否了是否具备开启新线程的能力

    同步:在当前线程执行,不具备开启新线程的能力

    异步:在新的线程执行,具备开启新线程的能力

GCD

  1. GCD的特点

    • GCD是Apple开发的一个在线程池模式的基础上执行并发任务的多线程方案。
    • 它可以自动利用更多的CPU内核进行并行运算,并自动管理线程的生命周期;我们只需要追加要执行的任务到适当的队列中,不需要编写任何线程管理代码。
    • GCD的任务以block形式追加到队列中。
    • GCD默认提供了主队列,所有主队列的任务都会放到主线程执行
    • GCD默认提供了全局并发队列,使用dispatch_get_global_queue方法来获取。
  2. 同步任务(dispatch_sync)和异步任务(dispatch_async):

    dispatch_asyncdispatch_sync 的作用:

    • 任务(block)添加进指定的队列中
    • **async**和**sync决定调用该函数的线程是否需要阻塞,阻塞则等到block执行完毕后返回,不阻塞则立即返回**。
      • dispatch_sync(queue, block):把一个block加入到指定的队列,并阻塞当前线程直到执行完block,这个函数才返回;
      • dispatch_async(queue, block):把一个block加入到指定的队列中,不阻塞执行block的线程,这个函数立刻返回。
  3. 队列和任务的执行组合

    区别 并发队列 串行队列 主队列
    同步 没有开启新线程\br串行执行任务 没有开启新线程\br串行执行任务 没有开启新线程\br串行执行任务(死锁)
    异步 有开启新线程\br并发执行任务 有开启新线程\br串行执行任务 没有开启新线程\br串行执行任务
  4. 死锁

    使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列,产生死锁。

    示例1:

    1
    2
    3
    4
    5
    6
    7
    8
    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    dispatch_sync(dispatch_get_main_queue(), ^(void){
    NSLog(@"这里死锁了");
    });
    }
    return 0;
    }

    分析:

    在主线程上调用dispatch_sync,添加block到主队列,并阻塞当前线程也就是主线程等待block执行完成;

    主队列在主线程上调用,blockdispatch_asyn函数返回后才能执行,而主线程现在处于被阻塞状态,因为block执行后dispatch_sync函数才能返回。所以无法执行block。运行这段代码后,并不是卡在block中无法返回,而是根本无法执行block

    示例2:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    (void)deadLockCase2 { 
    dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create(
    "com.test.deadlock.queue",
    DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    dispatch_sync(aSerialDispatchQueue, ^{
    NSLog(@"2");
    dispatch_sync(aSerialDispatchQueue, ^{
    NSLog(@"3");
    });
    NSLog(@"4");
    });
    NSLog(@"5")
    }

    执行结果是1,2

    原因是外层dispatch_sync执行完打印4才能返回,然后打印后追加到队列的任务——打印3;内层dispatch_sync会阻塞当前的串行队列直到打印3之后才能返回去调用打印4,两者相互等待,造成死锁。

Dispatch Group

常用场景:分别异步执行两个任务,当两个任务都执行完毕后再回到主线程执行特定任务。

实现上分为两步:

  1. 添加耗时任务:dispatch_async + dispatch group
  2. 耗时任务完成后回到主线程执行特定任务:dispatch_group_notify / dispatch_group_wait / dispatch_group_enter + dispatch_group_leave
  • dispatch_group_notify示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    -(void)groupNotify {  
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t quque = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    dispatch_group_async(group, queue, ^{
    // 异步任务1
    });
    dispatch_group_async(group, queue, ^{
    // 异步任务2
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 任务1、2结束后调用
    NSLog(@"group end");
    });
    }
  • dispatch_group_wait示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    -(void)groupWait { 
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_async(group, queue, ^{
    //追加异步任务1
    });
    dispatch_group_async(group, queue, ^{
    // 追加异步任务2
    });
    // 任务1、2执行完后,会往下继续执行
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group---end");
    }
  • dispatch_group_enter + dispatch_group_leave

    dispatch_group_enter:标志着一个任务追加到Group,执行一次,相当于Group中未执行任务数 + 1

    dispatch_group_leave:标志着一个任务离开了Group,执行一次,相当于Group中未执行任务数 - 1

    当Group中未执行任务数为0时,才会使dispatch_group_wait解除阻塞,并执行追加到dispatch_group_notify中的任务。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    -(void)groupEnterAndLeave { 
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
    // 追加任务1
    dispatch_group_leave(group); // 每执行完一个任务调用一次
    });

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
    // 追加任务2
    dispatch_group_leave(group);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的异步操作都执行完毕后,回到主线程.
    NSLog(@"group---end");
    });
    }

其他常用API

  1. 快速迭代方法:dispatch_apply

    dispatch_apply按照指定的次数将指定的任务追加到特定的队列中,并等待全部队列执行结束

    • 在串行队列:就和for循环一样,按顺序同步执行
    • 在并发队列:异步执行,可在多个线程中同时执行多个任务。

    无论是串行队列还是并发队列,dispatch_apply都会等待全部任务执行完毕。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 快速迭代
    -(void)apply{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    NSLog(@"apply begin");
    dispatch_apply(6, queue, ^(size_t index){
    NSLog(@"%zd---%@",index,[NSThread currentThread]);
    });
    NSLog(@"apply end");
    }
    // 因为在并发队列中异步执行任务,各个任务的执行时间长短不定,最后结束时间也不定。
    // apply end一定在最后执行,因为dispatch_apply方法会等待全部任务执行完毕。
  2. GCD信号量:dispatch_semaphore

    • semaphore的值表示线程最大并发数。
    • 常用来进行加锁操作;
    • 也可以用来控制线程的最大并发数。

    使用:

    • 信号量>0,使信号量-1,并继续执行;
    • 信号量<=0,新进来的线程就会进入休眠,直到dispatch_semaphore_signal(semaphore)使信号量>0时,被唤醒。

    dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量

    dispatch_semaphore_wait:使信号量 -1,信号总量小于0就会一直阻塞所在线程,否则就可以正常执行。

    dispatch_semaphore_signal:发送一个信号,让信号总量加1

  3. dispatch_barrier_asyncdispatch_barrier_sync

    • 异步栅栏dispatch_barrier_async:不会等待栅栏函数内任务执行完,就会执行后面主线程的任务。

    • 同步栅栏dispatch_barrier_sync:会等待栅栏函数内的任务执行完,再执行后面的主线程或者子线程任务。

    优点:不会阻塞主线程

    生效必要条件:使用自定义的并发队列,不能使用全局并发队列。使用全局并发队列时,效果同dispatch_asyncdispatch_sync

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
    // 异步任务1
    });
    dispatch_async(queue, ^{
    // 异步任务2
    });
    // 任务1、2执行完成后执行栅栏任务
    dispatch_barrier_async(queue, ^{
    // 栅栏任务
    });
    dispatch_async(queue, ^{
    // 异步任务3
    });

最近在玩数独游戏,觉得很有意思,正好在温习Swift,就试着写个数独练练手。介绍下经典数独游戏和我实现数独的方法。

经典数独游戏

经典数独游戏共有81格,分为9个小九宫格。每行、每列、每个小九宫格内为1~9的无重复数字。

数独盘面生成方案

  1. 将1~9内的随机数按满足数独的条件(行、列、小九宫格均无重复数字)填充盘面数组(二维数组)
  2. 随机交换任意小九宫格内的两行或两列(demo中有三次行交换、三次列交换)
    阅读全文 »