GithubHelp home page GithubHelp logo

field's Introduction

field's People

Contributors

wxiubin avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

s2339956

field's Issues

用 Python 写脚本发送工作周报

事情的起因是这样的,每周都要发送工作周报,标题是需要的固定的格式(方便规则收信),比如 【工作周报】 xxx 11.20-11.24,发送和抄送也是给固定的人,企业邮箱还没有模板,所以每次发邮件都要复制粘贴标题、发送人和抄送人,只有内容是手写,便萌发了用脚本发送邮件的想法。

用脚本只需要第一次使用的时候配置收件人、抄送人和标题的格式,之后就只需要:

  1. 双击脚本文件
  2. 输入时间,如 11.20-11.24
  3. 输入周报内容

配置

# 配置项
from_addr 	= '[email protected]' # 发送方邮箱
password 	= 'passMhr22i6Uword' 		# 发送方邮箱的密码(企业邮箱-设置-客户端专用密码)
to_addr 	= '[email protected]' 	# 收件人邮箱
cc_addr 	= '[email protected]'	# 抄送人邮箱

from_name	= '王修斌'					# 发件人姓名

# QQ 企业邮箱不用修改
smtp_addr 	= 'smtp.exmail.qq.com'
smtp_port 	= 465

构建邮件

def _build_msg(content,date):
	msg = MIMEText(content, 'plain', 'utf-8')
	msg['From'] 	= _format_addr('%s <%s>' % (from_name, from_addr) )
	msg['To'] 		= _format_addr( 'zhuyue <%s>' % to_addr )
	msg['CC'] 		= _format_addr( 'bplus-ios <%s>' % cc_addr )
	msg['Subject'] 	= Header('【B+iOS周报】 %s %s' % (from_name, date), 'utf-8').encode()
	return msg

发送邮件

def send_email(msg):
	print
	try:
		print '初始化 SMTP...'
		server = smtplib.SMTP_SSL(smtp_addr, smtp_port)
		print '初始化 SMTP 成功'
		# server.set_debuglevel(1)
		print '开始登陆邮箱服务...'
		server.login(from_addr, password)
		print '开始发送邮件...'
		server.sendmail(from_addr, [to_addr], msg.as_string())
		print '邮件发送成功!'
	except server.SMTPException,e:
	    print '邮件发送失败 %s' % e
	finally:
	    server.quit()

GUI

def sendEmailCommand():
	
	input_date = app.dateInput.get()
	msg_content = app.content.get("0.0", "end")
	send_email(_build_msg(msg_content, input_date))


class Application(Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.pack()
        self.createWidgets()

    def createWidgets(self):
    	self.header = Frame()
        self.dateLabel = Label(self.header, text='周报标题日期(如 11.20-11.24):')
        self.dateLabel.grid(row=0,column=0)
        self.dateInput = Entry(self.header)
        self.dateInput.grid(row=0,column=1)
        self.sendButton = Button(self.header, text='发送', command=sendEmailCommand)
        self.sendButton.grid(row=0,column=2)
        self.content = Text(self,padx=10)
        self.content.insert(END, '本周任务:\n\n下周任务:\n\n风险评估:\n')
        self.content.pack()
    	self.header.pack()


app = Application()
# 设置窗口标题:
app.master.title('发送周报邮件')
# 主消息循环:
app.mainloop()

使用

cd 脚本目录
python GUI.py

或者修改名称 GUI.command 方便双击打开。
执行时提示权限不足可在终端执行 chmod +x GUI.command

效果预览

终端输出:

初始化 SMTP...
初始化 SMTP 成功
开始登陆邮箱服务...
开始发送邮件...
邮件发送成功!

最终只需要在脚本的图形界面中输入时间段和内容,然后点击发送,美滋滋~ 以后再也不用 command+Ccommand+V

我的博客:https://iosgg.cn/

前端笔记之CSS

CSS 全称是Cascading Style Sheets,层叠样式表,被用来控制HTML标签的样式,美化网页
。CSS有两个重点: 属性选择器

CCS的编写格式

CCS的编写格式是以键值对的形式,如:

color : red;
background-color : blue;
font-size : 20px;
  • 属性一般由属性名和属性值组成,左边是属性名,右边是属性的值

CSS有3种书写形式:

  1. 行内样式:(内联样式)直接在标签的 style 属性中书写
<body style="color : red;">
  1. 页内样式:在本网页的 style标签中书写
<style>
<body {
color : red;
}
</style>
  1. 外部样式:在单独的CSS文件中书写,然后在网页中用link引入
<link rel="stylesheet" href="index.css">

CSS选择器:

1. 标签选择器

根据标签名查找标签:

<div>div1<div>

找到标签,设置样式:

div {
color : red;
}
2. 类选择器
<p class="high">hello world</>
.high{
color : red;
}
3. id选择器
<p id="first">Hello world</p>
#first{
color : red;
}
4. 并列选择器
<div class="high">div high</div>
<div class="low">div low</div>
<p class="high">p high</p>
<p class="low">p low</p>
div, .high{
color : red;
}

并列选择,只要一个符合条件就可以
结果:

div high
div low
p high
p low
5. 复合选择器
<div class="high">div high</div>
<div class="low">div low</div>
<p class="high">p high</p>
<p class="low">p low</p>
div .high{
color : red;
}

复合选择,必须全部符合条件就可以
结果:

div high
div low
p high
p low
6. 后代选择器
<div>
<p>div -> p</p>
<span>
<p>div -> span -> p</p>
</span>
</div>
<p>div p</p>
div p {
color : red;
}

只要是在div 里面的 p 标签,都符合要求,结果:

div -> p
div -> span -> p
div p
7. 直接后代选择器
<div>
<p>div -> p</p>
<span>
<p>div -> span -> p</p>
</span>
</div>
div p {
color : red;
}

必须是直接在div 里面的 p 标签,才符合要求,即儿子可以,孙子就不行
结果:

div -> p
div -> span -> p
8. 相邻兄弟选择器
<div>
<p>div -> p</p>
<span>
<p>div -> span -> p</p>
</span>
</div>
<p>div p</p>
<p>p p</p>
div + p {
color : red;
}

只要是在div 里面的 p 标签,都符合要求,结果:

div -> p
div -> span -> p
div p
p p
9. 属性选择器
<div name="Jack">name = Jack</div>
<div name="Tom">name = Tom</div>
<div name="Bob" age="20">name = Bob, age = 20</div>
<div>no name</div>
div[name] {
color : red;
}
div[name][age] {
color : green;
}
div[name="Tom"] {
color : yellow;
}
name = Jack
name = Tom
name = Bob, age = 20
no name
10. 伪类
属性 描述
:active 向被激活的的元素添加样式
... ...
11. 伪元素

...

选择器优先级

选择器针对性越强、范围越小,优先级就越高
选择器的权值:

选择器 权值
通配符 0
标签 1
10
属性 10
伪类 10
id 100
important 1000
  • 原则:选择器的权值加在一起,大的优先,权值一样,后定义的优先

Swift 中的类和结构体

Swift 中的结构体的能力被大大加强,不仅可以拥有属性,还以有方法、构造函数、甚至是扩展和遵守协议。这样的结构体和类有很多相同点:

  • 属性:存储数据
  • 方法:提供一些功能
  • 下标:可以使用下标语法
  • 构造器:生成初始化值
  • 扩展:增加功能
  • 协议:提供某种通用功能

当然,类和结构体也有很多不同的地方,类还有许多独有的附加功能:

  • 继承:一个类可以继承另一个类的特征
  • 类型转换:运行时检查和解释一个类实例的类型
  • 析构器:一个类实例释放任何其所被分配的资源
  • 引用计数:对一个类的多次引用

结构体会提供一个默认的构造函数,这个构造函数是结构体所有的属性分别作为参数来构建:

struct MyPoint {
    var x = 0
    var y = 0
}
let point:MyPoint = MyPoint(x: 1, y: 2)

结构体和枚举都是值类型,值类型在赋值(给变量或者常量)和传递(作为参数给一个函数)的时候都会被拷贝,值类型实例的值属性也会被拷贝。
Swift 中的整型、浮点型、布尔型、字符串、字典、数组都是值类型,底层都是由结构体来实现的。

类是引用类型,引用类型在赋值和传递的时候,内容并不会被拷贝。因此赋值的实例和被赋值的实例其实是一份内容,内容在内存中也是一份。

值类型和引用类型的区别在于,值类型在赋值和传递的时候是深拷贝,而引用类型是浅拷贝。
深拷贝就是把内存地址中存放的内容也拷贝一份内存中的内容就会有两份;而浅拷贝只是拷贝了内存的地址,内存中的内容还是只有一份。

值和引用类型的区别

但需要注意的是,在 Swift 中,并不是值类型一旦被赋值和传递的时候就会被拷贝一份,只有当需要的时候,比如被赋值的实例去改变内容的时候才会真正的去拷贝。

那么,我们到底如何选择结构体或者类呢?如果你只是用来做以下功能是可以选择结构体:

  • 只是用来封装一些相关的数据
  • 这些数据被赋值或者传递的时候会被拷贝一份
  • 不需要被继承

比如 CGPointCGRectCGSize等都是结构体。

runtime(二) 给对象、分类添加实例变量

在开发中有时候想给对象实例添加个变量来存储数据,但又无法直接声明,比如说既有类的分类。这个时候我们就可以通过 关联对象 在运行时给对象关联一个 对象 来存储数据。(注意:并不是真实的添加了一个实例变量)

关联对象 可以给某个对象关联其他对象并用key来区分其他对象。需要注意的是,存储对象的时候要指明 存储策略,用来维护对象的内存管理语义。存储策略是 objc_AssociationPolicy 枚举定义,以下是存储策略对应的 @Property属性:

存储策略类型 对应的@Property属性
OBJC_ASSOCIATION_ASSIGN weak
OBJC_ASSOCIATION_RETAIN_NONATOMIC strong, nonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic
OBJC_ASSOCIATION_RETAIN strong
OBJC_ASSOCIATION_COPY copy

用下面的方法可以管理关联对象:

// 这个方法可以根据指定策略给对象关联对象值
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

// 这个方法可以获取对象关联对象值
id objc_getAssociatedObject(id object, const void *key)

// 这个方法可以删除指定对象的全部关联对象值
void objc_removeAssociatedObjects(id object)

对于关联对象这个OC特性,我们可以把对象想象成一个 NSDictionary,关联对象需要一个 key( 类型是 opaque pointer,无类型的指针 ) 来区分,我们可以把要添加的变量名作为 key ,把变量的值作为关联的对象来存储到 ”对象“ 这个 NSDictionary 中。
所以,关联对象的

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

方法类似于字典的 [dict setObject: forKey:] 方法。

在存储和获取关联对象时需要用一个相等的 key ,因为是给 Class 的实例对象关联对象,所以一般用静态变量来做 key

说的再多,不如上段代码!

比如说,我们给 NSString 实例加上个 NSDate 类型的 date 变量。什么?给字符串加个日期变量是要干袅?我要给字符串过个生日不行吗! 别闹,举个栗子嘛!(捂脸逃跑~~~)

首先,我们先给 NSString 新建个名为 RT 的 category。
在头文件中有个 NSDate 类型的 date 属性:

//  NSString+RT.h
//  runtime
#import <Foundation/Foundation.h>
//
@interface NSString (RT)
//
@property (nonatomic, strong) NSDate *date;
//
@end

在分类中的属性只会生成 getset 方法,并不会生成变量。
所以我们需要重写 getset 方法,关联对象以变相实现添加变量,在现实文件中:

//  NSString+RT.m
//  runtime
#import <objc/runtime.h>
#import "NSString+RT.h"
//
@implementation NSString (RT)
//
static void *runtime_date_key = "date";
- (NSDate *)date{
    return objc_getAssociatedObject(self, runtime_date_key);
}
//
- (void)setDate:(NSDate *)date{
    objc_setAssociatedObject(self, runtime_date_key, date, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

需要注意的是,关联对象用到的 key 是个无类型的指针,一般来说是静态来修饰。
另外,给对象关联的只能是对象,如果是 intfloat 等类型需要 NSNumber 进行包装。
因为 date 是强引用和非原子属性,所以关联策略用 OBJC_ASSOCIATION_RETAIN_NONATOMIC

然后执行代码:

NSString *string = @"runtimeTestString";
string.date = [NSDate date];
NSLog(@"string.date = %@",string.date);

输出结果:

2016-04-12 21:27:31.099 runtime[2837:103727] string.date = 2016-04-12 13:27:31 +0000

注意:

  • 定义关联对象时需要指定内存管理语义,用来模拟对象对变量的拥有关系
  • 尽量避免使用关联对象,因为如果出现bug不易于问题排查

Mac应用和命令推荐

这么长时间以来都是用Mac做开发主力机器,有一些常用的软件和小的技巧记录分享一下。

常用软件

  • Alfred - Mac效率神器
  • Xcode - 开发iOS和MacOS基本IDE,下载之后是个 .xip 文件,双击检验后就会丢出来个Xcode,如果失败最大的可能就是磁盘空间不足(一般需要近20G剩余空间,具体多少没测试)。 腾讯Bugly团队镜像
  • Sublime Text - 一个比较简洁大方带插件管理系统的流行编辑器
  • MWeb - 专业的 Markdown 写作、记笔记、静态博客生成软件
  • Chrome - 世界上最好的浏览器
  • IINA - 一个强大而优雅的现代的MacOS平台视频播放器
  • iTerm2 - 牛逼而又免费的终端工具,直接替代自带的Terminal
  • SourceTree - 强大的Git跨平台客户端
  • MindNode - 简洁的风格与人性化的操作,绘制思维脑图
  • Mark Man - 高效的设计稿标注、测量工具
  • Virtual Box - 免费的虚拟机
  • 搜狗输入法 - 搜狗输入法
  • ShadowsocksX - 一个快速的隧道代理,可以帮助你绕过防火墙
  • Charles - 一个代理工具,允许你查看所有的HTTP和HTTPS流量

常用命令

使用Python做个简易服务器

python -m SimpleHTTPServer
// http://0.0.0.0:8000
python -m SimpleHTTPServer 8080
// http://0.0.0.0:8080

"xx.app"已损坏,打不开。您应该将它移到废纸篓

当出现**"xx.app"已损坏,打不开。您应该将它移到废纸篓**提示的时候,多数是需要去选择 系统偏好设置 - 安全性与隐私 - 任何来源
macOs sierra版本系统可能没有该选项,需要在终端中输入 sudo spctl --master-disable 命令来打开选项。如果想关闭则再次输入命令就可以了。

查看隐藏文件

用终端输入命令:
显示隐藏文件:defaults write com.apple.finder AppleShowAllFiles -bool true
隐藏隐藏文件:defaults write com.apple.finder AppleShowAllFiles -bool false

iOS开发中多线程的那些事

线程、进程

什么是线程、进程

  有的人说进程就像是人的脑袋,线程就是脑袋上的头发~~。其实这么比方不算错,但是更简单的来说,用迅雷下载文件,迅雷这个程序就是一个进程,下载的文件就是一个线程,同时下载三个文件就是多线程。一个进程可以只包含一个线程去处理事务,也可以有多个线程。

多线程的优点和缺点

  多线程可以大大提高软件的执行效率和资源(CPU、内存)利用率,因为CPU只可以处理一个线程(多核CPU另说),而多线程可以让CPU同时处理多个任务(其实CPU同一时间还是只处理一个线程,但是如果切换的够快,就可以了认为同时处理多个任务)。但是多线程也有缺点:当线程过多,会消耗大量的CPU资源,而且,每开一条线程也是需要耗费资源的(iOS主线程占用1M内存空间,子线程占用512KB)。

iOS开发中的多线程

  iOS程序在启动后会自动开启一个线程,称为 主线程 或者 UI线程 ,用来显示、刷新UI界面,处理点击、滚动等事件,所以耗费时间的事件(比如网络、磁盘操作)尽量不要放在主线程,否则会阻塞主线程造成界面卡顿。
iOS开发中的多线程实现方案有四种:

技术方案 简介 语言 生命周期管理
pthread 一套通用的多线程API,适用于Unix\Linux\Windows等系统,跨平台\可移植,使用难度大 C 程序员管理
NSThread 使用更加面向对象,简单易用,可直接操作线程对象 Objective-C 程序员手动实例化
GCD 旨在替代NSThread等线程技术,充分利用设备的多核 C 自动管理
NSOperation 基于GCD(底层是GCD),比GCD多了一些更简单实用的功能,使用更加面向对象 Objective-C 自动管理

多线程中GCD我使用比较多,以GCD为例,多线程有两个核心概念:

  1. 任务 (做什么?)
  2. 队列 (存放任务,怎么做?)

任务就是你开辟多线程要来做什么?而每个线程都是要加到一个队列中去的,队列决定任务用什么方式来执行。

线程执行任务方式分为:

  1. 异步执行
  2. 同步执行

同步执行只能在当前线程执行,不能开辟新的线程。而且是必须、立即执行。而异步执行可以开辟新的线程。

队列分为:

  1. 并发队列
  2. 串行队列

并发队列可以让多个线程同时执行(必须是异步),串行队列则是让任务一个接一个的执行。打个比方说,串行队列就是单车道,再多的车也得一个一个的跑(--:我俩车强行并着跑不行? --:来人,拖出去砍了!),而串行是多车道,可以几辆车同时并着跑。那么到底是几车道?并发队列有个最大并发数,一般可以手动设置。

那么,线程加入到队列中,到底会怎么执行?

并发队列 串行队列(非主队列) 主队列(只有主线程,串行队列)
同步 不开启新的线程,串行 不开启新的线程,串行 不开启新的线程,串行
异步 开启新的线程,并发 开启新的线程,串行 不开启新的线程,串行

注意:

  1. 只用在并发队列异步执行才会开启新的线程并发执行;
  2. 在当前串行队列中开启一个同步线程会造成 线程阻塞 ,因为上文说过,同步线程需要立即马上执行,当在当前串行队列中创建同步线程时需要在串行队列立即执行任务,而此时线程还需要向下继续执行任务,造成阻塞。

上面提到线程会阻塞,那么什么是阻塞?除了阻塞之外线程还有其他什么状态?
一般来说,线程有五个状态:

  • 新建状态:线程刚刚被创建,还没有调用 run 方法,这个时候的线程就是新建状态;
  • 就绪状态:在新建线程被创建之后调用了 run 方法,但是CPU并不是真正的同时执行多个任务,所以要等待CPU调用,这个时候线程处于就绪状态,随时可能进入下一个状态;
  • 运行状态:在线程执行过 run方法之后,CPU已经调度该线程即线程获取了CPU时间;
  • 阻塞状态:线程在运行时可能会进入阻塞状态,比如线程睡眠(sleep);希望得到一个锁,但是该锁正被其他线程拥有。。
  • 死亡状态:当线程执行完任务或者因为异常情况提前终止了线程

iOS开发中的多线程的使用

pthread的使用

使用下面代码可以创建一个线程:

int pthread_create(pthread_t * __restrict, const pthread_attr_t * __restrict,void *(*)(void *), void * __restrict)

可以看到这个方法有四个参数,主要参数有 pthread_t * __restrict ,因为该方法是C语言,所以这个参数不是一个对象,而是一个 pthread_t 的地址,还有 *void ()(void ) 是一个无返回值的函数指针。
使用代码:

void * run(void *param)
{
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    return NULL;
}

- (void)createThread{
    pthread_t thread;
    pthread_create(&thread, NULL, run, NULL);
}

控制台输出:

currentThread--<NSThread: 0x7fff38602fb0>{number = 2, name = (null)}

number = 1 的线程是主线程,不为一的时候都是子线程。

NSThread的使用

NSThread创建线程一般有三种方式:

// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
//
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
//
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument
  1. 前两种创建之后会自动执行,第三种方式创建后需要手动执行;
  2. 第一种创建方式是创建一个子线程,类似的 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString > )array 方法可以创建并发任务在主线程中执行,- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString > )array 可以选择在哪个线程中执行。

