iOS应用文件路径简介

iOS 标准目录结构

出于安全考虑,iOS只有特定目录可以写入数据。 当一款应用安装在设备上后,系统会创建一些系统目录给应用使用,一款应用只有权限访问其自己的路径下的数据。

AppName.app

整个应用的bundle文件。此目录包含整个应用和其所使用到的所有资源。 此路径不能写, 只可读, 如果强行写入,会改变安装时在系统注册的该目录的签名,如果签名改变,系统会组织程序启动。

Documents/

用于存储用户产生的内容。 此目录会被iTunes默认备份

Documents/Inbox

此目录用于保存用于其他应用打开的文件。 例如,Mail应用汇将和此应用有关的附件保存于这个路径。 Documents interaction Controllers 也会放文件于此目录。

自己应用可以读取和删除此目录文件,但是不能创建新文件或者写入已经存在的文件, 如果要对该目录文件进行修改,请将要修改的文件移除到其他目录。
此目录默认被iTunes备份

Library/

此目录用于保存非用户数据文件。 Library下的子目录可以用于保存那些不希望用户看到的数据文件。不要用Library下的目录保存用户数据文件

Library目录下(除了Caches目录及其子目录)会被iTunes默认备份

tmp/

用于那些不需要在每次启动需要持久化的临时文件。手动删除那些此路径下不用的临时文件。当应用没有运行时,系统也许会删除此目录下的文件。

此目录默认不会被iTunes备份

应用产生的文件应该放在哪

为了避免iOS设备间同步和备份流程耗时太久,应该对文件归属目录的选择特别注意。

  • 将用户数据保存于 Documents/. 用户数据包括那些需要让暴露给用户的文件 — 用户可以创建,删除,编辑的文件。 例如,对于视频和音频应用,用户数据包括用户下载过的文件等。
  • 将应用创建的文件保存于Library/Application support/路径下。此目录下,用于保存应用自身使用,不暴露给用户的数据文件。包括数据文件,配置文件,模板文件和版本特定的资源文件等。
  • 谨记保存于Documents/ 和 Application support/ 目录下的文件是默认备份的。可以使用[NSURL setResourceValue:forKey:error:]使用NSURLIsExcludeFromBackupKey key. 任何可以被重新下载的文件,一定不要进行备份
  • 将数据缓存数据保存于Library/Caches 目录下。 缓存数据是那些没用该数据,应用也可以正常运行的数据, 但如果有缓存数据,会提高应用的性能。 例如,数据库缓存文件,可以下载的内容等。 注意:系统也许会删除Caches/下的文件,来释放磁盘空间

参考: File System programming guide

Protocol Extensions

资源推荐:

此文为 The Best of What’s New in Swift 有关Protocol Extensions的翻译。

Protocol Extensions

在Swift 2.0 之前, Objective-C和Swift,在protocols中只能包含方法声明。 protocols只包含接口定义即一堆等待遵守类型去实现的方法。 在Swift 2中使用 protocol extensions, protocols可以既包含声明,也可以包含实现。

经常,会有很多功能性方法适用于一类对象。 如, 所有的集合类型都可以有将一个集合内对象经过映射(map),而创建一个映射后的集合对象。 在Swift 2之前, 有两种方式可以达到这种能力。

* 在protocol中声明方法并要求对应遵循接口的对象实现自己的方法
* 全局函数

Cocoa基本上采用的是第一种解决方案。 虽然这些方法可能太适合成为任何protocol的一部分, 但是Cocoa的集合对象都有一个enumerateObjectsUsingBlock:方法,每个集合类型有各自的实现。

Swift 2之前,采用的是第二种解决方案。 想 map 这种全局函数,操作于 CollectionType . 这种方式很好的共享了函数实现, 但是语法奇怪并且不能针对特定类型重写实现方法。

有了 protocol extensions , 我们可以有了更牛X的第三种方式。 map 可以实现在 CollectionType 的一个扩展(extension)中。 所有遵循 CollectionType 的类型,就自动获得了一个 map 实现。

一下为一个简单的 map 函数实现:

