Junsong’s Blog


  • 首頁

  • 歸檔

  • 標籤

常用git命令

發表於 2016-07-18   |  

查看、添加、提交、删除、找回,重置修改文件

git help # 显示command的help

git show # 显示某次提交的内容 git show $id

git co – # 抛弃工作区修改

git co . # 抛弃工作区修改

git add # 将工作文件修改提交到本地暂存区

git add . # 将所有修改过的工作文件提交暂存区

git rm # 从版本库中删除文件

git rm –cached # 从版本库中删除文件,但不删除文件

git reset # 从暂存区恢复到工作文件

git reset – . # 从暂存区恢复到工作文件

git reset –hard # 恢复最近一次提交过的状态,即放弃上次提交后的所有本次修改

git ci git ci . git ci -a # 将git add, git rm和git ci等操作都合并在一起做                                    git ci -am “some comments”

git ci –amend # 修改最后一次提交记录

git revert <$id> # 恢复某次提交的状态,恢复动作本身也创建次提交对象

git revert HEAD # 恢复最后一次提交的状态

查看文件diff

git diff # 比较当前文件和暂存区文件差异 git diff

git diff <$id1> <$id2> # 比较两次提交之间的差异

git diff .. # 在两个分支之间比较

git diff –staged # 比较暂存区和版本库差异

git diff –cached # 比较暂存区和版本库差异

git diff –stat # 仅仅比较统计信息

查看提交记录

git log git log # 查看该文件每次提交记录

git log -p # 查看每次详细修改内容的diff

git log -p -2 # 查看最近两次详细修改内容的diff

git log –stat #查看提交统计信息

tig

Mac上可以使用tig代替diff和log,brew install tig

Git 本地分支管理

查看、切换、创建和删除分支

git br -r # 查看远程分支

git br # 创建新的分支

git br -v # 查看各个分支最后提交信息

git br –merged # 查看已经被合并到当前分支的分支

git br –no-merged # 查看尚未被合并到当前分支的分支

git co # 切换到某个分支

git co -b # 创建新的分支,并且切换过去

git co -b # 基于branch创建新的new_branch

git co $id # 把某次历史提交记录checkout出来,但无分支信息,切换到其他分支会自动删除

git co $id -b # 把某次历史提交记录checkout出来,创建成一个分支

git br -d # 删除某个分支

git br -D # 强制删除某个分支 (未被合并的分支被删除的时候需要强制)

分支合并和rebase

git merge # 将branch分支合并到当前分支

git merge origin/master –no-ff # 不要Fast-Foward合并,这样可以生成merge提交

git rebase master # 将master rebase到branch,相当于: git co && git rebase master && git co master && git merge

Git补丁管理(方便在多台机器上开发同步时用)

git diff > ../sync.patch # 生成补丁

git apply ../sync.patch # 打补丁

git apply –check ../sync.patch #测试补丁能否成功

Git暂存管理

git stash # 暂存

git stash list # 列所有stash

git stash apply # 恢复暂存的内容

git stash drop # 删除暂存区

Git远程分支管理

git pull # 抓取远程仓库所有分支更新并合并到本地

git pull –no-ff # 抓取远程仓库所有分支更新并合并到本地,不要快进合并

git fetch origin # 抓取远程仓库更新

git merge origin/master # 将远程主分支合并到本地当前分支

git co –track origin/branch # 跟踪某个远程分支创建相应的本地分支

git co -b origin/ # 基于远程分支创建本地分支,功能同上

git push # push所有分支

git push origin master # 将本地主分支推到远程主分支

git push -u origin master # 将本地主分支推到远程(如无远程主分支则创建,用于初始化远程仓库)

git push origin # 创建远程分支, origin是远程仓库名

git push origin : # 创建远程分支

git push origin : #先删除本地分支(git br -d ),然后再push删除远程分支

Git远程仓库管理

GitHub

git remote -v # 查看远程服务器地址和仓库名称

git remote show origin # 查看远程服务器仓库状态

git remote add origin git@ github:robbin/robbin_site.git # 添加远程仓库地址

git remote set-url origin git@ github.com:robbin/robbin_site.git # 设置远程仓库地址(用于修改远程仓库地址) git remote rm # 删除远程仓库

创建远程仓库

git clone –bare robbin_site robbin_site.git # 用带版本的项目创建纯版本仓库

scp -r my_project.git git@ git.csdn.net:~ # 将纯仓库上传到服务器上

mkdir robbin_site.git && cd robbin_site.git && git –bare init # 在服务器创建纯仓库

git remote add origin git@ github.com:robbin/robbin_site.git # 设置远程仓库地址

git push -u origin master # 客户端首次提交

git push -u origin develop # 首次将本地develop分支提交到远程develop分支,并且track

git remote set-head origin master # 设置远程仓库的HEAD指向master分支

也可以命令设置跟踪远程库和本地库

git branch –set-upstream master origin/master

git branch –set-upstream develop origin/develop

泰国之旅前的做的功课

發表於 2016-07-16   |  

[toc]

餐厅一览

餐厅的优缺点取自于网络评价,仅供参考

The Hilltop

  • 营业时间:早11:00—>晚12:00
  • 餐厅地点:99 Moo 3, Ao Nang, Krabi Town 81000, Thailand
  • 预定电话:+66 75 637 195
  • 预定方式: 酒店可以提前一天帮预定
  • 优点:
  • 位于山顶
  • 露天餐厅
  • 可看日落(日落下午六点左右)
  • 有乐队演奏
  • 据说餐厅有接送
  • 缺点:
  • 贵(人均500-1000THB)
  • 菜味道一般
  • 参考图片:
    Alt text
    Alt text
    Alt text
    Alt text
  • 个人建议:如果想体验一把夕阳晚餐,可以选择,但最好是天气好的时候去,预定早点

    Jungle Kitchen

  • 入选理由:猫途鹰上排名第一
  • 营业时间:上午10:00-下午2:30 || 下午4:30-晚上10:00
  • 餐厅地点:33, Moo 2, AO Nang Road, Ao Nang, Krabi Town, Thailand
  • 预定电话:没找到
  • 优点:
  • 东西好吃,网上都这么说
  • 便宜,超级便宜!
  • 环境还可以,原始人小屋
    1
    2
    取一段评论
    從和平拉古娜步行大約20分鐘,在街道轉角處的左側。每道菜約50~100銖,包含麵飯等主食與冰砂都是這個價格,菜單以料理方式分類,可以自己搭配炒哪種肉或蝦子,菜滿好吃的,辣度約 little~small就行了。
  • 缺点:
  • 远,没去过,估计30分钟路程,找到路的情况下
  • 没预定方式
  • 有蚊子,记得擦防蚊水
  • 参考图片:
    Alt text
    Alt text
    Alt text
    Alt text
    Alt text
    Alt text
  • 个人建议:想去的话得提前预定,没电话,估计得找到店面才能预定,好吃就得花费点功夫,不是吗~
  • 网上推荐菜式:
    1
    2
    3
    4
    这些菜英文是什么?%>_<%
    菠萝烤鱼,咖喱螃蟹,黑椒炒虾,泰式炒河粉,马斯曼
    Tom Yum Goong soup , Red Curry,
    Massaman Curry

Alt text
Alt text

Ton Ma Yom Thai Food Restaurant

  • 入榜理由:猫途鹰排名第二,重要的是,有电话!
  • 营业时间:8am-2pm 5pm-9.30pm
  • 餐厅地点:262/7 Moo 2, Soi 11 Leelavalley, Ao Nang, Krabi Town 81000, Thailand
  • 预订电话:+66 89 735 0605
  • 预订方式:前台预订,电话预订
  • 优点:
  • 好吃!
  • 便宜
  • 正宗
  • 这些还不够吗?
  • 缺点
  • 偏僻吧,没去过,要导航
  • 参考图片
    Alt text
    Alt text
    Alt text

  • 个人建议:想尝尝正宗的泰国菜,就去吧!

  • 网上推荐菜式
    1
    芒果糯米饭,泰国炒河粉,椰子咖喱,绿咖喱牛肉,炒杂菜,冬阴功海鲜汤

Alt text
Alt text
Alt text

  • 一些好评:
    Alt text
    Alt text
    Alt text

    Diver’s Inn Steakhouse and International Cuisine

  • 入榜理由,吃腻了泰国菜,还有欧洲菜呀,别说我考虑不周全,到时想换口味别找我哦!
  • 营业时间:11am-11pm
  • 餐厅地点:27/9 Moo 2 | 150 M Above Mc Donalds, Opp. Siam Commercial Bank, Ao Nang, Krabi Town 81180, Thailand
  • 预订电话:+66 75 637 297
  • 预订方式:电话预定
  • 憋说话!先看图
    Alt text
    Alt text
    Alt text
  • 一些好评:
    Alt text
    Alt text
    Alt text
  • 推荐菜式:
    1
    肋眼牛排(rib eye)