示例代码:

- (void)createThread{
	// 创建线程
	NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"我是参数"];
	thread.name = @"我是线程名字啊";
	// 启动线程
	[thread start];
	// 或者 [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"我是参数"];
	// 或者 [self performSelectorInBackground:@selector(run:) withObject:@"我是参数"];
}
- (void)run:(NSString *)param{
    NSLog(@"-----run-----%@--%@", param, [NSThread currentThread]);
}

控制台输出:

-----run-----我是参数--<NSThread: 0x7ff8a2f0c940>{number = 2, name = 我是线程名字啊}

GCD的使用

苹果官方对GCD说:

开发者要做的只是定义执行的任务并追加到适当的 Dispatch Queue 中。

在GCD中我们要做的只是两件事:定义任务;把任务加到队列中。

dispatch_queue_create 获取/创建队列

GCD 的队列有两种:

Dispatch Queue 种类 说明
Serial Dispatch Queue 等待现在执行中处理结束(串行队列)
Concurrent Dispatch Queue 不等待现在执行中处理结束(并行队列)

GCD中的队列都是 dispatch_queue_t 类型,获取/创建方法:

// 1. 手动创建队列
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
// 1.1 创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.sanyucz.queue", DISPATCH_QUEUE_SERIAL);
// 1.2 创建并行队列
    dispatch_queue_t queue = dispatch_queue_create("com.sanyucz.queue", DISPATCH_QUEUE_CONCURRENT);
// 2. 获取系统标准提供的 Dispatch Queue
// 2.1 获取主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2.2 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

需要说明的是,手动创建队列时候的两个关键参数,*const char label 指定队列名称,最好起一个有意义的名字,当然如果你想调试的时候刺激一下,也可以设置为 NULL,而 dispatch_queue_attr_t attr 参数文档有说明:

/*!
 * @const DISPATCH_QUEUE_SERIAL
 * @discussion A dispatch queue that invokes blocks serially in FIFO order.
 */
#define DISPATCH_QUEUE_SERIAL NULL
/*!
 * @const DISPATCH_QUEUE_CONCURRENT
 * @discussion A dispatch queue that may invoke blocks concurrently and supports
 * barrier blocks submitted with the dispatch barrier API.
 */
#define DISPATCH_QUEUE_CONCURRENT \
		DISPATCH_GLOBAL_OBJECT(dispatch_queue_attr_t, \
		_dispatch_queue_attr_concurrent)
  • DISPATCH_QUEUE_SERIAL 创建串行队列按顺序FIFO(First-In-First-On)先进先出;
  • DISPATCH_QUEUE_CONCURRENT 则会创建并发队列

dispatch_async/dispatch_sync 创建任务

创建完队列之后就是定义任务了,有两种方式:

// 创建一个同步执行任务
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// 创建一个异步执行任务
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

完整的示例代码:

dispatch_queue_t queue = dispatch_queue_create("com.sanyucz.queue.asyncSerial", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
   NSLog(@"异步 + 串行 - %@",[NSThread currentThread]);
});

dispatch group 任务组

我们可能在实际开发中会遇到这样的需求:在两个任务完成后再执行某一任务。虽然这种情况可以用串行队列来解决,但是我们有更加高效的方法。

直接上代码,在代码的注释中讲解:

// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建任务组
// dispatch_group_t :A group of blocks submitted to queues for asynchronous invocation
dispatch_group_t group = dispatch_group_create();
// 在任务组中添加一个任务
dispatch_group_async(group, queue, ^{
	//
});
// 在任务组中添加另一个任务
dispatch_group_async(group, queue, ^{
	//
});
// 当任务组中的任务执行完毕之后再执行一下任务
dispatch_group_notify(group, queue, ^{
   //
});

dispatch_barrier_async

从字面意思就可以看出来这个变量的用处,即阻碍任务执行,它并不是阻碍某一个任务的执行,而是在代码中,在它之前定义的任务会比它先执行,在它之后定义的任务则会在它执行完之后在开始执行。就像一个栏栅。

使用代码:

dispatch_queue_t queue = dispatch_queue_create("com.gcd.barrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
   NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
   NSLog(@"----2-----%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
   NSLog(@"----barrier-----%@", [NSThread currentThread]);
}); 
dispatch_async(queue, ^{
   NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
   NSLog(@"----4-----%@", [NSThread currentThread]);
});

控制台输出:

----1-----<NSThread: 0x7fdc60c0fd90>{number = 2, name = (null)}
----2-----<NSThread: 0x7fdc60c11500>{number = 3, name = (null)}
----barrier-----<NSThread: 0x7fdc60c11500>{number = 3, name = (null)}
----3-----<NSThread: 0x7fdc60c11500>{number = 3, name = (null)}
----4-----<NSThread: 0x7fdc60c0fd90>{number = 2, name = (null)}

dispatch_apply 遍历执行任务

dispatch_apply 的用法类似于对数组元素进行 for循环 遍历,但是 dispatch_apply 的遍历是无序的。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i < 10; i++) {
   [array addObject:@(i)];
}
// array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
NSLog(@"--------apply begin--------");
dispatch_apply(array.count, queue, ^(size_t index) {
   NSLog(@"%@---%zu", [NSThread currentThread], index);
});
NSLog(@"--------apply done --------");

控制台输出:

--------apply begin--------
<NSThread: 0x7ffa7bd05800>{number = 1, name = main}---0
<NSThread: 0x7ffa7bd05800>{number = 1, name = main}---4
<NSThread: 0x7ffa7bd05800>{number = 1, name = main}---5
<NSThread: 0x7ffa7bda77c0>{number = 2, name = (null)}---1
<NSThread: 0x7ffa7be1fd00>{number = 4, name = (null)}---3
<NSThread: 0x7ffa7bd05800>{number = 1, name = main}---6
<NSThread: 0x7ffa7be1a920>{number = 3, name = (null)}---2
<NSThread: 0x7ffa7bd05800>{number = 1, name = main}---8
<NSThread: 0x7ffa7be1fd00>{number = 4, name = (null)}---9
<NSThread: 0x7ffa7bda77c0>{number = 2, name = (null)}---7
--------apply done --------

可以看到,遍历的时候自动开启多线程,可以无序并发执行多个任务,但是有一点可以确定,就是 NSLog(@"--------apply done --------"); 这段代码一定是在所有任务执行完之后才会去执行。

GCD 的其他用法

除了上面的那些,GCD还有其他的用法

  • dispatch_after 延期执行任务
  • dispatch_suspend / dispatch_resume 暂停/恢复某一任务
  • dispatch_once 保证代码只执行一次,而且线程安全
  • Dispatch I/O 可以以更小的粒度读写文件

NSOperation的使用

NSOperation 及其子类

NSOperationNSOperationQueue 配合使用也能实现并发多线程,但是需要注意的是 NSOperation 是个抽象类,想要封装操作需要使用其子类。
系统为我们提供了两个子类:

  • NSInvocationOperation
  • NSBlockOperation

当然,我们也可以自定义其子类,只是需要重写 main() 方法。

先看下系统提供两个子类的初始化方法:

- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;

两个子类初始化方法不一样的地方就是一个用 实例对象方法选择器 来确定执行一个方法,另外一个是用block闭包保存执行一段代码块。
另外 NSBlockOperation 还有一个实例方法 - (void)addExecutionBlock:(void (^)(void))block; ,只要调用这个方法以至于封装的操作数大于一个就会开启新的线程异步操作。
最后调用NSOperationstart方法启动任务。

NSOperationQueue

NSOperation 默认是执行同步任务,但是我们可以把它加入到 NSOperationQueue 中编程异步操作。

- (void)addOperation:(NSOperation *)op;
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
- (void)addOperationWithBlock:(void (^)(void))block;

之前提到过多线程并发队列可以设置最大并发数,以及队列的取消、暂停、恢复操作:

// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
// 设置最大并发操作数
queue.maxConcurrentOperationCount = 2; // 并发队列
queue.maxConcurrentOperationCount = 1; // 串行队列

 // 恢复队列,继续执行
queue.suspended = NO;
// 暂停(挂起)队列,暂停执行
queue.suspended = YES;

// 取消队列
[queue cancelAllOperations];

线程安全

多线程使用的时候,可能会多条线程同时访问/赋值某一变量,如不加限制的话多相处同时访问会出问题。具体情况可以搜索一下相关资料,多线程的 买票问题 很是经典。
iOS线程安全解决方法一般有以下几种:

  • @synchronized 关键字
  • NSLock 对象
  • NSRecursiveLock 递归锁
  • GCD (dispatch_sync 或者 dispatch_barrier_async)

在iOS中线程安全问题一般是关键字 @synchronized 用加锁来完成。
示例代码:

@synchronized(self) {
      // 这里是安全的,同一时间只有一个线程能到这里哦~~      
}

需要注意的是 synchronized 后面括号里的 self 是个 token ,该 token 不能使用局部变量,应该是全局变量或者在线程并发期间一直存在的对象。因为线程判断该加锁的代码有没有线程在访问是通过该 token 来确定的。

Swift 语法初窥

References: The Swift Programming Language

某程序员对书法十分感兴趣,退休后决定在这方面有所建树。于是花重金购买了上等的文房四宝。一日,饭后突生雅兴,一番磨墨拟纸,并点上了上好的檀香,颇有王羲之风范,又具颜真卿气势,定神片刻,泼墨挥毫,郑重地写下一行字:hello world


print("Hello, World")

值与类型

定义变量用 var,常量用 let。常量只能赋值一次,但不用必须在声明的时候去赋值。

var myVariable = 42
myVariable = 50

let myConstant = 42

编译器会自动推断常量和变量的类型,但是如果推断不出来(比如说没有初始值等),就需要声明类型。

let helloTalk : String
helloTalk = "helloTalk"

let helloTalk = "helloTalk"

Swift的值不会隐式被转为其他类型