1
2
3
4
5
6
7
8
9
10
11
12
extension CollectionType {
func myMap<U>(f: Self.Generator.Element -> U) -> [U] {
var result: [U] = []
for elt in self {
result.append(f(elt))
}
return result
}
}

[1, 2, 3, 4].myMap({ $0 * 2 })
// This produces [2, 4, 6, 8]

之前只可以作为 Array 的扩展,然后只可以在 Array 中使用。 有了一个接口的扩展, 上面的实现也可以在 Set 和 ArraySlice 和其他遵循 CollectionType 接口的类型中获得。

另一个很有意思的Swift protocol extensions特性是可以对类型做出限制。 举个栗子, 如果你想实现一个 max 属性。 但是 max 并不适用于所有集合类型, 只有那些有序集合才使用这个属性。 没问题! 只要给扩展加一个限制,要求其实现类型必须遵守 Comparable 协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extension CollectionType where Self.Generator.Element: Comparable {
var max: Self.Generator.Element {
var best = self[self.startIndex]
for elt in self {
if elt > best {
best = elt
}
}
return best
}
}

Set([5, 4, 3, 2, 1]).max
// This produces 5

[NSObject(), NSObject()].max
// This produces an error, as NSObject is not Comparable

protocol extensions 有一个会令人迷惑的地方, 虽然很小但是很重要,决定了protocol extension中方法是否需要动态分发(dynamic dispatch)。

一个在protocol extension中声明的方法也许也会在protocol中声明, 或者只存在于extension中。 只存在extension中的方法,不会动态分发(dynamic dispatch)并且也不能被复写。只声明在protocol中的方法是动态分发(dynamic dispatch)的并且可以被复写。talk is cheap, show me the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protocol P {
func a()
}

extension P {
func a() {
print("default implementation of A")
}

func b() {
print("default implementation of B")
}
}

struct S: P {
func a() {
print("specialized implementation of A")
}

func b() {
print("specialized implementation of B")
}
}

let p: P = S()
p.a()
p.b()

上面的代码会打印出:

1
specialized implementation of A和default implementation of B.

使用protocol extensions还可以让Swift的protocol方法成为“可选”方法,在Swift 2中,我们可以这样实现一个delegate中的可选方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

protocol MyClassDelegate {
func shouldDoThingOne() -> Bool
func shouldDoThingTwo() -> Bool
}

extension MyClassDelegate {
func shouldDoThingOne() -> Bool {
// Thing one is harmless and should almost always be done
return true
}

func shouldDoThingTwo() -> Bool {
// Thing two is a menace and should not be done without careful consideration
return false
}
}

iOS 7 - 9 新特性总结

iOS 7

  1. Text Kit

    文字排版相关

  1. 多任务
    • 设置UIBackgroundModesfetch来进行定期后台下载。
    • 设置UIBackgroundModesremote-notification, 用远程推送的方式,来让应用在后台进行下载内容。
  1. NSURLSession 用来替代NSURLConnection,配合多任务后台下载使用。
  2. AirDrop
    附近设备分享传输文件,不需要网络。

  3. 新Framework:

    • GameController.framework - 与游戏硬件通信
    • SpriteKit.framework - 游戏图形渲染和动画
    • Multipeer connectivity framework - 点对点网络
    • JavaScriptCore.framework - JavaScript对象的oc封装
    • MediaAccessibility.framework
    • SafariServices.framework - 用程序添加URL到用户Safari的阅读列表等

iOS 8

  1. Swift 1.0

  2. App Extension

    • Share, 在系统提供的分享选项中添加自己
    • Action, 对于选中内容可以执行一个action
    • Today, 通知中心的widget
    • Photo editinig.
    • Storage provider. 与其他应用共享文件存储位置
    • 自定义键盘
  3. Touch ID

  4. Photos Framework

  5. AV Audio Engine: Core Audio 更上层的封装。
  6. HealthKit Framework
  7. HomeKit Framework
  8. Handoff
  9. Size class
  10. Metal - 替代OpenGL ES