Lae Lay Grill

  • 入榜理由

    May&Zin. Restaurant

  • 入榜理由

    零食药品

  • mama方便面
  • 泰国红牛
  • 防蚊药

图片无限循环

發表於 2016-07-12   |  

ps:文中imageView与image是划分得非常清楚的,image有时也用图片表示

  • 图片无限循环的文章网上已经有一大堆,但是我还是希望自己能写一个非常详细的解读文章出来
  • 我会先一步一步文字加图片描述实现的原理,最后再上代码
  • 在无限循环中我只使用3张ImageView,在不断的滑动过程中,我不会去移动ImageView的位置,我要做的只有两件事:
  • 每次滑动后,重新计算好imageView新的image,然后设置给imageView
  • 紧接着重新设置scrollView的contentoffsize

  • 接下来我会图解上面所说的两个步骤:

  • 先假设我有5张广告图片,由于宽度问题,我只显示三张,图片中的数字代表第几张图片,看清楚,是图片不是imageView!
    初始图片显示状态
  • 一开始,把contentoffset设置到中间的那张imageView上,也就是显示中间的imageView,并且以后每次滚动完都会进行这样的设置,这里先记住
  • 图中显示的是第0张图片,所以向左滑动显示的肯定是最后一张图片,也就是第4张,同理,右滑是第1张
  • 所以,三张imageView分别放置第4,第0,第1张图片,是非常正常的
  • 接下来,我们向右滑动:
    滑动后应该显示的状态
  • 大家注意看了,因为我们只设置了三张图片,那么经过我们向右滑动,屏幕应该显示的应该是第1张,而左边的imageView放置的应该是第0张图片,右边的imageView放置的则是第2张图片,这是理所当然的
  • 这时,大家肯定会有疑问,我们刚刚不是右滑了么,屏幕应该停留在第三张imageView上,并且右边已经没有imageView了,scrollView已经滑动到头了,而左边两张imageView依然放置的是第4张和第0张图片啊,你现在是神马情况?
  • 大家的疑问是非常正确的,那么我想请大家想一下,如果我在向右滑动这个用户操作结束后,我马上替换这三张imageView的image,让三张imageView分别显示第0,第1,第2张图片,并且,马上设置scrollView的contentoffset于中间的那张imageView上,看清楚哈,是马上!
  • 经过上面那一小段的操作,现在我们的scrollView不就是这样一个状态了么:
  • 这时候,有个关键点我必须详细说明一下,在上述步骤操作中,我的确是先把每个imageView应该显示的图片设置完毕后,再来设置contentoffset回归于中间
  • 也就是说,按照正常逻辑,屏幕曾有那么一段时间显示的应该是第3个imageView上的第2张图片,然后再经过强制设置contentoffset,再次显示第2张imageView的图片
  • 我曾经也有过这样的疑问,但是大家想想看,代码运行一行的需要的时间是多少?当前一行代码刚把界面的图片给换了,后一行代码就马上把contentoffset给重置了,你觉得我们能够看到刚替换的图片么?
  • 所以说,无限循环就是一个赤果果的障眼法!

    代码解释

  • 我先分段代码解释,再在最后上完整的代码
  • 整个无限循环功能其实重点只在接下来的三个方法里
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//这四个方法是灵魂
//重置imageView的图片与重置contentoffset于中间的方法
- (void)updateImage
{
//1.遍历scrollview的三个imageView
for (NSUInteger i = 0; i < self.scrollView.subviews.count; i++) {
//1.1按顺序拿到imageView
UIImageView *imagV = self.scrollView.subviews[i];
//1.2拿到当前显示的页数
NSInteger index = self.pageControl.currentPage;
//1.3如果是第一张imageView,也就是最左边的那张imageView
if (i == 0) {
index--;//1.4应当显示的图片应该是当前页数(显示的图片前一张)减一
}else if(i == 2)//1.5如果是第三张imageView,也就是最右边的imageView
{
index++;//1.6应当显示的图片应该是当前页数(显示的图片后一张)加一
}
//2.1两种特殊情况
//2.2当中间的imageView显示的是第0张图片,那么currentpage就是0,左边的imageView按理来说应该显示的是最后一张图片,这案例有五张图片,所以也就是第4张,由于之前index--为-1,所以进入判断
if (index < 0) {
index = self.pageControl.numberOfPages - 1;//2.3把页数重置位最后一张
}else if(index >= self.pageControl.numberOfPages )//2.4这是另一种特殊情况,当滑动到最右边,也就是中间imageView显示的是第五张图片时,页码为5,currentpage为4,由于前面index++为5,所以进入该if判定,将右边的imageView的currentpage设为0
{
index = 0;//2.5将右边的imageView的currentpage设为0
}
imagV.tag = index;//将每一张imageView的currentpage作为其tag值,用于scrollViewDidScroll方法计算currentpage用
imagV.image = [UIImage imageNamed:self.imageNames[index]];//重置三张imageView应当显示的图片
}
//前面重新设置完imageView的image后,重置contentoffset,使scrollview再次显示中间的imageView,设置不带动画,所以用户无法感觉到界面有所重置
if (_isPortrait) {
self.scrollView.contentOffset = CGPointMake(0, self.scrollView.frame.size.height);
}else
{
self.scrollView.contentOffset = CGPointMake(self.scrollView.frame.size.width, 0);
}
}
//定时器要走的方法,跳转到下一张imageView
- (void)next
{
//通过设置contentoffset来跳转下一张,是带动画的
if (_isPortrait) {
[self.scrollView setContentOffset:CGPointMake(0, 2 * self.scrollView.frame.size.height) animated:YES];
}else
{
[self.scrollView setContentOffset:CGPointMake(2 * self.scrollView.frame.size.width, 0) animated:YES];
}
}
//在这个scrollView一动就会调用的方法里计算currentPage
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//计算的思路就是取出三张imageView,判断三个imageView的origin.x也就是imageView左上角的x与当前contentoffset.X
差值最小的,那么,这个imageView的tag值就是当前的currentPage,这个思路很简单,就不过多阐述
NSInteger page = 0;
CGFloat minDistance = MAXFLOAT;
for (int i = 0; i<self.scrollView.subviews.count; i++) {
UIImageView *imageView = self.scrollView.subviews[i];
CGFloat distance = 0;
if (_isPortrait) {
distance = ABS(imageView.frame.origin.y - scrollView.contentOffset.y);
}else
{
distance = ABS(imageView.frame.origin.x - scrollView.contentOffset.x);//计算差值,并且取绝对值
}
if (distance < minDistance) {
minDistance = distance;
page = imageView.tag;
}
}
self.pageControl.currentPage = page;//设置当前页数
}
//每次定时跳转动画或者拖拽滚动结束后,调用重置方法,把图片与位置都重置,进入新的循环
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView//timer动画结束调用
{
[self updateImage];
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView//拖拽滚动停止调用
{
[self updateImage];
}
  • 我已经将无限循环的灵魂思想与代码结合在一起,在上面的代码中详细叙述了,其他的就不过多解释了,解释多了反而会造成反效果,接下来是这个无限循环的整体代码,欢迎拿来使用

完整代码

使用

1
2
3
4
5
6
7
8
//控制器中的使用代码
/*
loopPageView *loopV = [[loopPageView alloc]init];
loopV.imageNames = @[@"img_00",@"img_01",@"img_02",@"img_03",@"img_04"];
loopV.frame = CGRectMake(0, 0, 300, 130);
// loopV.isPortrait = YES;//打开就会竖着显示
[self.view addSubview:loopV];
*/

loopPageView.h

1
2
3
4
5
6
7
8
9
10
//loopPageView.h
#import <UIKit/UIKit.h>
@interface loopPageView : UIView
@property (nonatomic ,strong)NSArray *imageNames;
/**
* 是否垂直显示
*/
@property (nonatomic,assign) BOOL isPortrait;
@end

loopPageView.m

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//loopPageView.m
#import "loopPageView.h"
#define imageViewCount 3
@interface loopPageView ()<UIScrollViewDelegate>
@property (nonatomic,weak) UIScrollView *scrollView;
@property (nonatomic,weak) UIPageControl *pageControl;
@property (nonatomic,weak) NSTimer *timer;
@end
@implementation loopPageView
#pragma mark -
#pragma mark Life Cycle
-(instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
//scrollView
UIScrollView *scrollV = [[UIScrollView alloc]init];
//这里一定得设置啊,不然用来计算的scrollview的subviews这个数值不对了
scrollV.showsHorizontalScrollIndicator = NO;
scrollV.showsVerticalScrollIndicator = NO;
scrollV.delegate = self;
scrollV.pagingEnabled = YES;
self.scrollView = scrollV;
[self addSubview:scrollV];
//添加三张imageView
for (NSUInteger i = 0; i < imageViewCount; i++) {
UIImageView *imageV = [[UIImageView alloc]init];
[self.scrollView addSubview:imageV];
}
//pagecontrol
UIPageControl *pageC = [[UIPageControl alloc]init];
self.pageControl = pageC;
[self addSubview:pageC];
}
return self;
}
-(void)layoutSubviews
{
[super layoutSubviews];
CGFloat w = self.frame.size.width;
CGFloat h = self.frame.size.height;
self.scrollView.frame = CGRectMake(0, 0, w, h);
self.pageControl.frame = CGRectMake(0, h - 37, w, 37);
if (_isPortrait) {
[self.scrollView.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
UIImageView *img = (UIImageView *)obj;
img.frame = CGRectMake(0, idx * h, w, h);
}];
self.scrollView.contentSize = CGSizeMake(0, imageViewCount * self.scrollView.frame.size.height);
}else
{
[self.scrollView.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
UIImageView *img = (UIImageView *)obj;
img.frame = CGRectMake(idx * w, 0, w, h);
}];
self.scrollView.contentSize = CGSizeMake(imageViewCount * self.frame.size.width, 0);
}
self.pageControl.numberOfPages = self.imageNames.count;
self.pageControl.currentPage = 0;
[self updateImage];
// [self performSelectorInBackground:@selector(startTimer) withObject:nil];
}
//在layoutSubviews中做操作时,要小心,它会走至少两次,之前无形中走了两次startTimer方法,然后创建了两个timer,虽然后面我打self.timer是null,但是还有一个timer,虽然表面上没有强制指针引用它,实际上它在创建出来的时候就有强指针引用它了,然后我没法获取另一个timer并且invalidate掉,尼玛的内存泄露啊,而且他奶奶的还一直操控我的程序,好坑爹!!!
#pragma mark -
#pragma mark <UIScrollViewDelegate>
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self stopTimer];
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
[self startTimer];
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self updateImage];
}
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
[self updateImage];
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSInteger page = 0;
CGFloat minDistance = MAXFLOAT;
for (int i = 0; i<self.scrollView.subviews.count; i++) {
UIImageView *imageView = self.scrollView.subviews[i];
CGFloat distance = 0;
if (_isPortrait) {
distance = ABS(imageView.frame.origin.y - scrollView.contentOffset.y);
}else
{
distance = ABS(imageView.frame.origin.x - scrollView.contentOffset.x);
}
if (distance < minDistance) {
minDistance = distance;
page = imageView.tag;
}
}
self.pageControl.currentPage = page;
}
#pragma mark -
#pragma mark private methods
- (void)updateImage
{
for (NSUInteger i = 0; i < self.scrollView.subviews.count; i++) {
UIImageView *imagV = self.scrollView.subviews[i];
NSInteger index = self.pageControl.currentPage;
if (i == 0) {
index--;
}else if(i == 2)
{
index++;
}
if (index < 0) {
index = self.pageControl.numberOfPages - 1;
}else if(index >= self.pageControl.numberOfPages )
{
index = 0;
}
imagV.tag = index;
imagV.image = [UIImage imageNamed:self.imageNames[index]];
}
if (_isPortrait) {
self.scrollView.contentOffset = CGPointMake(0, self.scrollView.frame.size.height);
}else
{
self.scrollView.contentOffset = CGPointMake(self.scrollView.frame.size.width, 0);
}
}
- (void)next
{
if (_isPortrait) {
[self.scrollView setContentOffset:CGPointMake(0, 2 * self.scrollView.frame.size.height) animated:YES];
}else
{
[self.scrollView setContentOffset:CGPointMake(2 * self.scrollView.frame.size.width, 0) animated:YES];
}
}
- (void)startTimer
{
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0f target:self selector:@selector(next) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop]addTimer:self.timer forMode:NSRunLoopCommonModes];
}
-(void)stopTimer
{
[self.timer invalidate];
self.timer = nil;
}
#pragma mark -
#pragma mark setter and getter
-(void)setImageNames:(NSArray *)imageNames
{
_imageNames = imageNames;
[self startTimer];
}
@end