let widthFloat = 93.33          // 自动推断 为 Double
let width : Int = widthFloat    // 把 Double 赋值给 Int,会报错
let widthLabel = label + String(width)
// 把值转换成字符串还可以这样: \(ValueName)
let widthString = "width: \(width)."

创建、定义一个数组或者字典

let emptyArray = [String]()
let emptyDictionary = [String: Float]()
// 或者
let emptyArray = []
let emptyDictionary = [:]

数组和字典都是集合类型,对于这种类型的 letvar 修饰并非是像普通值的可否赋值。
比如说,用 let 修饰的数组是不能添加和移除数组中的元素,数组中的元素个数、位置均不可变,但是用 var 修饰的数组可以添加/删除元素。

枚举

枚举是为一组相关的值定义了一个共同类型,在 Swift 中,枚举是“一等公民”。枚举成员的原始值不仅可以是整形,还可以是字符、字符串、浮点型;此外,枚举还支持属性、方法、甚至是构造函数、扩展和遵守协议。

enum 关键词来创建枚举:

enum Direction {
    case north
    case south
    case east
    case west
}
// 或者用逗号分隔写作一行:
enum Direction {
    case north, south, east, west
}

let direction: Direction = .west

原始值

enum Direction: String {
    case north = ""
    case south = ""
    case east = ""
    case west = "西"
}

let north = Direction.north     // the sanme as : let north = Direction(rawValue: "北")
north.rawValue  // "北"

关联值

Swift 的枚举可以存储各个类型的关联值,而且每个成员的类型可以都不一样。所以对于一个网络请求可以有这样的抽象:一个网络请求的结果,可以是成功或者失败的,如果成功则返回的是我们想要的数据,不成功返回错误原因,那么可以写成这样:

enum Result<Value> {
    case success(Value)
    case failure(Error)
}

Value 是泛型语法,可以是任何你需要的类型。

控制流

for-in

let persons = ["person1","person2"]
for personString in persons {
  print(personString)
}
// 遍历字典
let dict = [
            "name" : "Joke",
            "age"  : 16
        ] as [String : Any]
        
for (key,value) in dict {
  print("\(key) : \(value)")
}

if-else

if 2>1 {
  print("2 大于 1")
}else{
  print("2 还是大于 1 啊")
}

if 后面必须是布尔表达式,如果是一个值的话不会隐式的与 0 比较。

switch

let vegetable = "red pepper"
switch vegetable {
case "celery":
  print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
  print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
  print("Is it a spicy \(x)?")
default:
  print("Everything tastes good in soup.")
}

switch 不仅支持基本数据类型。另外Swift中的switch语法可以省略break。但是不能省略 default,会报 Switch must be exhaustive, consider adding a default clause 的编译错误。

while

var i = 1
while i<100 {
  i += 1
}
// 或者
var i = 1
repeat{
  i += 1
}while i < 100

序言

记得刚从事工作的时候,经常性的会遇到困难而不知所措。幸而在网上能找到很多牛人前辈们的技术博客能将问题和开发中的知识深入浅出的剖析,给予了我很大的帮助。于是,那会我便下定决心以后有机会一定要写自己的博客。

在CSDN和博客园分别写了几篇总觉得体验不好,不是样式不满意就是广告太多。所以才有这次自己折腾几天用 Hexo 搭建了自己专属的技术博客。

写这个博客主要作为学习以用,记录自己在生活、学习和工作中的点点滴滴。

既可以温故而知新,也可查漏补缺,方便自己回头查找使用方便,或能帮助他人也是好的。

runtime(一) objc_msgSend

调用方法(函数)是语言经常使用的功能,在 Objective-C 中专业一点的叫法是 传递消息(pass a message)。Objective-C 的方法调用都是 动态绑定 ,而C语言中函数调用方式是 静态绑定 ( static binding ),也就是说,在编译时期就能决定和知道在运行时所调用的函数。

以下面代码为例:

void sayHello(){
}
void sayGoodBye(){
}
void saySomething(int type){
	if(type == 0){
		sayHello();
	}else{
		sayGoodBye();
	}
}

基本上,上面的代码在编译的时候编译器就知道 sayHellosayGoodBye 两个函数的存在,函
数地址是硬编码在指令之中的。但是如果换一种写法:

void sayHello(){
}
void sayGoodBye(){
}
void saySomething(int type){
	void (*something) ();
	if(type == 0){
		something = sayHello;
	}else{
		something = sayGoodBye;
	}
	something();
}

这就得使用 动态绑定 ,待调用的函数地址需要到运行时才能读取出来。
在 Objective-C 中,对某一个对象传递消息,会用动态绑定机制来决定到底是调用哪个方法。而Objective-C是 C 的超集,底层是由 C语言实现,但是对象接收消息后会调用哪个方法都是在运行期决定。

给对象发送消息可以这么来写:

id object = [list objectAtIndex:1];

在这行代码中, list 称为 接收者objectAtIndex 叫做 选择器, 选择器和参数合起来称为消息。当编译器看到这行代码的时候,会换成标准的C语言函数调用:

void objc_msgSend(id self, SEL cmd, ...);
id lastObject = objc_msgSend(list, @selector(objectAtIndex:), parameter);

objc_msgSend 这个函数可以接收两个及两个以上的参数,第一个参数是接收者,第二个参数是选择器,后面的参数是保持顺序的原来消息传递的参数,objc_msgSend会依据接收者和选择器来决定调用哪个方法,首先在接收者的方法列表中寻找,如果找不到就会沿着继承体系去向上一层一层的寻找,如果仍旧找不到就会执行消息转发(message forwarding)
当消息第一次传递之后,objc_msgSend 会将匹配结果进行缓存,下次会直接调用方法。消息传递除了objc_msgSend之外在特殊情况下还会有其他的方法来处理:

  • objc_msgSend_stret 如果待发送的消息返回一个结构体,就会调用这个函数来处理。
  • objc_msgSend_fpret 如果消息返回的是浮点数,就会调用这个函数进行处理。
  • objc_msgSendSuper 如果要传递消息给父类。

总结:

  • 消息由 接收者、选择器及参数构成,给某对象 发送消息( invoke a message ) 也就相当于在该对象上调用方法。
  • 发送给某对象的全部消息都要有动态消息派发系统( dynamic message dispatch system ) 来处理。

消息转发可以参看我的博客 runtime系列(三) Objective-C 的消息转发机制与动态添加方法

AFNetworking 源码阅读

AFNetworking 3.1.0

AFNetworking 主要的几个类或协议:

  • AFURLSessionManager
  • AFHTTPSessionManager
  • AFNetworkReachabilityManager
  • AFSecurityPolicy
  • <AFURLRequestSerialization>
  • <AFURLResponseSerialization>

AFHTTPSessionManager

AFHTTPSessionManager 是我们使用 AFNetworking 框架最外层或者是可以直接使用的类,它对于框架核心类 AFURLSessionManager 进行了常用的封装。比如设置 BaseURL 和请求序列化,AFHTTPSessionManager 重要的两个属性:

@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer

当发送一个 GET 请求时,框架会依次调用以下三个方法:

[AFHTTPSessionManager GET:parameters:progress:success:failure:]
[AFHTTPSessionManager dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:]
[AFURLSessionManager dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:]

第一个方法是 AFHTTPSessionManagerGETPOSTPUTPATCHDELETE 等请求做的一个封装,它们的区别就是请求方法的不同,不同的请求会传入不同的参数给第二个方法,在第二个方法中会对传入的参数进行序列化。具体的序列化方法可以查看 AFHTTPRequestSerializer。然后会把序列化之后的 NSMutableURLRequest 作为参数调用第三方方法,这时就进入了框架的核心类: AFURLSessionManager

AFURLSessionManager

AFURLSessionManager 持有一个 NSURLSession 类型的 session 属性,并作为该属性的 delegate,遵守实现了 NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate 四个代理方法,如果任何子类继承重写这些方法,都必须先调用父类的实现。

initWithSessionConfiguration:AFURLSessionManager 的指定初始化方法,并且在session 初始化指定为其的代理:

self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

这样的话当一个网络请求的数据有返回或者状态被更改的时候,可以在代理方法中及时获知。AFURLSessionManagersession 属性可能会实例化多个 NSURLSessionDataTask 对象,但是 AFURLSessionManager 并不会直接维护它们,而是通过一个可变字典以 task.taskIdentifier 为 key 保存一个私有类 AFURLSessionManagerTaskDelegate 的实例,这个实例会保存这次网络请求的回调、响应数据和进度,两者是一对一的关系。当 AFURLSessionManager 所实现的协议方法被调用时,会及时更新 AFURLSessionManagerTaskDelegate 实例的数据。

在实现文件中,框架还对 NSURLSessionDataTaskresumesuspend 方法进行了 Hook,在方法执行的时候发送通知。

除此之外,AFURLSessionManager 还管理者 AFSecurityPolicyAFNetworkReachabilityManager,来保证请求的安全和查看网络连接情况,它有一个 AFJSONResponseSerializer 的实例来序列化 HTTP 响应。

AFURLRequestSerialization 和 AFURLResponseSerialization

<AFURLRequestSerialization><AFURLResponseSerialization> 只是两个协议,并且分别只有一个方法。

@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(nullable id)parameters
                                        error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end

@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end

实现 <AFURLRequestSerialization> 协议的有 AFHTTPRequestSerializer 及其两个子类: AFJSONRequestSerializerAFPropertyListRequestSerializerAFHTTPRequestSerializer除了实现协议之外还封装了对 TTPHeader 和 HTTPBody 操作。

实现 <AFURLResponseSerialization> 协议的有 AFHTTPResponseSerializerAFJSONResponseSerializer 等数个子类。

AFHTTPResponseSerializer 有两个属性分来判断接受的 StatusCode 和 ContentType:

@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;
@property (nonatomic, copy, nullable) NSSet <NSString *> *acceptableContentTypes

AFJSONResponseSerializerAFHTTPSessionManager 类默认的响应序列化类型。

AFNetworkReachabilityManager

AFNetworkReachabilityManager 作用是监听网络,是一个比较独立的模块,可以拿出来单独使用。