iOS 9

  1. Swift 2.0: 错误处理,Availability, Protocol extensions等
  2. iPad 多任务
  3. watchOS2
  4. UI Test
  5. App Thinning
  6. Search API可以搜索App内容
  7. 新系统字体San francisco
  8. Safari View Controller
  9. Contacts Framework
  10. 3D Touch
  11. Live Photo

参考资料:
1.https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS7.html

2.https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS8.html#//apple_ref/doc/uid/TP40014205-SW1

SQLite WAL 调研总结

  • iOS 系统从iOS 7版本自带的SQLite数据库默认为开启WAL模式,即journal_mode=WAL
  • iOS 版本4.3 以上系统自带SQLite版本为3.7.0以上(SQLite 3.7.0开始支持WAL)
  • 在创建数据库连接时,执行
    [conn executeUpdate:[NSString stringWithFormat:@”PRAGMA journal_mode=WAL”]];
    即可
  • 使用注意,在开启数据库时,不能只读打开。
  • 低版本的SQLite不能读取高版本的SQLite生成的WAL文件,但是数据库文件是通用的。这种情况在用户进行iOS降级时可能会出现,可以把模式改成delete,再改回WAL来修复。
  • iOS 版本的SQLite是线程安全的(db 连接不要多线程共享)。详情

WAL 工作原理:

开启WAL模式后, 会有3个文件:

{<1>}database

.db 为数据库文件


-shm 为每个数据库文件的共享内存文件


-wal 对数据库的修改操作于此文件

修改并不直接写入到数据库文件中,而是写入到另外一个称为WAL的文件中;如果事务失败,WAL中的记录会被忽略,撤销修改;如果事务成功,它将在随后的某个时间被写回到数据库文件中,提交修改。

同步WAL文件和数据库文件的行为被称为checkpoint(检查点),它由SQLite自动执行,默认是在WAL文件积累到1000页修改的时候;当然,在适当的时候,也可以手动执行checkpoint,SQLite提供了相关的接口。执行checkpoint之后,WAL文件会被清空。

在读的时候,SQLite将在WAL文件中搜索,找到最后一个写入点(end mark),记住它,并忽略在此之后的写入点(这保证了读写和读读可以并行执行);随后,它确定所要读的数据所在页是否在WAL文件中,如果在,则读WAL文件中的数据,如果不在,则直接读数据库文件中的数据。为了避免每次读操作都遍历整个WAL文件去查找,SQLite使用了一种叫做wal-index的数据结构(保存于-shm)用于提高查找效率同时减少磁盘I/O。

在写的时候,SQLite将之写入到WAL文件尾即可,读写互不影响,所以渡河写可以同时执行。因为只有一个WAL文件,所以必须保证一次只执行一次写操作,因此写操作之间不能并行执行

checkpoint的时候,将WAL中的数据同步回数据库中。checkpoint操作可以与读操作并行,但,如果有读操作正在读checkpoint要更新的数据库中的数据时,checkpoint操作会停止,直到读操作完成。

WAL在实现的过程中,使用了共享内存技术,因此,所有的读写进程必须在同一个机器上,否则,无法保证数据一致性。

WAL相关使用配置:

启动: PRAGMA journal_mode=WAL;

自动checkpoint操作:

默认情况下,SQLite 自动触发checkpoint操作

  • 当一个COMMIT时,WAL文件的page数超过1000(可配置)
  • database连接关闭时。

手动触发checkpoint操作:详情

  • PRAGMA wal_autocheckpoint;
  • PRAGMA wal_autocheckpoint=N;
  • SQLite3_wal_checkpoint()

参考资料

NSAutoreleasePool 实现原理

概述

autorelease 是NSObject的一个类方法,它是通过调用[NSAutoreleasePool addObject:self] 来把一个对象添加到自动释放池中的,因为这是一个类方法,所以有必要知道是对哪个对象进行autorelease调用。

NSAutoreleasePool 实例是保存在每一个线程的stack中。当一个新pool创建,它就会进栈。当一个stack被销毁的时候,它就出栈。 使用NSAutoreleasePool类方法时,需要从当前线程中获取stack最上面的pool对象,来获取当前pool。

当找到正确的pool后,调用addObject: 实例方法来将对象添加到pool中。当对象被添加到pool中,其实他是添加到pool中的一个可变数组中去。