消息转发

發表於 2016-07-12   |  
  • 消息转发的一些源码分析
1
2
3
4
5
6
7
8
//正常调用一个方法都会转化成这个函数
id objc_msgSend(id self, SEL op, ...) {
if (!self) return nil;
//获取方法的实现,通过isa指针,去类里用SEL找方法
//同时记住,SEL其实也就是字符串罢了
IMP imp = class_getMethodImplementation(self->isa, SEL op);
imp(self, op, ...); //调用这个函数,伪代码...
}
  • 接下来去到方法寻找函数里
1
2
3
4
5
6
7
8
9
10
//查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
if (!cls || !sel) return nil;
//真正的寻找方法在下面这个函数里,这里负责防止类或方法为空
IMP imp = lookUpImpOrNil(cls, sel);
//如若没有找到方法实现,那么就进入了我们熟悉的下一个消息转发机制
if (!imp) return _objc_msgForward;
//_objc_msgForward 用于消息转发
return imp;
}
  • 查找方法实现的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
IMP lookUpImpOrNil(Class cls, SEL sel) {
//如果类没有初始化,那么初始化它,熟悉的懒加载呀
if (!cls->initialize()) {
_class_initialize(cls);
}
Class curClass = cls;
IMP imp = nil;
do { //先查缓存,缓存没有时重建,仍旧没有则向父类查询
if (!curClass) break;//如果类不存在,跳出
if (!curClass->cache) fill_cache(cls, curClass);//创建缓存,也是懒加载
//在缓存中查找方法
imp = cache_getImp(curClass, sel);
//找到跳出
if (imp) break;
} while (curClass = curClass->superclass);//一直向父类找,最终为空时跳出,NSObeject类的父类就为空,元类不为空
return imp;
}
  • Apple没有公开_objc_msgForward的实现源码,_objc_msgForward是一个函数指针(和 IMP 的类型一样),是用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
  • _objc_msgForward消息转发做的几件事:
1
2
3
4
5
1.调用resolveInstanceMethod:方法 (或 resolveClassMethod:)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调用并返回YES,那么重新开始objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。如果仍没实现,继续下面的动作。
2.调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。
3.调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:。
4.调用forwardInvocation:方法,将第3步获取到的方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非ni。
5.调用doesNotRecognizeSelector: ,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。
  • 上面前4个方法均是模板方法,开发者可以override,由 runtime 来调用。最常见的实现消息转发:就是重写方法3和4,吞掉一个消息或者代理给其他对象都是没问题的
  • 直接调用_objc_msgForward是非常危险的事,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。
  • _objc_msgForward隶属 C 语言,有三个参数 :
1
2
3
1.所属对象 ->id类型
2.方法名 ->SEL类型
3.可变参数 ->可变参数类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
那么 objc_msgSend 到底是怎么工作的呢
在Objective-C中,消息直到运行时才会绑定到方法的实现上。编译器会把代码中[target doSth]转换成 objc_msgSend消息函数,这个函数完成了动态绑定的所有事情。它的运行流程如下:
检查selector是否需要忽略。(ps: Mac开发中开启GC就会忽略retain,release方法。)
检查target是否为nil。如果为nil,直接cleanup,然后return。(这就是我们可以向nil发送消息的原因。)
然后在target的Class中根据Selector去找IMP
寻找IMP的过程:
先从当前class的cache方法列表(cache methodLists)里去找
找到了,跳到对应函数实现
没找到,就从class的方法列表(methodLists)里找
还找不到,就到super class的方法列表里找,直到找到基类(NSObject)为止
最后再找不到,就会进入动态方法解析和消息转发的机制。

runloop

發表於 2016-07-12   |  
  • Run Loop 观察者
  • Run loop 入口
  • Run loop 何时处理一个定时器
  • Run loop 何时处理一个输入源
  • Run loop 何时进入睡眠状态
  • Run loop 何时被唤醒,但在唤醒之前要处理的事件
  • Run loop 终止
  • 手动添加观察者
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 创建Observer
/*
第1个参数: 指定如何给observer分配存储空间
第2个参数: 需要监听的状态类型/ kCFRunLoopAllActivities监听所有状态
第3个参数: 是否每次都需要监听
第4个参数: 优先级
第5个参数: 监听到状态改变之后的回调
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即将进入runloop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理source");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入睡眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"刚从睡眠中唤醒");
break;
case kCFRunLoopExit:
NSLog(@"即将退出");
break;
default:
break;
}
});
// 给主线程的RunLoop添加一个观察者
/*
第1个参数: 需要给哪个RunLoop添加观察者
第2个参数: 需要添加的Observer对象
第3个参数: 在哪种模式下可以可以监听
*/
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
// 释放对象
CFRelease(observer);
  • 每次运行 run loop,你线程的runloop对会自动处理之前未处理的消息,并通 知相关的观察者。具体的顺序如下:
  • 1.通知观察者 run loop 已经启动
  • 2.通知观察者任何即将要开始的定时器
  • 3.通知观察者任何即将启动的非基于端口的源
  • 4.启动任何准备好的非基于端口的源
  • 5.如果基于端口的源准备好并处于等待状态,立即启动;并进入步骤 9。
  • 6.通知观察者线程进入休眠
  • 8.将线程置于休眠直到任一下面的事件发生:
  • 某一事件到达基于端口的源
  • 定时器启动
  • Run loop 设置的时间已经超时
  • run loop 被显式唤醒
  • 9.处理未处理的事件
  • 如果用户定义的定时器启动,处理定时器事件并重启 run loop。进入步骤 2
  • 如果输入源启动,传递相应的消息
  • 如果 run loop 被显式唤醒而且时间还没超时,重启 run loop。进入步骤 2
  • 启动 run loop 只对程序的辅助线程有意义。一个 run loop 通常必须包含一个输入源或定时器来监听事件。如果一个都没有,run loop 启动后立即退出。
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/// 1. 通知Observers,即将进入RunLoop
/// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
do {
/// 2. 通知 Observers: 即将触发 Timer 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 4. 触发 Source0 (非基于port的) 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 6. 通知Observers,即将进入休眠
/// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
/// 7. sleep to wait msg.
mach_msg() -> mach_msg_trap();
/// 8. 通知Observers,线程被唤醒
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
/// 9. 如果是被Timer唤醒的,回调Timer
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
/// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
/// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
} while (...);
/// 10. 通知Observers,即将退出RunLoop
/// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);