指定构造方法: initWithReachability:AFNetworkReachabilityManager 会持有这个 SCNetworkReachabilityRef 属性。

调用 startMonitoring 开始监听:

- (void)startMonitoring {
    // 先停止监听
    [self stopMonitoring];
    // 不符合条件,直接返回
    if (!self.networkReachability) {
        return;
    }
    // 创建一个block作为 SCNetworkReachabilityContext 初始化的第二个参数,参数名:info 参数类型:无类型指针
    __weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;

        strongSelf.networkReachabilityStatus = status;
        if (strongSelf.networkReachabilityStatusBlock) {
            strongSelf.networkReachabilityStatusBlock(status);
        }

    };
    // 创建监听上下文
    SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
    // 设置监听回调,回调的是 AFNetworkReachabilityCallback 函数,并且callback会被作为参数一起回调
    SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
    // 开始监听
    SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
    // 获取当前的网络状态并回调
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
        SCNetworkReachabilityFlags flags;
        if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
            AFPostReachabilityStatusChange(flags, callback);
        }
    });
}

我的博客:https://iosgg.cn/

Git的基础知识和使用

Git常用命令

Git 配置

Git 配置变量存放在以下三个不同的地方:(摘自1.5 起步 - 初次运行 Git 前的配置

  • /etc/gitconfig 文件:系统中对所有用户都普遍适用的配置。若使用 git config 时用 --system 选项,读写的就是这个文件。
  • ~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用 git config 时用 --global 选项,读写的就是这个文件。
  • 当前项目的 Git 目录中的配置文件(也就是工作目录中的 .git/config 文件):这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖 /etc/gitconfig 中的同名变量。
$ git config --global user.name "Sanyucz"
$ git config --global user.email [email protected]
    
要检查已有的配置信息,可以使用 git config --list 命令

创建版本库

版本库(仓库repository)的创建和文件添加与提交

// 初始化仓库
git init
// fileName 是要添加的文件 添加所有可以用通配符 *
git add <fileName>
// 提交到本地,message是提交的信息
git commit -m "<message>"

查看版本库和文件信息

// 查看仓库状态: git status
git status
// 如果要查看具体内容:git diff
git diff readme.txt
// 查看conmmit的历史记录:git log
git log
// 如果嫌输出信息太多,看得眼花缭乱的,可以试试加上 --pretty=oneline 参数:
git log --pretty=oneline
// 查询历史命令:git reflog
git reflog
// 查看工作区和仓库文件的区别
git diff HEAD -- < fileName >

版本回退和撤销修改

// 回退上一版本:
git reset --hard HEAD^
// 回退指定版本:3628164 是用 git log 查询出来的版本数值
git reset --hard 3628164
// 放弃修改文件
git checkout -- file
//可以把已被添加到暂存区的 'file' 移到工作区
git reset HEAD file

远程仓库和分支管理

// 克隆远程仓库
git clone <url>
// 添加远程仓库
git remote add origin https://github.com/sanyucz/cocoachina-ios-app.git
// 删除远程仓库
git remote rm origin
// 创建dev分支,然后切换到dev分支:
git checkout -b dev
// 和下面两条命令等同:
git branch dev
git checkout dev
// 查看当前分支:
git branch
// 把 'dev' 分支合并到主分支然后再删除 'dev' 分支
git merge dev
git branch -d dev
// 删除远程分支:(其实是把一个空分支push到远程某个分支)
git push origin :dev

撒销一个合并

// 如果你想把当前的修改都放弃,可以用下面的命令回到合并之前的状态:
git reset --hard HEAD
// 如果你已经把合并后的代码提交,但还是想把它们撒销:
git reset --hard ORIG_HEAD
// 再如果你不光把代码提交,你提交之后又做了新的修改,又不想放弃修改
// HEAD 合并的那个 版本号
git revert -m 1 HEAD

标签

git tag v1.0 // 打标签到当前分支最新的 commit 上
git tag v0.9 6224937 // 打标签到当 head 为 6224937 的 commit 上
git tag // 查看标签
git show <tagname> // 查看标签信息
git tag -a v0.1 -m "version 0.1 released" 3628164 // -a指定标签名,-m指定说明文字
git tag -d v0.1 // 删除标签
git push origin <tagname> // 推送某个标签到远程
git push origin --tags // 推送到远程的本地标签
git tag -d v0.9 // 加上下个命令删除远程的标签
git push origin :refs/tags/v0.9

配置别名

一般可以:co 表示 checkoutci 表示 commitbr 表示 branch

git config --global alias.st status
// 一个丧心病狂的别名配置:
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

GIT 的配置

  1. /etc/gitconfig 文件:系统中对所有用户都普遍适用的配置。若使用 git config 时用 --system 选项,读写的就是这个文件。
  2. ~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用 git config 时用 --global 选项,读写的就是这个文件。
  3. 当前项目的 Git 目录中的配置文件(也就是工作目录中的 .git/config 文件):这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖 / etc/gitconfig 中的同名变量。
git config --global user.name "John Doe"
git config --global user.email [email protected]

Git图解

廖大的图实在是太通俗易懂了,无耻盗图ing..

Git结构示意图

Git的版本库里面有个 stage(暂存区),我们平时工作的文档目录是工作区,对文件有了修改执行 git add 命令就会把变化的文件添加到版本库的暂存区,再执行 git commit 才会把文件从暂存区添加到当前分支。
stage (暂存区)在物理上的路径是 .git/index ,文件存储的是每次的修改。

HEAD 其实是一个指向当前分支的指针,

Git HEAD指针

当创建了 dev 分支的时候,是创建了一个 dev 指针,并且直接把 HEAD 指向 dev 指针。

Git 创建分支

当合并分支的时候直接把主分支 master 指向 dev 分支。

runtime(三) Objective-C 的消息转发机制与动态添加方法

在上上一篇博客 runtime系列(一) objc_msgSend 中介绍了运行时的消息传递机制,但是却没有说对象收到消息却无法解读该怎么办。本篇博客就着重介绍当消息传递时无法解读的时候就会启动的 消息转发机制( message forwarding )

开发可能经常会遇到这种情况:

2016-04-20 13:14:07.391 runtime[1096:22076] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[AutoDictionary setDate:]: unrecognized selector sent to instance 0x100302f50'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff9f2d94f2 __exceptionPreprocess + 178
	1   libobjc.A.dylib                     0x00007fff90db3f7e objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff9f3431ad -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
	3   CoreFoundation                      0x00007fff9f249571 ___forwarding___ + 1009
	4   CoreFoundation                      0x00007fff9f2490f8 _CF_forwarding_prep_0 + 120
	5   runtime                             0x0000000100001c1c main + 124
	6   libdyld.dylib                       0x00007fff91df85ad start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

这个异常信息是由 NSObjectdoesNotRecognizeSelector: 方法抛出来的,本来是给 AutoDictionary 的一个实例对象发送消息,但是该对象并没有 setDate: 方法,所以消息转发给了 NSObject ,最后抛出异常。

先看下消息处理机制流程图:

消息处理机制流程图

消息转发分为两阶段三步,第一阶段先看接受消息的对象能不能自己处理这个无法解读的消息,这一步可以动态的添加方法去解读接受这个消息;第二阶段是先看看对象自己不能处理这个消息,能不能交给其他对象来进行处理,在这一步如果仍然无法解读消息,那么就会走最后一步:把和消息有关的所有细节封装到一个 NSInvocation 中,再询问一次对象是否能解决。
看下三个方法:

// 询问对象是否自己处理,是返回YES,一般会在这个方法里面动态添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel;

// 这一步询问对象把消息交给哪个对象来进行处理
- (id)forwardingTargetForSelector:(SEL)aSelector;

// 如果走到这一步的话,就把消息的所有信息封装成 NSInvocation 对象进行 "最后通牒"
- (void)forwardInvocation:(NSInvocation *)anInvocation;

来一段代码示例:
新建一个 AutoDictionary 类,添加一个 NSDate 类型的 date 属性,在实现文件里面用 @dynamic date; 禁止自动生成存取方法,这样当代码中给 AutoDictionary 实例对象的 date属性赋值时就会出现消息无法解读的现象。
.h 文件:

@interface AutoDictionary : NSObject

@property (nonatomic, strong) NSDate *date;

@end

.m 实现文件代码内容:

@interface AutoDictionary()
@property (nonatomic, strong) NSMutableDictionary *backingStore;

/**
 *  该类仅在实现文件 实现了
 *  - (NSDate *)date
 *  - (void)setDate:(NSDate *)date
 *  两个方法,用于处理 AutoDictionary 无法解读的消息
 */
@property (nonatomic, strong) MethodCreator *methodCreator;
@end
@implementation AutoDictionary
//
@dynamic date;
//
- (instancetype)init{
    if (self = [super init]) {
        self.backingStore = [NSMutableDictionary dictionary];
        self.methodCreator = [MethodCreator new];
    }
    return self;
}

#pragma mark - 消息转发机制 :1.动态添加方法 2.后备消息接收者 3.封装NSInvocation,最后通牒
// 3. 封装NSInvocation,最后通牒
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    //
}
// 2. 无法接受消息,选择由谁来接受
- (id)forwardingTargetForSelector:(SEL)aSelector{
    return self.methodCreator;
}
// 1. 动态添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSString *selString = NSStringFromSelector(sel);
    if ([selString hasPrefix:@"set"]) {
        class_addMethod(self, sel, (IMP)autoDictSetter, "");
    }else{
        class_addMethod(self, sel, (IMP)autoDictGetter, "");
    }
    return YES;
}
id autoDictGetter (id self, SEL _cmd){
    AutoDictionary *dict = self;
    NSString *key = NSStringFromSelector(_cmd);
    return [dict.backingStore objectForKey:key];
}
void autoDictSetter (id self, SEL _cmd, id value){
    AutoDictionary *dict = self;
    NSString *selString = NSStringFromSelector(_cmd);
    NSString *key = [selString substringWithRange:NSMakeRange(3, selString.length-4)];
    key = [key lowercaseStringWithLocale:[NSLocale currentLocale]];
    if (value) {
        [dict.backingStore setObject:value forKey:key];
    }else{
        [dict.backingStore removeObjectForKey:key];
    }
}