当pool被销毁时,pool会遍历可变数组中的所有对象,并发送 release 方法给每一个对象。还有一点额外说明,如果一个pool被销毁,并且这个pool不是stack最上端的,这个pool还会销毁该pool之上的所有pool。

总结

没有方法知道一个对象是否已经被自动释放了。
如果对象被autorelease 两次,这个对象就会被两次添加到pool中,当pool被销毁,该对象就会被release两次。
自动释放池的对象是当当前自动释放池被释放时被释放。pool是当它的代码显性的销毁它时而被销毁。
如果在一个线程上autorelease一个对象,并将它传给另一个线程,不会有特殊处理。当第一个线程池被销毁的时候,该对象也会被release, 不管该对象在新线程发生了什么。如果你需要继续保留这个对象,就需要在传递之前retain这个对象。

原文链接:http://www.mikeash.com/pyblog/friday-qa-2911-09-02-lets-build-nsautoreleasepool.html

Best Practices for Handling Multitouch Events

  • Always implement the event cancelation methods.

    In your implementation, restore the state of a view to what it was before the current multitouch sequence. If you don’t, your view could be left in an inconsistent state, or in some cases, another view could receive the cancelation message.

  • If you handle events in a subclass of UIView, UIViewController, or UIResponder:

    • Implement all of the event handling methods, even if your implementations of those methods do nothing.
    • Do not call the superclass implementation of the methods.

  • If you handle events in a subclass of any other UIKit responder class:

    • You do not have to implement all of the event handling methods.
    • In the methods you do implement, be sure to call the superclass implementation. For example, [super touchesBegan:touches withEvent:event].

  • Do not forward events to other responder objects of the UIKit Framework.

    Instead, forward events to instances of your own subclasses of UIView. Additionally, make sure these responder objects are aware that event forwarding is taking place and tat they can receive touches that are not bound to them.

  • Do not explicitly send events up the responder chain through the nextResponder method; instead, invoke the superclass implementation and let UIKit handle responder-chain traversal.

  • Do not use round-to-integer code in your touch handling because you lose precision.

    iOS reports touches in a 320x480 coordinate space to maintain source compatiblity. However, on high-resolution devices, the resolution is actually twice as high in each dimension(640x960), which means that touches can land on half-point boundaries on high-resolution devices. On older devices, touches land only on full-point boundaries.

iOS 企业证书签名包ipa无线安装服务器搭建注意事项

  1. 因为iOS 7.1以后要求链接地址为https(itms-services://?action=download-manifest&url=https://10.208.99.199/ota/ios/plist/8239),所以apache服务器要开启https,具体步骤参见:http://www.cnblogs.com/y500/p/3596473.html

在以上步骤需要注意的是,生成SSL证书时,输入的common name要以服务器地址名称一致,如10.208.99.199或者域名,或者

10.*.*.*

通配符形式。

  1. apache配置文件httpd.conf, httpd-ssl.conf, httpd-vhost.conf三个文件中的ServerName 都要配成与证书适配的名称,如10.208.99.199, 此处一定要保持一致。

  2. 在apache httpd.conf配置文件中添加

    AddType application/octet-stream ipa
    

, 是为Mac OS X上的配置,其他请上网查阅一下。

  1. 启动apache服务器

    sudo apachectl -D SSL -k restart
    

通过以上配置,应该是可以搭建无线安装ipa的服务器了。

Linux Web.py网站迁移Mac OS X

  1. 数据库导出

    mysqldump --opt -u name -p dbname > backupfile.sql
    
  2. 在Mac OS X上安装web.py环境
    0)install web.py

    sudo pip install web.py
    

1) 安装mod_wsgi
下载mod_wsgi

http://code.google.com/p/modwsgi/wiki/DownloadTheSoftware?tm=2

解压,

./configure

make

sudo make install

2) 配置apache httpd.conf

#Load mod_wsgi module:
LoadModule wsgi_module libexec/apache2/mod_wsgi.so

WSGIScriptAlias /ota /Users/iosteam/ota_all/website.py/

AddType text/html .py