rumTime基础使用

發表於 2016-07-10   |  

获取对象的所有属性,不包括属性值

1
2
3
4
5
6
7
8
9
10
11
12
13
- (NSArray *)getAllProperties
{
u_int count;
objc_property_t *properties =class_copyPropertyList([self class], &count);
NSMutableArray *propertiesArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i<count; i++)
{
const char* propertyName =property_getName(properties[i]);
[propertiesArray addObject: [NSString stringWithUTF8String: propertyName]];
}
free(properties);
return propertiesArray;
}

获取对象的所有属性 以及属性值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (NSDictionary *)properties_aps
{
NSMutableDictionary *props = [NSMutableDictionary dictionary];
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (i = 0; i<outCount; i++)
{
objc_property_t property = properties[i];
const char* char_f =property_getName(property);
NSString *propertyName = [NSString stringWithUTF8String:char_f];
id propertyValue = [self valueForKey:(NSString *)propertyName];
if (propertyValue) [props setObject:propertyValue forKey:propertyName];
}
free(properties);
return props;
}

获取对象的所有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-(void)printMothList
{
unsigned int mothCout_f =0;
Method* mothList_f = class_copyMethodList([self class],&mothCout_f);
for(int i=0;i<mothCout_f;i++)
{
Method temp_f = mothList_f[i];
IMP imp_f = method_getImplementation(temp_f);
SEL name_f = method_getName(temp_f);
const char* name_s =sel_getName(method_getName(temp_f));
int arguments = method_getNumberOfArguments(temp_f);
const char* encoding =method_getTypeEncoding(temp_f);
NSLog(@"方法名:%@,参数个数:%d,编码方式:%@",[NSString stringWithUTF8String:name_s],
arguments,[NSString stringWithUTF8String:encoding]);
}
free(mothList_f);
}

多线程之NSOperation

發表於 2016-07-10   |  

warning tip

  • GCD中的global并行队列是单例,只是在栅栏队列里由于不知名原因,表现得不是一个队列,但是实际还是一个单例
  • 开发中要自定义一个全局队列的话使用正常的单例就可以了

  • NSOperation有两个子类,NSBlockOperation和NSInvocationOperation,正常情况它们是这样使用的
  • NSBlockOperation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//1. 封装任务
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
// 主线程
NSLog(@"1---%@", [NSThread currentThread]);
}];
// 2.追加其它任务
// 注意: 在没有队列的情况下, 如果给BlockOperation追加其它任务, 那么其它任务会在子线程中执行
[op1 addExecutionBlock:^{
NSLog(@"2---%@", [NSThread currentThread]);
}];
[op1 addExecutionBlock:^{
NSLog(@"3---%@", [NSThread currentThread]);
}];
// 3.启动任务
[op1 start];
  • NSInvocationOperation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 注意: 父类不具备封装操作的能力
// NSOperation *op = [[NSOperation alloc] init];
// 1.封装任务
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// 2.要想执行任务必须调用start
[op1 start];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2) object:nil];
[op2 start];
- (void)run
{
NSLog(@"%@", [NSThread currentThread]);
}
- (void)run2
{
NSLog(@"%@", [NSThread currentThread]);
}

  • 上面的代码并没有主动控制线程,NSInvocationOperation的执行线程都是在主线程中,NSBlockOperation第一个block在主线程中,追加的则在子线程中,一般开发中我们不会这样直接使用,需要配合队列使用
  • 配合队列很简单

  • NSOperationQueue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建任务
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1 == %@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2 == %@", [NSThread currentThread]);
}];
// 注意: 如果是使用block来封装任务, 那么有一种更简便的方法
// 只要利用队列调用addOperationWithBlock:方法, 系统内部会自动封装成一个NSBlockOperation \
然后再添加到队列中
[queue addOperationWithBlock:^{
NSLog(@"3 == %@", [NSThread currentThread]);
}];
// 3.添加任务到队列
[queue addOperation:op1];
[queue addOperation:op2];
  • NSInvocationOperation
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
28
29
30
31
32
// 1.创建队列
/*
GCD中有哪些队列:
并发: 自己创建, 全局
串行: 自己创建, 主队列
NSOperationQueue:
主队列: mainQueue
自己创建: 会在子线程中执行
*/
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建任务
// 只要是自己创建的队列, 就会在子线程中执行
// 而且默认就是并发执行
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil];
// 3.添加任务到队列中
// 只要将任务添加到队列中, 队列会自动调用start
[queue addOperation:op1];
[queue addOperation:op2];
- (void)download1
{
NSLog(@"1 == %@", [NSThread currentThread]);
}
- (void)download2
{
NSLog(@"2 == %@", [NSThread currentThread]);
}
  • 上面的都会自动创建多个线程同时进行,所以都是并行队列,如果想要串行执行,就把最大并行数设置为1就可以了
1
queue.maxConcurrentOperationCount = 1;
  • NSOperation有个好处就是可以自定义任务
  • 创建一个继承自NSOperation的对象
1
@interface HJSOperation : NSOperation
  • 然后.m文件里把想要执行的任务填写在重写的main函数里
1
2
3
4
5
6
7
8
/*
只要将任务添加到队列中, 那么队列在执行自定义任务的时候
就会自动调用main方法
*/
- (void)main
{
NSLog(@"%s, %@", __func__, [NSThread currentThread]);
}
  • 自定义NSOperation的操作方法
1
2
3
4
5
6
7
8
9
10
11
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建任务
// 自定义任务的好处: 提高代码的复用性
XMGOperation *op1 = [[XMGOperation alloc] init];
XMGOperation *op2 = [[XMGOperation alloc] init];
// 3.添加任务到队列
[queue addOperation:op1];
[queue addOperation:op2];