@end

测试代码:

AutoDictionary *dict = [AutoDictionary new];
dict.date = [NSDate date];
NSLog(@"dict.date = %@",dict.date);

Swift 的函数和闭包

函数的关键字是 func ,函数定义的格式是:

swift-function

func funcName(para:paraType) -> returnType{
    // code
}

函数的参数标签

其中参数的那部分的详细结构是用小括号括起来,参数名,冒号,参数类型: (number:Int)
在默认情况下,函数的参数标签使用参数名,或者用 _ 不使用参数标签,也可以自定义标签,我们定义一个奇葩的函数:

func 求和(数字1 num1:Float, 数字2 num2:Float) -> Float {
   return num1 + num2
}

求和 就是方法名,数字1 就是自定义的参数标签。调用时会显示标签:

let sum = 求和(数字1: 2, 数字2: 4)

函数返回多个值

swift 还可以用元组返回多个返回值:

func compare(numarray:([Int])) -> (min:Int, max:Int) {
   var min = numarray[0]
   var max = numarray[0]
   for num in numarray {
       if num > max {
           max = num
       }else if num < min{
           min = num
       }
   }
   return (min, max)
}

调用时获取返回值:

compare(numarray: [1, 2, 3, 4, 5]).max

函数嵌套函数

swift 语法中函数可以嵌套函数,用于分割太长或者太复杂的函数:

// 不要在意逻辑,只是为了示例一下。。。
func sumWithArray(numArray:([Int])) -> Int{
   var sum = 0
   func add(num1:Int, num2:Int) -> Int{
       return num1 + num2
   }
   sum = add(num1: numArray[0], num2: numArray[1])
   return sum
}

返回一个函数

函数还可以用一个函数做为返回值

func makeMethod() -> ((Int)->(Int)) {
   func addOne(num:Int)->(Int){
       return num+1
   }
   return addOne
}

函数调用:

print("makeMethod()(1993): ",makeMethod()(1993))
// makeMethod()(1993):  1994
/**
makeMethod() 返回的是一个函数,继续传入参数 1993,最后返回 1994
*/

传入一个函数

函数可以把一个函数当做返回值返回,也可以当做一个参数来传入:

func sumOfMaxMin(numarray:([Int]),compare:(_ numarray:([Int]))->(min:Int, max:Int)) -> (Int) {
   return compare(numarray).max + compare(numarray).min
}

可以看到, sumOfMaxMin 函数有两个参数:numarray:([Int])compare:(_ numarray:([Int]))->(min:Int, max:Int) 。其中 compare 是一个函数。

在调用的时候:

var sumOfMaxMinValue = sumOfMaxMin(numarray: [1, 2, 3, 4, 5],compare: compare)

compare 是上个例子中的函数。当然,我们也可以不传入一个现成已经定义和实现的函数:

var sumOfMaxMinValue = sumOfMaxMin(numarray: [1, 2, 3, 4, 5]) { (numarray:([Int])) -> (min: Int, max: Int) in
  var min = numarray[0]
  var max = numarray[0]
  
  for num in numarray {
      if num > max {
          max = num
      }else if num < min{
          min = num
      }
  }
  
  return (min, max)
}

函数是一种特殊的闭包

大家伙看到这里,肯定会一拍大腿:哎呦,这玩意不就是闭包嘛!

(The Swift Programming Language)函数实际上是一种特殊的闭包:它是一段能之后被调取的代码。闭包中的代码能访问闭包所建作用域中能得到的变量和函数,即使闭包是在一个不同的作用域被执行的

我们可以使用{}来创建一个匿名闭包。使用in将参数和返回值类型声明与闭包函数体进行分离。

let numArray:([Int]) = [1, 2, 3, 4, 5] 
var newNumArray:([Int]) = numArray.map({
  (num:Int) -> Int in
  let newNum = num * 3
  return newNum
})

闭包的简写

如果闭包的类型已知,那么可以省略参数和返回值的类型

let numArray:([Int]) = [1, 2, 3, 4, 5]
var newNumArray:([Int]) = numArray.map({
  num in
  let newNum = num * 3
  return newNum
})

单个语句闭包会把它语句的值当做结果返回

let numArray:([Int]) = [1, 2, 3, 4, 5]
var newNumArray:([Int]) = numArray.map({
  num in
  num * 3
})

如果把上面的闭包写成一行的话

let numArray:([Int]) = [1, 2, 3, 4, 5]
var newNumArray:([Int]) = numArray.map({num in num * 3})

我们可以通过参数位置而不是参数名字来引用参数,那么上面的代码就变成这样

let numArray:([Int]) = [1, 2, 3, 4, 5]
var newNumArray:([Int]) = numArray.map({$0 * 3})

当一个闭包是传给函数的唯一参数,我们可以完全忽略括号

let numArray:([Int]) = [1, 2, 3, 4, 5]
var newNumArray:([Int]) = numArray.map{$0 * 3}

runtime(四) method swizzling 与AOP编程

什么是 AOP : (site: baike.baidu.com),引用百度百科中的解释就是:

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

主要功能:
日志记录,性能统计,安全控制,事务处理,异常处理等等

主要意图:
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

iOS 开发中的 AOP
Objective-C 中,类的方法列表会把选择器的名称映射到方法的实现上,这样 动态消息转发系统 就可以以此找到需要调用的方法。这些方法是以函数指针的形式来表示,这种指针叫做 IMP
如下:

c1f0660ejw1f51w4zipmhj20c5052glu

id (*IMP) (id, SEL, ...)

Objective-Cruntime 机制以此提供了获取和交换映射IMP的的接口:

// 获取方法
Method class_getInstanceMethod(Class cls, SEL name);
// 交换两个方法
void method_exchangeImplementations(Method m1, Method m2)

我们可以通过上面两个方法来进行选择器和所映射的IMP进行交换:

c1f0660ejw1f51w5m2wipj20c008874r

来,直接上代码示例,比如我们的要实现功能是在每个控制器的- viewDidLoad方法里面log一下,一般有三种实现方式:

  1. 直接修改每个页面的 view controller 代码,简单粗暴;
  2. 子类化 view controller ,并让我们的 view controller 都继承这些子类;
  3. 使用 Method Swizzling 进行 hook,以达到 AOP 编程的**

第一种实现的代码是在每个类的里面都这么写:

- (void)viewDidLoad {
    [super viewDidLoad];
    DDLog();
}

第二种是只在基类里面写。然后所有的控制器都继承这个基类。
最后一种是最佳的解决方案:

@implementation UIViewController (Log)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL originalSelector = @selector(viewDidLoad);
        SEL swizzledSelector = @selector(log_viewDidLoad);
        //
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        //
        BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

#pragma mark - Method Swizzling

- (void)log_viewDidLoad{
    [self log_viewDidLoad];
    DDLog(...);
}
@end

注意:

  • 为什么使用 + (void)load ?因为父类、子类和分类的该方法是分别调用,互不影响,而且是在类被加载的时候必定会调用的方法。

Swift 的对象和类

类的定义

类定义的关键字是 class,我们用 class + 类名 + ":" + 父类,比如定义一个 Person 类,类中去声明和定义变量和函数:

class Person: NSObject {
    var name: String
    public func sayHello(){
        print("Hello ~")
    } 
}

类的扩展

extension Person {  // 给人加一个飞的功能...
    func fly() {}
}

属性

存储属性

存储属性就是存储在类或者结构体一个实例里的一个常量和变量,用 var 或者 let 修饰。

但是如果一个结构体实例被声明为常量,那么即便这个结构体的某个属性是变量,也是不能去改变的:

let point = CGPoint(x: 1, y: 1)
point.x = 10    // 编译是不会通过的

延迟存储属性

lazy var nameLabel:UILabel = {
   let label = UILabel()
   // ...
   return label
}()

其实就是把一个立即执行的闭包的返回值赋值给属性,以达到懒加载的目的。

属性观察器

willSet 在新的值被设置之前调用
didSet 在新的值被设置之后立即调用
需要注意的是:当为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者。

计算属性

计算属性提供了一个 getter 和一个可选的 setter 。属性的 gettersetter 的关键词是 getset ,在 setter 中新值是 newValue

class Person: NSObject {
    var birthYear: Int
    var age : Int{
        set{
           self.age = newValue
           self.birthYear = 2016 - newValue
        }
    }
}

只读计算属性

当一个属性只有 getter 而没有 setter 时,那么它就是一个只读计算属性

类对象的实例和访问:

var person = Person()
person.age = 10

类的构造和析构

我们可以给 Person 自定义一个构造函数,构造函数中需要给所有的存储型属性一个赋值或者默认值:

init(name: String) {
   self.name = name
}

这样我们就可以用 name 来实例化一个对象: var person = Person(name:"Tom")

如果所有属性都有默认值,我们没有自定义的构造,系统会生成一个默认的构造函数:var person = Person()
相应的,如果我们自定义了一个构造函数,那么系统便不会为该类生成默认的构造函数。
但是,我们可以把自定义的构造函数写到类的扩展(extension)里,而不是类的原始定义里面。

与构造函数对应的是析构函数:deinit

指定构造器和便利构造器
指定构造器里面会初始化类实例所有的属性,所以为了保证继承的属性也能被初始化,子类的指定构造器都会调用父类的指定构造器,而类的便利构造器总是会调用本类的指定构造器。
也就是说:指定构造器需要向上调用,便利构造器需要横向调用

所以,我们可以把父类的指定构造函数重写成便利构造,却不能重写父类的便利构造。即便重写了父类的便利构造函数,但是由于我们不能直接调用父类的便利构造函数,所以不用给函数以 override 修饰。

必要构造器 如果某个构造函数被 required 修饰,那么该函数就是必要构造函数,子类继承该类时都必须要实现改构造函数。在子类重写父类的构造函数的时候,也要加 required 来修饰,以确定继承链上子类的子类也遵守。