Alias /ota/static "/Users/iosteam/ota_all/static"

<Directory "/Users/iosteam/ota_all">

  Order deny,allow

  Allow from all

</Directory>

3) 如果仍不能访问,修改对应的webpy启动文件

# for mod_python

app = web.application(urls, globals())

application = app.wsgifunc()
  1. 导入数据库数据

    mysql -u root -p < database.sql
    
  2. 安装python连接数据库包

    sudo pip search MySQL_python
    sudo pip install MySQL_python
    

Linux 服务器高I/O 等待延迟问题查找

  1. 首先是top查看一下系统状况

发现两个参数异常,一是平均负载高,一是cpu %wa一直在50%以上。

查了一下%wa参数的意义:

wa -- iowait
 Amount of time the CPU has been waiting for I/O to complete.
  1. 查看磁盘读写数据

用iostat 查看磁盘状况

$ iostat -x 2 5
 avg-cpu: %user %nice %system %iowait %steal %idle
  3.66 0.00 47.64 48.69 0.00 0.00

 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
 sda 44.50 39.27 117.28 29.32 11220.94 13126.70 332.17 65.77 462.79 9.80 2274.71 7.60 111.41
 dm-0 0.00 0.00 83.25 9.95 10515.18 4295.29 317.84 57.01 648.54 16.73 5935.79 11.48 107.02
 dm-1 0.00 0.00 57.07 40.84 228.27 163.35 8.00 93.84 979.61 13.94 2329.08 10.93 107.02

2.查看进程状态
进程状态表

PROCESS STATE CODES
 D uninterruptible sleep (usually IO)
 R running or runnable (on run queue)
 S interruptible sleep (waiting for an event to complete)
 T stopped, either by a job control signal or because it is being traced.
 W paging (not valid since the 2.6.xx kernel)
 X dead (should never be seen)
 Z defunct ("zombie") process, terminated but not reaped by its parent.

查看到进程中状态D(disk sleep)的进程

# for x in `seq 1 1 10`; do ps -eo state,pid,cmd | grep "^D"; echo "----"; sleep 5; done

查看到有kjournald这个进程
查看这个进程是做什么的?

kjournald是ext3文件系统记录文件系统日志的进程。

查看进程相关状态信息

# cat /proc/487/io
 rchar: 48752567
 wchar: 549961789
 syscr: 5967
 syscw: 67138
 read_bytes: 49020928
 write_bytes: 549961728
 cancelled_write_bytes: 0

查看都往哪写东西了

# lsof -p 487

iOS GCD常用函数总结

GCD提供两种Dispatch Queue, 分别为Concurrent Dispatch Queue 和 Serial Dispatch Queue.

Serial Dispatch Queue 使用一个thread。

Concurrent Dispatch Queue 使用多个thread。

获取Dispatch Queue:

0)dispatch_queue_create

dispatch_queue_t queue = dispatch_queue_create("name.of.your.queue",NULL);

第一个参数为queue的名字,第二个参数传NULL表明要创建一个Serial Dispatch Queue, 传入DISPATCH_QUEUE_CONCURRENT 创建一个 Concurrent Dispatch Queue。
如果是对数据库表更新或更新文件,最好为每一个表或文件建立一个Serial Dispatch Queue,这样能保证只有一个thread会对数据进行更新。

如果对那些不会引起数据不一致问题的任务,需要放到Concurrent Dispatch Queue执行。

1)从Main Dispatch Queue/Global Dispatch Queue获取

//main queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();

//global queue of high priority
dispatch_queue_t globalHigh =         dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);

//global queue of default priority
dispatch_queue_t globalDefault =     dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

//global queue of low priority
dispatch_queue_t globalLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0);

//global queue of background priority
dispatch_queue_t globalBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);

Main Dispatch Queue -> Serial dispatch queue
Global Dispatch Queue -> Concurrent dispatch queue

dispatch_set_target_queue: 主要用来给新建的queue设置优先级

dispatch_queue_t serialQueue = dispatch_queue_create("name.of.queue",NULL);

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);

dispatch_set_target_queue(serialQueue, globalQueue);