NSOperation的暂停、恢复和取消

  • NSOperation中还有暂停(suspended),恢复(!suspended)和取消(cancelAllOperations)功能,其实操作很简单,就拿到对应的队列操作就可以了,但是有些注意点
  • 如果是一个串行队列,里面有多个任务,当队列设置为取消或者暂停的时候,那么正在执行的任务不会暂停,知道执行结束,下一个任务不会再执行
  • 如下面的多个任务
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 1;
// 2.创建任务
/*
[self.queue addOperationWithBlock:^{
NSLog(@"++++++++++++++++++++++++++++++++++++++");
// [NSThread sleepForTimeInterval:1];
// NSLog(@"1 == %@", [NSThread currentThread]);
for (int i = 0; i < 10000; i++) { // 500
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
NSLog(@"++++++++++++++++++++++++++++++++++++++");
// [NSThread sleepForTimeInterval:1];
// NSLog(@"2 == %@", [NSThread currentThread]);
for (int i = 0; i < 10000; i++) {
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
NSLog(@"++++++++++++++++++++++++++++++++++++++");
// [NSThread sleepForTimeInterval:1];
// NSLog(@"3 == %@", [NSThread currentThread]);
for (int i = 0; i < 10000; i++) {
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
NSLog(@"++++++++++++++++++++++++++++++++++++++");
// [NSThread sleepForTimeInterval:1];
// NSLog(@"4 == %@", [NSThread currentThread]);
for (int i = 0; i < 10000; i++) {
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
NSLog(@"++++++++++++++++++++++++++++++++++++++");
// [NSThread sleepForTimeInterval:1];
// NSLog(@"5 == %@", [NSThread currentThread]);
for (int i = 0; i < 10000; i++) {
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
NSLog(@"++++++++++++++++++++++++++++++++++++++");
// [NSThread sleepForTimeInterval:1];
// NSLog(@"6 == %@", [NSThread currentThread]);
for (int i = 0; i < 10000; i++) {
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
}];
*/
  • 暂停代码
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
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// [self operation1];
/*
// 只要设置队列的suspended为YES, 那么就会暂停队列中其它任务的执行
// 也就是说不会再继续执行没有执行到得任务
// 只要设置队列的suspended为NO, 那么就会恢复队列中其它任务的执行
if (self.queue.suspended) {
self.queue.suspended = NO;
}else
{
// 注意: 设置为暂停之后, 不会立即暂停
// 会继续执行当前正在执行的任务, 直到当前任务执行完毕, 就不会执行下一个任务了
// 也就是说, 暂停其实是暂停下一个任务, 而不能暂停当前任务
// 注意: 暂停是可以恢复的
self.queue.suspended = YES;
}
*/
// 取消队列中所有的任务的执行
// 取消和暂停一样, 是取消后面的任务, 不能取消当前正在执行的任务
// 注意: 取消是不可以恢复的
[self.queue cancelAllOperations];
}
  • 由于暂停受到如此的限制,所以即使苹果也建议我们在自定义的任务中,做耗时操作时,可以分段做出一些判断,当察觉到线程状态改变时,及时主动停止任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)main
{
// 耗时操作1
for (int i = 0; i < 10000; i++) { // 500
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
NSLog(@"++++++++++++++++++++++++++++++++++++++");
if (self.isCancelled) {
return;
}
// 耗时操作2
for (int i = 0; i < 10000; i++) { // 500
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
if (self.isCancelled) {
return;
}
NSLog(@"++++++++++++++++++++++++++++++++++++++");
// 好所操作3
for (int i = 0; i < 10000; i++) { // 500
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
}
  • 自定义任务只能访问isCancelled属性,在几个耗时代码之间插入判断,当线程被取消时,主动return

线程依赖

  • 这里有点类似于GCD中的栅栏,但是它更厉害一些,栅栏的使用必须要在同一队列中,而NSOperation没有这样的限制
  • GCD栅栏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("com.520it.lmj", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1-------%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-------%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-------%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue2, ^{
NSLog(@"4-------%@", [NSThread currentThread]);
});
  • 上面的栅栏无法起到限制作用,栅栏依然有可能在前面的异步之前执行,因为必须要在同一队列中
  • 再来看看NSOperation的
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
// 2.创建任务
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1-------%@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2-------%@", [NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3-------%@", [NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"4-------%@", [NSThread currentThread]);
for (int i = 0; i < 1000; i++) {
NSLog(@"%i", i);
}
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"5-------%@", [NSThread currentThread]);
}];
// 3.添加依赖
[op5 addDependency:op1];
[op5 addDependency:op2];
[op5 addDependency:op3];
[op5 addDependency:op4];
// 4.监听op4什么时候执行完毕
op4.completionBlock = ^{
NSLog(@"op4中所有的操作都执行完毕了");
};
// 4.添加任务到队列
[queue addOperation:op1];
[queue addOperation:op2];
[queue2 addOperation:op3];
[queue2 addOperation:op4];
[queue addOperation:op5];
  • 上面的代码无论执行多少次,op5都只会在最后执行,并且没有队列限制

线程间的通信

  • 知道了怎么添加线程依赖后,就可以来学习线程通信了
  • 因为线程通信牵扯到线程的先后执行,只有当耗时操作完成后,才能来通知主线程刷新UI,这就是线程通信
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1.创建一个新的队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.添加任务(操作)
[queue addOperationWithBlock:^{
// 2.1在子线程中下载图片
NSURL *url = [NSURL URLWithString:@"http://imgcache.mysodao.com/img2/M04/8C/74/CgAPDk9dyjvS1AanAAJPpRypnFA573_700x0x1.JPG"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 2.2回到主线程更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
}];
  • 上面的代码没有体现出线程依赖的使用,下面弄个有线程依赖的
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 1.创建一个队列
// 一般情况下, 在做企业开发时候, 都会定义一个全局的自定义队列, 便于使用
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.添加一个操作下载第一张图片
__block UIImage *image1 = nil;
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://imgcache.mysodao.com/img2/M04/8C/74/CgAPDk9dyjvS1AanAAJPpRypnFA573_700x0x1.JPG"];
NSData *data = [NSData dataWithContentsOfURL:url];
image1 = [UIImage imageWithData:data];
}];
// 3.添加一个操作下载第二张图片
__block UIImage *image2 = nil;
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://imgcache.mysodao.com/img1/M02/EE/B5/CgAPDE-kEtqjE8CWAAg9m-Zz4qo025-22365300.JPG"];
NSData *data = [NSData dataWithContentsOfURL:url];
image2 = [UIImage imageWithData:data];
}];
// 4.添加一个操作合成图片
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
[image1 drawInRect:CGRectMake(0, 0, 100, 200)];
[image2 drawInRect:CGRectMake(100, 0, 100, 200)];
UIImage *res = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 5.回到主线程更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = res;
}];
}];
// 6.添加依赖
[op3 addDependency:op1];
[op3 addDependency:op2];
// 7.添加操作到队列中
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
  • 不管怎样,UI的刷新必定在数据下载线程完毕之后

    同步在串行队列里造成死锁的情况解析

    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    /*
    1:
    - (void)viewDidLoad {
    //dispatch_sync(dispatch_get_main_queue(), ^{
    //NSLog(@3);
    //死锁原因
    //1:dispatch_sync在等待block语句执行完成,而block语句需要在主线程里执行,所以dispatch_sync如果在主线程调用就会造成死锁
    //2:dispatch_sync是同步的,本身就会阻塞当前线程,也即主线程。而又往主线程里塞进去一个block,所以就会发生死锁。
    //});
    //dispatch_async(dispatch_get_global_queue(), ^{
    //async 在主线程中 创建了一个异步线程 加入 全局并发队列,async 不会等待block 执行完成,立即返回
    NSLog(@2);//不会造成死锁;
    });
    }
    分析这段代码:view DidLoad 在主线程中,也即dispatch_get_main_queue()中,执行到sync时向dispatch_get_main_queue()插入同步thread,sync会等到后面的block执行完成才返回。sync又在主队列里面,是个串行队列,sync是后面才加入的,前面一个是主线程,所以sync想执行block必须等待前一个主线程执行完成,而主线程却
    在等待sync返回,去执行后续工作,从而造成死锁。
    2:
    dispatch_sync 和 dispatch_async 区别:
    dispatch_async(queue,block) async 异步队列,dispatch_async 函数会立即返回, block会在后台异步执行。
    dispatch_sync(queue,block) sync 同步队列,dispatch_sync 函数不会立即返回,即阻塞当前线程,等待 block同步执行完成。
    3:
    GCD Queue 分为三种:
    1,The main queue :主队列,主线程就是在个队列中。
    2,Global queues : 全局并发队列。
    3,用户队列:是用函数 dispatch_queue_create 创建的自定义队列
    */
  • 队列是管理任务的,当我们用dispatch_async或者dispatch_sync这些函数加入队列中的时候,只是把任务塞给了队列

  • 然后由队列来分配线程执行任务
  • 那么async与sync其实指的就是任务的优先级
  • 在串行队列中,任务的执行是按顺序的,那么排在最前面的任务优先级最高
  • 在并行队列中,任务执行不按顺序
  • dispatch_sync本身是一个函数,它执行同时又要执行它的代码块,这些任务都是相同优先级,会对线程资源造成抢占
  • 而在dispatch_async没有任务的顺序强制性,所以,不会造成资源抢占

AFNet上传

發表於 2016-07-10   |  
  • 上传
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSDictionary *dict = @{@"username":@"hjs"};
[manager POST:@"http://120.25.226.186:32812/upload" parameters:dict constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
NSURL *url = [NSURL fileURLWithPath:@"/Users/Programer/Desktop/桌面/20140701231656_tfNAZ.jpeg"];
[formData appendPartWithFileURL:url name:@"file" error:nil];
} success:^(NSURLSessionDataTask *task, id responseObject) {
NSDictionary *res = responseObject;
NSLog(@"%@",res);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
if (error) {
NSLog(@"失败");
}
}];
  • 下载
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
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"]];
NSProgress *progress = nil;
NSURLSessionDownloadTask * task = [manager downloadTaskWithRequest:request progress:&progress destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSString *cacheP = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)lastObject];
NSString *path = [cacheP stringByAppendingPathComponent:response.suggestedFilename];
NSLog(@"%@",path);
NSURL *url = [NSURL fileURLWithPath:path];
return url;
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
}];
//为进度添加观察者
[progress addObserver:self forKeyPath:@"completedUnitCount" options:NSKeyValueObservingOptionNew context:nil];
[task resume];
  • get请求