除了构造和析构函数,类还有实例的私有函数、公共函数和静态函数。

类、函数的访问控制(访问、继承/重载 权限)

Swift 中的访问控制有 模块源文件 两个概念。用 "import" 导入的就是模块。

对于类而言的修饰词与权限:

修饰词 权限
open 修饰的类可以随意继承与访问
public 修饰的类只能在本模块内被继承,但是可以随便访问
internal 默认 - 模块内拥有访问权限
fileprivate 是文件外部不能被访问
private 是文件内部不能被访问
final 是文件内部也不能被继承

当然了,对于一个 internal 的类,其属性和方法的级别是不会超出类本身的,比如不可能是 public

函数也是同样的修饰词和权限,只是类的继承对应函数的重载权限。
函数除了以上几个,常用的修饰词还有 staticoverride

static修饰的是类方法。override修饰的是重写父类的方法。

Hexo + coding 博客的搭建和优化

hexo-next-arao-edit

以后会持续更新的~

博客在于内容而不在于形式,反反复复折腾体验了几个主题。以后或许便会稳定不在折腾。


前段时间用Github上Pages服务配合Hexo搭建了自己的博客,后来整理下记录下过程。
后来将主题换为 next (最终是使用fexo),所以更新一下,方便后来人,也便于自己再用到来查看

从写博客到在网页上展示的过程是这样的,用MarkDown写下自己的博客内容,通过Hexo生成静态的HTML网页,然后push(git源代码管理工具提交)到 coding/Github 上你的代码仓库,再通过代码托管平台的Pages服务发布到网上。

博客搭建

博客搭建的准备工作

步骤:

  • 申请 Github/coding 账号,然后新建一个 github_username.github.io 的仓库,比如我的Github用户名是 sanyucz ,所以仓库名称就是 sanyucz.github.io ,这个名字是要严格遵守的,搭建完成后也是在浏览器中直接通过 sanyucz.github.io 来访问自己的博客。同样的 coding 上直接新建一个用户名的仓库,比如我的就是 sanyucz ,搭建完成之后可以通过 sanyucz.coding.me 来访问。
  • 在Github/coding中加入自己电脑的 ssh。
  • 安装 node.js
  • 安装 homebrew
  • 安装 Hexo,参考文档

开始搭建博客

当 Hexo 也安装好之后执行以下命令来搭建本地博客:

```
$ hexo init <folder>	// <folder> 是博客的本地路径
$ cd <folder> // cd 切换目录到博客的本地路径
$ npm install			
```

当执行完之后,如果成功了的话,那么目录下的结构应该是这样的:

```
.
├── _config.yml	// 博客的配置文件
├── package.json
├── scaffolds
├── scripts
├── source			// 博客的内容
|   ├── _drafts
|   └── _posts
└── themes			// 博客的主题
```

继续在终端中输入 hexo s 会看到以下输出:

```
INFO  Start processing
INFO  Hexo is running at http://0.0.0.0:4000/. Press Ctrl+C to stop.
```

这时候在浏览器中输入 http://0.0.0.0:4000/ 就能看到自己的博客了,
停止服务摁下 Ctrl + C

```
// Hexo 常用的命令
hexo s 	// hexo server 开启本地 Hexo 服务
hexo g 	// hexo generate 生成本地 html js
hexo d 	// hexo deploy 发布到远程仓库
hexo c 	// hexo clean 删除generate生成的文件和缓存
hexo n 	// hexo new,用于新建一篇文章 eg: hexo new "我的第一篇博客"
```

配置 Hexo

Hexo 的配置文件有两个,其中一个是在博客目录下的 _config.yml,另外一个在 blog/theme/your_theme/_config.yml ,从位置就可以看出来,在博客目录下的主要是配置博客站点信息,比如所博客的title,作者等。在主题目录下多是配置主题样式相关,关于主题,下面再讲。

# Hexo Configuration
## Docs: https://hexo.io/docs/configuration.html
## Source: https://github.com/hexojs/hexo/
# Site
title: 王修斌的技术博客	// 博客标题
subtitle: 与肝胆人共事,无字句处读书	// 博客子标题
description: 爱生活、爱编程,爱学习,爱折腾。	// 博客的描叙
author: sanyucz	// 作者
language: zh-CN	// 默认语言
timezone:
# URL
## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'
url: https://sanyucz.github.io/ // 博客的地址
root: /
permalink: :year/:month/:day/:title/ // 时间格式
permalink_defaults:
# Pagination
## Set per_page to 0 to disable pagination
per_page: 10	// 博客列表每页显示博客的数量
pagination_dir: page
# Extensions
## Plugins: http://hexo.io/plugins/
#RSS订阅
plugin:
- hexo-generator-feed
- hexo-generator-baidu-sitemap
# Extensions
## Plugins: https://hexo.io/plugins/
# theme: hexo-theme-next
# theme: hexo-theme-material
theme: jacman	// 博客的主题
stylus:
  compress: true
// 博客评论 一般是多说和disqus,disqus是世界主流,在国内大多用多说,原因你懂得,这里两个都用了
duoshuo_shortname: sanyucz
disqus_shortname: sanyucz
# Deployment
## Docs: https://hexo.io/docs/deployment.html
// 远程仓库配置 这里我是推送到 coding 和 github 两个平台的仓库,所以配置了两个
deploy:
      type: git
      repo: 
          github: [email protected]:sanyucz/sanyucz.github.io.git,master
          coding: [email protected]:sanyucz/sanyucz.git,master
      # branch: master
      # message: github

为博客安装插件

npm install hexo-generator-index --save
npm install hexo-generator-archive --save
npm install hexo-generator-category --save
npm install hexo-generator-tag --save
npm install hexo-server --save
npm install hexo-deployer-git --save
npm install hexo-deployer-heroku --save
npm install hexo-deployer-rsync --save
npm install hexo-deployer-openshift --save
npm install [email protected] --save
npm install [email protected] --save
npm install hexo-generator-feed@1 --save
npm install hexo-generator-sitemap@1 --save

插件的作用各不相同,有的是用来生成RSS订阅,有的是压缩 js/CSS。
各种插件的安装推荐查看我所使用的 Jacman 主题的作者写的文章:如何使用 Jacman 主题

切换主题

Hexo 自带的主题太过于简单,这里推荐使用 Next。简洁,但不简单。
下载下来重命名为 next 并放到 blog/theme 下面,修改 站点配置文件 blog/_config.yml 中的 theme 字段: theme: next,注意冒号之后要有空格。

绑定域名

从万网(阿里域名)买了三年的top域名,三年也不过就一包烟钱而已,买过域名之后需要解析,解析是为了访问域名可以直接访问我们的博客。
在阿里域名注册并登陆之后,点击 控制台-域名-解析,解析如下:
![/images/aliyuming.png)

因为国内访问 Github 很慢,所以我默认访问的是coding上的博客。如果只在Github上面托管了博客,那么只需要解析一条默认的就好,需要注意的是域名后面有个 ‘ . ’。解析成功之后等待几分钟就可以通过域名来访问博客啦。

搭建中遇到的错误

DTraceProviderBindings 错误

{ [Error: Cannot find module './build/Release/DTraceProviderBindings'] code: 'MODULE_NOT_FOUND' }
{ [Error: Cannot find module './build/default/DTraceProviderBindings'] code: 'MODULE_NOT_FOUND' }
{ [Error: Cannot find module './build/Debug/DTraceProviderBindings'] code: 'MODULE_NOT_FOUND' }

试过官网建议 npm install hexo --no-optional,不行。
网上说的删除插件~~~,什么鬼?

最后我直接把引起错误的代码给注释了,根据报错信息,定位到dtrace-provider.js
注释其中内容:

var builds = ['Release', 'default', 'Debug'];
for (var i in builds) {
    try {
        var binding = require('./build/' + builds[i] + '/DTraceProviderBindings');
        DTraceProvider = binding.DTraceProvider;
        break;
    } catch (e) {
        // if the platform looks like it _should_ have DTrace
        // available, log a failure to load the bindings.
        if (process.platform == 'darwin' ||
            process.platform == 'sunos' ||
            process.platform == 'freebsd') {
            console.error(e);
        }
    }
}

OK,继续搞起!

博客的优化

虽然博客已经搭建起来,并且有了一个好看的主题,但是很多时候还是显得过于千篇一律。

访问速度优化

尝试了多种的优化方式,我觉得最简单、效果最好的就是,只在Github上托管的博客,最好在coding上也托管一份,毕竟coding是国内的,访问速度上要快很多。
另外再推荐一个压缩页面的插件:gulp,具体使用请另行百度。
其他方式:

访客、工具箱以及其他效果

绘制字符串字体大小自适应

突然想起前段时间做的需求:根据视图的大小来自动调整字体的大小。系统有给定的根据字体大小来自动调整视图(Label、Button)的大小,但是反过来则不行,网上也没有比较好的解决办法。

效果如下:

https://github.com/sanyucz/example/blob/master/DrawAutoLayoutFont/screenshot.gif?raw=true

所以,自己来分享一个办法:从系统的字体大小开始,计算文字所占用的 Size,超出则字体小一号再计算,直至合适;当文字尺寸小于所能占用的最大 Size,则字体大一号在继续计算,直至找到合适的大小。

所以,就需要系统的一个计算字体大小的方法:

- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(nullable NSDictionary<NSAttributedStringKey, id> *)attributes context:(nullable NSStringDrawingContext *)context

参数:

  1. size:字符串的最大尺寸
  2. options:字符串尺寸的计算方式
  3. attributes:字符串的的属性,比如:Font
  4. context:字符串绘制上下文,如果不清楚这是什么,传 nil 即可

options 类型的详细解释:

typedef NS_OPTIONS(NSInteger, NSStringDrawingOptions) {  
          
        NSStringDrawingUsesLineFragmentOrigin = 1 << 0,  
        // 整个文本将以每行组成的矩形为单位计算整个文本的尺寸  
        // The specified origin is the line fragment origin, not the base line origin  
          
        NSStringDrawingUsesFontLeading = 1 << 1,  
        // 使用字体的行间距来计算文本占用的范围,即每一行的底部到下一行的底部的距离计算  
        // Uses the font leading for calculating line heights  
          
        NSStringDrawingUsesDeviceMetrics = 1 << 3,  
        // 将文字以图像符号计算文本占用范围,而不是以字符计算。也即是以每一个字体所占用的空间来计算文本范围  
        // Uses image glyph bounds instead of typographic bounds  
          
        NSStringDrawingTruncatesLastVisibleLine  
        // 当文本不能适合的放进指定的边界之内,则自动在最后一行添加省略符号。如果NSStringDrawingUsesLineFragmentOrigin没有设置,则该选项不生效  
        // Truncates and adds the ellipsis character to the last visible line if the text doesn't fit into the bounds specified. Ignored if NSStringDrawingUsesLineFragmentOrigin is not also set.  
          
    }  

在工程考虑性能,部分控件采用的是自己绘制,所以代码应该如下:

- (void)drawRect:(CGRect)rect text:(NSString *)text color:(UIColor *)color{
    
    // Draw 的最大 Size
    CGSize maxSize = CGSizeMake(rect.size.width, rect.size.height);
    
    int currentFontSize = [UIFont systemFontSize];
    
    CGSize requiredSize = [text boundingRectWithSize:maxSize options:NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize: currentFontSize]} context:nil].size;
    
    // 小于最大尺寸 FontSize += 1
    if(requiredSize.height < maxSize.height && requiredSize.width < maxSize.width)
    {
        while (requiredSize.height <= maxSize.height && requiredSize.width < maxSize.width) {
            currentFontSize++;
            requiredSize=[text boundingRectWithSize:maxSize options:NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:currentFontSize]} context:nil].size;
        }
        currentFontSize--;
    }
    else    // 大于等于 FontSize -= 1
    {
        while (requiredSize.height >= maxSize.height || requiredSize.width >= maxSize.width) {
            currentFontSize--;
            requiredSize=[text boundingRectWithSize:maxSize options:NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:currentFontSize]} context:nil].size;
        }
    }
    
    // 绘制自适应文本
    [text drawAtPoint:CGPointMake(rect.origin.x + rect.size.width/2 - requiredSize.width/2,
                                  rect.origin.y + rect.size.height/2 - requiredSize.height/2)
       withAttributes:@{
                        NSFontAttributeName:[UIFont systemFontOfSize: currentFontSize],
                        NSForegroundColorAttributeName:color
                        }];
    
}

demo:https://github.com/sanyucz/example/tree/master/DrawAutoLayoutFont

不要滥用懒加载

写代码是一种习惯的养成,一种生活的态度。

有一次同事看着我写的代码说,你为什么要这么写啊?

我看了一下,是在 ViewController 和 Cell 里初始化视图,还有数据模型数组的时候,我都是用的懒加载(Lazy-Load)。

为什么这么喜欢用懒加载来实例化一个属性呢?

以前学 iOS 开发的时候,

  1. 觉得懒加载可以延迟加载,需要的时候才去加载数据;
  2. 数组和字典等集合类型还可以防止为初始化或者使用中被置为 nil
  3. 类的属性多了这么写看着更舒服、清晰
    。。。

基本上每个属性我都希望去懒加载实现它,这会给我一种错觉:这样写更好,性能更高!

其实,这是一个不好的习惯,随着编程时间越发的长,越是觉得之前有些偏激。

iOS 中懒加载的写法一般为重写 getter 方法,判断属性是否为 nil ,是的话去初始化,否就直接返回:

- (UIView *)layerView {
    if (!_layerView) {
        _layerView = [UIView new];
    }
    return _layerView;
}

但是我们有更清晰、简洁的写法

一般来说,如果 layerView 是控制器的属性,我们一般都会在 viewDidLoad 方法中去加载视图;如果是一个视图,我们一般会在 initWithFrame: 加载子视图,我们只需要安安静静的用以下代码来初始化即可:

 _layerView = [UIView new];

根本无需使用懒加载,因为如果你不是一个人在开发的话,你永远不会知道你的队友会在 get 方法里面做什么

而且这样写更简洁,更清晰。当属性很多的时候也可以使用以下方式来初始化:

self.layerView = ({
    [UIView new];
});

用懒加载至少六行代码,现在只需要一行或者三行就可以做到。

我们不能使用懒加载来防止那些可能出现的错误

很大一部分人用懒加载是为了保证数组和字典等集合类型在使用中永远不会是空值,这是错误的做法,因为可变集合类型被初始化之后,在正确的使用中如果不会被置 nil,那么也无需使用懒加载。如果因此而引发的问题,也可以帮我们提前找到原因。

对于耗时或性能很大的操作,我们可以使用惰性计算而不是懒加载

比如,我重构项目遇到的一个需求:请求股票列表返回的数据会告诉我总共会有上千条数据,并且不做分页,就是全部展示,滑到第几条就去请求第几条的数据。

上千条数据不做分页,我们也不可能全部请求回来,即便能全部请求回来也不可能在一个方法里去做这样的操作:

NSMutableArray *dataArray = [NSMutableArray array];
for (int i = 0; i < 100000; ++i) {
  [dataArray addObject:data];
}

但是,对于 TableView 来讲,上千条数据,当然需要 Array 的 count 返回是一千。这个时候我们可以用惰性计算来解决这个问题:有多少条数据,我们就让数组返回的 count 是多少,但是只有真正的向数组取这个下标的对象的时候,我们才去处理!

那我们继承 NSArray 来写(真正的写一个 NSArray 还需要重写其他几个方法,在此不细说):

typedef id(^HTLazyArrayItemBlock)(NSInteger index);
@interface HTLazyArray : NSArray
- (instancetype)initWithItemBlock:(HTLazyArrayItemBlock)block count:(NSInteger)count;
@end

#import "HTLazyArray.h"
@interface HTLazyArray()
@property (nonatomic, copy) HTLazyArrayItemBlock block;
@end

@implementation HTLazyArray {
    NSInteger _ct;
}
- (instancetype)initWithItemBlock:(HTLazyArrayItemBlock)block count:(NSInteger)count {
    if (self = [super init]) {
        _ct = count;
        self.block = block;
    }
    return self;
}
#pragma mark - override
- (NSUInteger)count {
    return _ct;
}
- (id)objectAtIndex:(NSUInteger)index {
    return self.block(index);
}
@end

我们初始化的时候传入一个 count,被 TableView 的代理方法访问的时候,有则返回数据模型,没有就先返回 nil,待到网络请求到数据再进行刷新。这样做的性能损耗微乎其微。

self.lazyArray = [[HTLazyArray alloc]initWithItemBlock:^id(NSInteger index) {
       HTQuoteAHCellModel *model = weakSefl.cache[@(index)];
       return model;
   } count:dataTotalCount];

那么,我们到底什么时候该用懒加载呢?

懒加载的使用需要看具体的场景,比如一个很可能不会被使用的属性,使用懒加载确实可以避免无所谓的性能损耗;
还有就是 null_resettable 修饰的属性,该属性意为:setter nullable,但是 getter nonnull,典型的就是控制器的 view 属性:“你可以不要我,把我置空;但只要你需要我,我就是在的”。诸如此类都可以使用懒加载。

https://iosgg.cn/2017/02/18/dont_abuse_lazy_load/

runtime 之Objc 中类和对象的本质

Objc 中任何对象都可以称之为 id 类型,那么看下在 objc.hid 类型的定义:

/// A pointer to an instance of a class.
typedef struct objc_object *id;

注释中的描述是 一个指向类的实例的指针,那么是不是意味一个类的实例即对象就是一个 objc_object 结构体呢?再看源码:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

@interface Object { 
    Class isa; 
}

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

struct objc_object {
private:
    isa_t isa;
}

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
}

通过阅读 runtime 的源码可以得出以下结构:

注:class_ro_t 中存放的是编译时可以确定的属性、方法和协议等

  1. Objc 中的对象是一个 objc_object 结构体,结构体中第一个变量是 isa_t ,存放着该对象所属类的信息;
  2. 类是一个objc_class 结构体,继承自 objc_object ,所以类也是一个对象,另外还有两个变量进行方法缓存和数据存放,比如变量、方法(实例方法,以 - 开头的方法)和所遵守的协议。
  3. 类的 isa 变量中存放的类是元类(meta-class),类是一个对象,对象的类型就是元类,元类存放着类的方法(类方法,以 + 开头的方法)。
  4. 元类也是类,所以元类也是对象。元类 isa 变量中存放的类是根元类(一般是 NSObject )。
  5. 根元类 isa 指向其自身
  6. 类和元类都是单例

做个试验,新建一个 BBObject 类,添加一个属性 bb_name,头文件如下:

@interface BBObject : NSObject

@property (nonatomic, strong) NSString *bb_name;

@end

那么以下代码会输出什么呢:

NSString *name = @"这是 bb_name";
void *cls = (__bridge void *)([BBObject class]);
void *bb_obj = &cls;
NSLog(@"%@",[(__bridge BBObject*)bb_obj bb_name]);

输出的是:2017-12-16 19:55:33.477716+0800 runtime[45876:7116185] 这是 bb_name

因为 Objc 中一个对象就是 首地址指向一个类的连续空间,为什么是连续空间?那是因为对象还有自己属性变量的值要存储,这也是为什么没有给 bb_objbb_name 属性赋值,却打印出 name 值的原因,在 iOS 中,栈的地址是由高到低,堆的地址是由低到高,在这段代码中栈中依次压入了 namebb_obj,而 bb_obj 对象自身的属性是根据自身首地址进行偏移去获取,所以会取到 name 的值。

使用 clang -rewrite-objc BBObject.m 可以把得到重写后的 C++ 文件,在其中也可以看到其中获取属性就是自身地址加偏移量:

static NSString * _I_BBObject_bb_name(BBObject * self, SEL _cmd) { 
    return (*(NSString **)((char *)self + OBJC_IVAR_$_BBObject$_bb_name)); 
}

我的博客:https://iosgg.cn/

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.