需要注意的是,第一个参数是自定义的queue,而不是系统的queue。因为你不能给系统的queue设置权限。通过上面设置,serialQueue 就有了与globalQueue一样的优先级。其实这个函数不仅可以设置queue的优先级,还可以设置queue之间的层级结构。

dispatch_after: 过一段时间执行queue中的task

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);

dispatch_after(time, dispatch_get_main_queue(),^{
           NSLog(@"after 3 seconds...");});

Dispatch Group

0)dispatch_group_notify

dispatch group 可以在group中的dispatch queue都执行完之后,通过一个dispatch_group_notify通知回调。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{NSLog(@"task1");});
dispatch_group_async(group, queue, ^{NSLog(@"task2");});
dispatch_group_async(group, queue, ^{NSLog(@"task3");});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"finished...");});

输出总会保证”finished…”会在最后一句输出。

1)dispatch_group_wait

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{NSLog(@"task1");});
dispatch_group_async(group, queue, ^{NSLog(@"task2");});
dispatch_group_async(group, queue, ^{NSLog(@"task3");});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

dispatch_group_wait提供了一种类似超时的机制,当然如果等待时间设置为DISPATCH_TIME_FOREVER,功能和dispatch_group_notify是一样的。

dispatch_barrier_async

dispatch_barrier_async是一个可以让当前任务等待queue其他任务完成再执行的函数。例如,从数据库读取数据是可以多线程并发读取的,因为这样提高效率。但是,更新数据库数据,就要保证只有一个线程进行更新操作。所以,当更新操作发生的时候,一定要保证读操作和其他更新操作等待。

dispatch_queue_t queue = dispatch_queue_create("barrier.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, read_block_0);
dispatch_async(queue, read_block_1);
dispatch_async(queue, read_block_2);
dispatch_async(queue, read_block_3);
dispatch_async(queue, read_block_4);

dispatch_barrier_async(queue, update_block);

dispatch_async(queue, read_block_5);
dispatch_async(queue, read_block_6);
dispatch_async(queue, read_block_7);
dispatch_async(queue, read_block_8);
dispatch_async(queue, read_block_9);

这样就可以保证只有当更新操作结束后,才会进行其他数据库读操作,保证了数据的一致性。

dispatch_apply

dispatch_apply用于给一个block添加到dispatch queue若干次。 “for”循环

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatch_apply(10, queue, ^(size_t index) { NSLog(@"%zu",index);});

NSLog(@"done...");

dispatch_suspend/dispatch_resume

用于暂停和继续执行queue

dispatch semaphore

dispatch semaphore 适合处理比dispatch queue或者dispatch_barrier_async更小颗粒度的操作。

例如,

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

NSMutableArray *array = [[NSMutableArray alloc] init];

for (int i = 0; i < 100000; i++)
{
dispatch_async(queue, ^{
  [array addObject:[NSNumber numberWithInt:i]];
});
}

如果执行上段代码,因为NSMutableArray不是thread safe的,所以在global queue中对其进行添加对象,会导致程序crash掉。

使用dispatch semaphore我们可以实现多线程对NSMutableArray进行操作

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

NSMutableArray *array = [[NSMutableArray alloc] init];

for (int i = 0; i < 100000; i++)
{
dispatch_async(queue, ^{

  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

  [array addObject:[NSNumber numberWithInt:i]];

  dispatch_semaphore_signal(semaphore);
});
}

dispatch semaphore 是一个信号量的counter,当counter为0,当前线程停止运行,当大于0,自减1,继续执行。

dispatch_semaphore_create

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

初始化一个counter为1的semaphore.
dispatch_semaphore_wait

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

等待counter大于0才运行,第二个参数为等待时间。执行后counter减1.

dispatch_semaphore_signal

dispatch_semaphore_signal(semaphore);

给counter加1
所以在上面的代码中,就可以保证在wait和signal中的[array addObject:[NSNumber numberWithInt:i]]; 就只有一个thread进行操作,保证了线程安全。

dispatch_onece

保证了dispatch_once中的代码只会被执行一次,常用于单例。

+ (id)sharedManager {
static MyManager *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}