1
2
3
4
5
6
7
8
9
10
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSDictionary *parameters = @{
@"username":@"hjsit",
@"pwd":@"hjsit"
};
[manager GET:@"http://120.25.226.186:32812/login" parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"%@",[responseObject class]);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
  • 监听网络状态
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
28
// 1.创建网络监听管理者对象
AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager];
// 2.设置监听
/*
AFNetworkReachabilityStatusUnknown = -1, 未识别
AFNetworkReachabilityStatusNotReachable = 0, 未连接
AFNetworkReachabilityStatusReachableViaWWAN = 1, 3G
AFNetworkReachabilityStatusReachableViaWiFi = 2, wifi
*/
[manager setReachabilityStatusChangeBlock:^ void(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"未连接");
break;
case AFNetworkReachabilityStatusReachableViaWWAN:
NSLog(@"手机自带网络");
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"wifi网络");
break;
default:
NSLog(@"未识别");
break;
}
}];
// 3.开启监听
[manager startMonitoring];
  • 序列化,数据返回类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1.创建网络管理者
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
// 2.利用网络管理者发送get请求
// NSDictionary *parameters = @{
// @"username":@"hjsit",
// @"pwd":@"hjsit",
// @"type":@"XML"
// };
[AFJSONResponseSerializer serializer].acceptableContentTypes = [NSSet setWithObject:@"text/xml"];
// 告诉AFN, 将返回的数据当做JSON来处理
// manager.responseSerializer = [AFJSONRequestSerializer serializer];
// 告诉AFN, 将返回的数据当做XML来处理
// manager.responseSerializer = [AFXMLParserResponseSerializer serializer];
// 告诉AFN, 将返回的数据当做二进制来数据 (服务器返回什么就是什么)
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager GET:@"http://120.25.226.186:32812/resources/images/minion_02.png" parameters:nil success:^ void(NSURLSessionDataTask * task, id responseObject) {
NSLog(@"请求成功 %@", [responseObject class]);
} failure:^ void(NSURLSessionDataTask * operation, NSError * error) {
NSLog(@"请求失败 %@", error);
}];
  • AFN推荐继承,推荐使用initWithBaseURL,单例
1
2
3
4
5
6
7
8
9
10
11
12
+ (instancetype)shareNetworkTools2
{
static XMGNetworkTools2 *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/"];
instance = [[self alloc] initWithBaseURL:url sessionConfiguration:cfg];
});
return instance;
}
  • AFN已经封装了HTTPS证书授权代码,直接可以请求,下面是证书授权代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
*
只要请求的地址是HTTPS的, 就会调用这个代理方法
我们需要在该方法中告诉系统, 是否信任服务器返回的证书
Challenge: 挑战 质问 (包含了受保护的区域)
protectionSpace : 受保护区域
NSURLAuthenticationMethodServerTrust : 证书的类型是 服务器信任
*/
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
// NSLog(@"didReceiveChallenge %@", challenge.protectionSpace);
NSLog(@"%@", challenge.protectionSpace.authenticationMethod);
// 1.判断服务器返回的证书类型, 是否是服务器信任
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSLog(@"是服务器信任的证书");
/*
NSURLSessionAuthChallengeUseCredential = 0, 使用证书
NSURLSessionAuthChallengePerformDefaultHandling = 1, 忽略证书(默认的处理方式)
NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2, 忽略书证, 并取消这次请求
NSURLSessionAuthChallengeRejectProtectionSpace = 3, 忽略当前这一次, 下一次再询问
*/
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential , credential);
}
}

网络环境监听(Reachability)

發表於 2016-07-10   |  
  • 没事看看这个网络环境监听的东西,待会还要去学英语呢
  • 我看的这个是属于ASI框架的
  • 大概看了一下,最核心的判断网络的方法是这个
  • SCNetworkReachabilityGetFlags
  • 这个c函数获取到各种flag,它是系统SystemConfiguration框架里SCNetworkReachability.h里的一个方法
  • 看它的介绍

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /*!
    @function SCNetworkReachabilityGetFlags
    @discussion Determines if the given target is reachable using the
    current network configuration.
    @param target The network reference associated with the address or name
    to be checked for reachability.
    @param flags A pointer to memory that will be filled with the
    SCNetworkReachabilityFlags detailing the reachability
    of the specified target.
    @result Returns TRUE if the network connection flags are valid;
    FALSE if the status could not be determined.
    */
  • 大概意思就是根据传入的target(SCNetworkReachabilityRef)来得到当前网络状态

  • 让我们看看得到的flag分别怎么处理

    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    - (NetworkStatus)networkStatusNewForFlags:(SCNetworkReachabilityFlags)flags
    {
    if ((flags & kSCNetworkReachabilityFlagsReachable) == 0)
    {
    return NotReachable;
    }
    NetworkStatus retVal = NotReachable;
    if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0)
    {
    // if target host is reachable and no connection is required
    // then we'll assume (for now) that your on Wi-Fi
    retVal = ReachableViaWiFi;
    }
    if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) ||
    (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))
    {
    // ... and the connection is on-demand (or on-traffic) if the
    // calling application is using the CFSocketStream or higher APIs
    if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0)
    {
    // ... and no [user] intervention is needed
    retVal = ReachableViaWiFi;
    }
    }
    if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
    {
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
    {
    CTTelephonyNetworkInfo *info = [[CTTelephonyNetworkInfo alloc] init];
    NSString *currentRadioAccessTechnology = info.currentRadioAccessTechnology;
    if (currentRadioAccessTechnology) {
    if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyLTE]) {
    return kReachableVia4G;
    } else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyEdge] || [currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyGPRS]) {
    return kReachableVia2G;
    } else {
    return kReachableVia3G;
    }
    }
    }
    if ((flags & kSCNetworkReachabilityFlagsTransientConnection) == kSCNetworkReachabilityFlagsTransientConnection) {
    if((flags & kSCNetworkReachabilityFlagsConnectionRequired) == kSCNetworkReachabilityFlagsConnectionRequired) {
    return kReachableVia2G;
    }
    return kReachableVia3G;
    }
    return kReachableViaWWAN;
    }
    return retVal;
    }
  • 上面这里将一个方法,一个生产flag的东西当做参数传了进去

  • 很明显,利用runloop进行网络的环境监听
  • 当有变动时,则会调用ReachabilityCallback函数
  • 这个函数也是有讲究的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /*!
    @typedef SCNetworkReachabilityCallBack
    @discussion Type of the callback function used when the
    reachability of a network address or name changes.
    @param target The SCNetworkReachability reference being monitored
    for changes.
    @param flags The new SCNetworkReachabilityFlags representing the
    reachability status of the network address/name.
    @param info A C pointer to a user-specified block of data.
    */
    typedef void (*SCNetworkReachabilityCallBack) (
    SCNetworkReachabilityRef target,
    SCNetworkReachabilityFlags flags,
    void * __nullable info
    );
  • 看它说明

    the callback function used when the reachability of a network address or name changes

  • 为网络改变而生的回调,哈哈

  • 再来看看这个设置callback的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    SCNetworkReachabilitySetCallback
    /////////////
    /*!
    @function SCNetworkReachabilitySetCallback
    @discussion Assigns a client to a target, which receives callbacks
    when the reachability of the target changes.
    @param target The network reference associated with the address or
    name to be checked for reachability.
    @param callout The function to be called when the reachability of the
    target changes. If NULL, the current client for the target
    is removed.
    @param context The SCNetworkReachabilityContext associated with
    the callout. The value may be NULL.
    @result Returns TRUE if the notification client was successfully set.
    */
    Boolean
    SCNetworkReachabilitySetCallback (
    SCNetworkReachabilityRef target,
    SCNetworkReachabilityCallBack __nullable callout,
    SCNetworkReachabilityContext * __nullable context
    ) __OSX_AVAILABLE_STARTING(__MAC_10_3,__IPHONE_2_0);
  • 所以,你也可以自己写个网络环境监听,知道系统方法干啥的就行了

  • 最后别忘了释放监听

    1
    2
    3
    4
    5
    6
    7
    - (void)stopNotifier
    {
    if (_reachabilityRef != NULL)
    {
    SCNetworkReachabilityUnscheduleFromRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
    }
    }
  • 细看就可以大概了解这些flag是怎么运作的

  • 接下来看看是如何做到实时的网络环境监听的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    - (BOOL)startNotifier
    {
    BOOL returnValue = NO;
    SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
    if (SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context))
    {
    if (SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode))
    {
    returnValue = YES;
    }
    }
    return returnValue;
    }

  • 这里附上一个监测ipv6的东西
  • 这里先贴上两个工具方法

    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
    28
    29
    30
    31
    32
    /**
    * @brief 判断是否为IPv4地址
    *
    * @return
    */
    - (BOOL)isIPv4Address:(NSString *)addr {
    if (addr == nil) {
    return NO;
    }
    const char *utf8 = [addr UTF8String];
    // Check valid IPv4.
    struct in_addr dst;
    int success = inet_pton(AF_INET, utf8, &(dst.s_addr));
    return success == 1;
    }
    /**
    * @brief 判断是否为IPv6地址
    *
    * @return
    */
    - (BOOL)isIPv6Address:(NSString *)addr {
    if (addr == nil) {
    return NO;
    }
    const char *utf8 = [addr UTF8String];
    // Check valid IPv6.
    struct in6_addr dst6;
    int success = inet_pton(AF_INET6, utf8, &dst6);
    return (success == 1);
    }
  • 然后再进行下面的逻辑运算

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    dnsIPStack = IPLocalIPStack_IPv4 | IPLocalIPStack_IPv6;
    dnsIPStack &= [self getDNSServersIpStack];
    if (dnsIPStack & IPLocalIPStack_IPv4) {
    // support IPv4
    isIPv6Only = NO;
    } else if (dnsIPStack & IPLocalIPStack_IPv6) {
    // IPv6-Only
    isIPv6Only = YES;
    [[OSSIPv6PrefixResolver getInstance] updateIPv6Prefix];
    } else {
    KGIPv6Log(@"[%s]: Error.", __FUNCTION__);
    isIPv6Only = NO;
    }
  • 其中如果是ipv6我们就要做些事情了,不过先贴上完整代码

    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    - (BOOL)isIPv6OnlyNetwork {
    @synchronized(self) {
    if (isIPv6OnlyResolved) {
    return isIPv6Only;
    }
    struct in6_addr addr6_gateway = {0};
    if (0 != getdefaultgateway6(&addr6_gateway) || IN6_IS_ADDR_UNSPECIFIED(&addr6_gateway)) {
    isIPv6Only = NO;
    return isIPv6Only;
    }
    struct in_addr addr_gateway = {0};
    if (0 != getdefaultgateway4(&addr_gateway)) {
    isIPv6Only = YES;
    [[OSSIPv6PrefixResolver getInstance] updateIPv6Prefix];
    return isIPv6Only;
    }
    if (INADDR_NONE == addr_gateway.s_addr
    || INADDR_ANY == addr_gateway.s_addr ) {
    isIPv6Only = YES;
    [[OSSIPv6PrefixResolver getInstance] updateIPv6Prefix];
    return isIPv6Only;
    }
    int haveIPv4 = _have_ipv4();
    int haveIPv6 = _have_ipv6();
    int local_stack = 0;
    if (haveIPv4) { local_stack |= IPLocalIPStack_IPv4; }
    if (haveIPv6) { local_stack |= IPLocalIPStack_IPv6; }
    if (IPLocalIPStack_IPv4 == local_stack) {
    isIPv6Only = NO;
    return isIPv6Only;
    } else if (IPLocalIPStack_IPv6 == local_stack) {
    isIPv6Only = YES;
    [[OSSIPv6PrefixResolver getInstance] updateIPv6Prefix];
    return isIPv6Only;
    }
    KGIPv6Log(@"Start resolved network to see if in IPv6-Only env.");
    int dnsIPStack = 0;
    dnsIPStack = IPLocalIPStack_IPv4 | IPLocalIPStack_IPv6;
    dnsIPStack &= [self getDNSServersIpStack];
    if (dnsIPStack & IPLocalIPStack_IPv4) {
    // support IPv4
    isIPv6Only = NO;
    } else if (dnsIPStack & IPLocalIPStack_IPv6) {
    // IPv6-Only
    isIPv6Only = YES;
    [[OSSIPv6PrefixResolver getInstance] updateIPv6Prefix];
    } else {
    KGIPv6Log(@"[%s]: Error.", __FUNCTION__);
    isIPv6Only = NO;
    }
    isIPv6OnlyResolved = YES;
    if (isIPv6Only) {
    KGIPv6Log(@"[%s]: IPv6-Only network now.", __FUNCTION__);
    } else {
    KGIPv6Log(@"[%s]: Not IPv6-Only network now.", __FUNCTION__);
    }
    return isIPv6Only;
    }
    }
  • 下面我们看一下是如何判断当前网络是否为ipv6only的

  • 答案是查询本机DNS Servers协议

    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
    - (int)getDNSServersIpStack {
    int dns_stack = 0;
    res_state res = malloc(sizeof(struct __res_state));
    int result = res_ninit(res);
    if (result == 0) {
    union res_9_sockaddr_union *addr_union = malloc(res->nscount * sizeof(union res_9_sockaddr_union));
    res_getservers(res, addr_union, res->nscount);
    for (int i = 0; i < res->nscount; i++) {
    if (addr_union[i].sin.sin_family == AF_INET) {
    char ip[INET_ADDRSTRLEN];
    if (inet_ntop(AF_INET, &(addr_union[i].sin.sin_addr), ip, INET_ADDRSTRLEN)) {
    dns_stack |= IPLocalIPStack_IPv4;
    }
    } else if (addr_union[i].sin6.sin6_family == AF_INET6) {
    char ip[INET6_ADDRSTRLEN];
    if (inet_ntop(AF_INET6, &(addr_union[i].sin6.sin6_addr), ip, INET6_ADDRSTRLEN)) {
    dns_stack |= IPLocalIPStack_IPv6;
    }
    } else {
    KGIPv6Log(@"%s: Undefined family.", __FUNCTION__);
    }
    }
    }
    res_nclose(res);
    return dns_stack;
    }
  • 我们看看这个OSSIPv6PrefixResolver是如何解决ipv6问题的

  • 直接就是下面的转换,不过还需要一个函数解决prefix的问题,代码下面附上

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if (([[KGIPv6Adapater sharedInstance] isIPv6OnlyNetwork]
    && [[UIDevice currentDevice].systemVersion floatValue] < 9.0)) {
    NSURL *tempURL = [NSURL URLWithString:domainURLString];
    if ([[KGIPv6Adapater sharedInstance] isIPv4Address:tempURL.host]) {
    NSString *tempIPv6String = [[KGIPv6Adapater sharedInstance] handleIpv4Address:tempURL.host];
    domainURLString = [domainURLString stringByReplacingOccurrencesOfString:tempURL.host
    withString:tempIPv6String];
    }
    }
  • 查看当前是否只允许ipv6网络,是则将ipv4转换成ipv6

  • 下面是转换算法

    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    - (NSString *)convertIPv4toIPv6:(NSString *)ipv4 {
    if ([ipv4 length] <= 0) {
    return nil;
    }
    __uint8_t ipv6[16] = {0x00};
    __uint8_t length = [self resolveIPv6Prefix:ipv6];
    if (length <= 0) {
    return nil;
    }
    in_addr_t addr_v4 = inet_addr([ipv4 UTF8String]);
    // 按length的不同情况进行处理
    if (length==4 || length==12) { //32 bits or 96 bits
    ipv6[length+0] |= (__uint8_t)(addr_v4>>0 & 0xff);
    ipv6[length+1] |= (__uint8_t)(addr_v4>>8 & 0xff);
    ipv6[length+2] |= (__uint8_t)(addr_v4>>16 & 0xff);
    ipv6[length+3] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    else if (length == 5) { //40 bits :a.b.c.0.d
    ipv6[length+0] |= (__uint8_t)(addr_v4>>0 & 0xff);
    ipv6[length+1] |= (__uint8_t)(addr_v4>>8 & 0xff);
    ipv6[length+2] |= (__uint8_t)(addr_v4>>16 & 0xff);
    ipv6[length+4] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    else if (length == 6) { //48 bits :a.b.0.c.d
    ipv6[length+0] |= (__uint8_t)(addr_v4>>0 & 0xff);
    ipv6[length+1] |= (__uint8_t)(addr_v4>>8 & 0xff);
    ipv6[length+3] |= (__uint8_t)(addr_v4>>16 & 0xff);
    ipv6[length+4] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    else if (length == 7) { //56 bits :a.0.b.c.d
    ipv6[length+0] |= (__uint8_t)(addr_v4>>0 & 0xff);
    ipv6[length+2] |= (__uint8_t)(addr_v4>>8 & 0xff);
    ipv6[length+3] |= (__uint8_t)(addr_v4>>16 & 0xff);
    ipv6[length+4] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    else if (length == 8) { //64 bits :0.a.b.c.d
    ipv6[length+1] |= (__uint8_t)(addr_v4>>0 & 0xff);
    ipv6[length+2] |= (__uint8_t)(addr_v4>>8 & 0xff);
    ipv6[length+3] |= (__uint8_t)(addr_v4>>16 & 0xff);
    ipv6[length+4] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    // 构造IPv6的结构
    char addr_text[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
    if(inet_ntop(AF_INET6, ipv6, addr_text, INET6_ADDRSTRLEN)) {
    NSString *ret = [NSString stringWithUTF8String:addr_text];
    return ret;
    }
    return nil;
    }
  • 解决ipv6prefix问题

    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    - (__uint8_t)resolveIPv6Prefix:(__uint8_t *)prefix {
    if ( !prefix ) {
    return 0;
    }
    __uint8_t len = prefixLen;
    memcpy(prefix, ipv6Prefix, prefixLen);
    @synchronized(self) {
    if (ipv6PrefixResolveStatus==IPv6PrefixUnResolved ) {
    ipv6PrefixResolveStatus = IPv6PrefixResolving;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    struct addrinfo hints, *addr;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_INET6;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_DEFAULT;
    if (0 != getaddrinfo("ipv4only.arpa", "http", &hints, &addr)) {
    ipv6PrefixResolveStatus = IPv6PrefixUnResolved;
    return;
    }
    if (addr && AF_INET6 == addr->ai_addr->sa_family) {
    struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)(addr->ai_addr);
    if ( !addr6 ) {
    ipv6PrefixResolveStatus = IPv6PrefixUnResolved;
    return;
    }
    __uint8_t* u8 = addr6->sin6_addr.__u6_addr.__u6_addr8;
    for (__uint8_t i=0; i < V6_PREFIX_TABLE_SIZE; i++) {
    if ([self isIPv6Prefix:(__uint8_t *)V6_PREFIX_CONTENT_TABLE[i]
    withPrefixLen:V6_PREFIX_SIZE_TABLE[i]
    withIP:u8
    withIPLen:16]) {
    ipv6Prefix = (__uint8_t *)V6_PREFIX_CONTENT_TABLE[i];
    prefixLen = V6_PREFIX_SIZE_TABLE[i];
    ipv6PrefixResolveStatus = IPv6PrefixResolved;
    break;
    }
    }
    ipv6PrefixResolveStatus = IPv6PrefixUnResolved;
    }
    });
    }
    }
    return len;
    }
  • ok,本期讲座到此结束

二维码扫描、更新与创建

發表於 2015-10-17   |  
  • 二维码需要用到库AVFoundation
  • 导入库完毕后,按照下面几个步骤来做

    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
    28
    29
    /// 1.创建输入
    private lazy var inputDevice: AVCaptureDeviceInput? =
    {
    let device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
    do{
    let input = try AVCaptureDeviceInput(device: device)
    return input
    }catch
    {
    return nil
    }
    }()
    /// 2.创建输出
    private lazy var output: AVCaptureMetadataOutput = {
    let output = AVCaptureMetadataOutput()
    return output
    }()
    /// 3.创建会话
    private lazy var session: AVCaptureSession = {
    let s = AVCaptureSession()
    s.sessionPreset = "AVCaptureSessionPreset1920x1080"
    return s
    }()
    /// 4.创建预览图层
    private lazy var previewLayer: AVCaptureVideoPreviewLayer = {
    let layer = AVCaptureVideoPreviewLayer(session: self.session)
    layer.frame = UIScreen.mainScreen().bounds
    return layer
    }()
  • 代理捕捉扫描信息

    1
    2
    3
    4
    5
    6
    7
    8
    extension QRCodeViewController: AVCaptureMetadataOutputObjectsDelegate
    {
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!)
    {
    NJLog(metadataObjects.last?.stringValue)
    }
    }
  • 启动扫描

    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
    private func startScanQRCode()
    {
    // 1.判断是否可以添加输入设备
    if !session.canAddInput(inputDevice)
    {
    return
    }
    // 2.判断是否可以添加输出对象
    if !session.canAddOutput(output)
    {
    return
    }
    // 3.添加输入和输出到会话中
    session.addInput(inputDevice)
    session.addOutput(output)
    // 4.设置输出解析数据类型
    // 必须在输出对象添加到会话之后才可以设置, 否则会报错
    output.metadataObjectTypes = output.availableMetadataObjectTypes
    // 5.设置输出代理, 监听解析到得结果
    output.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
    // 6.添加预览图层
    view.layer.insertSublayer(previewLayer, atIndex: 0)
    // 将绘制边框图层添加到预览图层上 previewLayer.addSublayer(drawLayer)
    // 6.利用会话开始扫描
    session.startRunning()
    }
  • 设置感兴趣范围

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /// 2.创建输出
    private lazy var output: AVCaptureMetadataOutput = {
    let output = AVCaptureMetadataOutput()
    // 设置兴趣点(感兴趣范围), 默认是全屏
    // 注意: 是以横屏的左上角为参照
    // output.rectOfInterest = CGRect(x: 0, y: 0, width: 0.5, height: 0.5)
    let frame = self.containerView.frame
    let size = self.view.frame.size
    // 由于是按照横屏来计算的, 所以需要将x变为y, y变为x, 将宽变为高, 高变为宽
    output.rectOfInterest = CGRect(x: frame.origin.y / size.height, y: frame.origin.x / size.width, width: frame.size.height / size.height, height: frame.size.width / size.width)
    return output
    }()
  • 描绘边框

    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!)
    {
    // 0. 移除以前的描边
    clearCorners()
    for objc in metadataObjects
    {
    // 1.将扫描到二维码的坐标转换为我们能够识别的坐标
    let codeObjc = previewLayer.transformedMetadataObjectForMetadataObject(objc as! AVMetadataObject)
    // 2.根据转换好的坐标绘制二维码描边
    drawCorners(codeObjc as! AVMetadataMachineReadableCodeObject)
    }
    }
    ///绘制二维码描边
    private func drawCorners(codeObjc: AVMetadataMachineReadableCodeObject)
    {
    if codeObjc.corners == nil || codeObjc.corners.count == 0
    {
    return
    }
    // 1.绘制路径
    let subLayer = CAShapeLayer()
    subLayer.lineWidth = 4
    subLayer.strokeColor = UIColor.redColor().CGColor
    subLayer.fillColor = UIColor.clearColor().CGColor
    // 2.从传入对象中获取4个点, 创建Path
    let path = UIBezierPath()
    var point = CGPointZero
    var index = 0
    let dictArr = codeObjc.corners
    // 2.1取出第0个点
    // 将字典中的xy转换为CGPoint
    CGPointMakeWithDictionaryRepresentation(dictArr[index++] as! CFDictionaryRef, &point)
    // 2.2移动到第0个点
    path.moveToPoint(point)
    // 2.3添加其它的点
    while index < dictArr.count
    {
    CGPointMakeWithDictionaryRepresentation(dictArr[index++] as! CFDictionaryRef, &point)
    path.addLineToPoint(point)
    }
    // 2.4关闭路径
    path.closePath()
    subLayer.path = path.CGPath
    // 3.将layer添加到drawLayer上
    drawLayer.addSublayer(subLayer)
    }
    /// 移除以前的描边
    private func clearCorners()
    {
    if drawLayer.sublayers == nil || drawLayer.sublayers?.count == 0
    {
    return
    }
    for subLayer in drawLayer.sublayers!
    {
    subLayer.removeFromSuperlayer()
    }
    }

二维码创建

  • 根据信息创建二维码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 1.生成二维码
    // 1.创建滤镜
    let filter = CIFilter(name: "CIQRCodeGenerator")!
    // 2.还原滤镜默认设置
    filter.setDefaults()
    // 3.设置数据
    filter.setValue("极客江南".dataUsingEncoding(NSUTF8StringEncoding), forKey: "inputMessage")
    // 4.从滤镜从取出二维码
    let ciImage = filter.outputImage!
    // customImageView.image = UIImage(CIImage: ciImage)
    // customImageView.image = createNonInterpolatedUIImageFormCIImage(ciImage, size: 500)
    let QRCodeImage = createNonInterpolatedUIImageFormCIImage(ciImage, size: 500)
    // 5.设置图片到界面上
    image.image = QRCodeImage
  • 生成高清放大的图像

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
28
29
30
/**
根据CIImage生成指定大小的高清UIImage
:param: image 指定CIImage
:param: size 指定大小
:returns: 生成好的图片
*/
private func createNonInterpolatedUIImageFormCIImage(image: CIImage, size: CGFloat) -> UIImage {
let extent: CGRect = CGRectIntegral(image.extent)
let scale: CGFloat = min(size/CGRectGetWidth(extent), size/CGRectGetHeight(extent))
// 1.创建bitmap;
let width = CGRectGetWidth(extent) * scale
let height = CGRectGetHeight(extent) * scale
let cs: CGColorSpaceRef = CGColorSpaceCreateDeviceGray()!
let bitmapRef = CGBitmapContextCreate(nil, Int(width), Int(height), 8, 0, cs, 0)!
let context = CIContext(options: nil)
let bitmapImage: CGImageRef = context.createCGImage(image, fromRect: extent)
CGContextSetInterpolationQuality(bitmapRef, CGInterpolationQuality.None)
CGContextScaleCTM(bitmapRef, scale, scale);
CGContextDrawImage(bitmapRef, extent, bitmapImage);
// 2.保存bitmap到图片
let scaledImage: CGImageRef = CGBitmapContextCreateImage(bitmapRef)!
return UIImage(CGImage: scaledImage)
}
12
Tyrion

Tyrion

When you fall down,keep on getting back up

11 文章
© 2015 - 2016 Tyrion
由 Hexo 強力驅動
主題 - NexT.Pisces