Why Git? Learn It!

总览

  • 为什么要用 Git?
  • Git + SourceTree + Gitlab 的使用演示
  • 提醒和推荐

为什么要用 Git?

  • 一个工具、一套方案,解决源码管理的问题

对于版本控制,我们需要什么?

  • 代码历史追溯
    • 某一次提交的改动(SVN,Git)
    • 某一个功能的改动过程(好的提交习惯更重要,在多人协作情况下提交历史更清晰)
  • 协同开发
    • 用分支来隔离上线与开发中的代码
    • 多人多线并行开发:1 人对 N 线,N 人对 1 线都有可能

秒极定律
一件事情如果能控制在 10s 内完成,人们就会极频繁的使用它。
举栗子:

  • docker 被推崇,从虚拟机到 Vagrant 到 docker,碰到了秒级定律
  • 持续集成,快速迭代

秒级的代码管理?

  • 本地代码飞快变化至任意历史提交记录:SVN 30s-2min;Git 秒级
    • 工作状态快速切换
  • 代码提交速度飞快:SVN 受限于网络速度;Git commit 秒级,push 受限于网络速度
  • 代码创建、切换、合并分支飞快:SVN 速度慢,很麻烦;Git 赞!
    • 勇于原型探索,历史清晰,易调整方向,易新建,易弃用
    • 与 code review 结合

SVN 比 Git 好在哪里

  • 更简单,符合原有自主文件夹管理版本的心理预期
    • Git 暴露概念过多,学习曲线陡峭,这点不如 hg
  • SVN 有目录级的权限控制
    • 适合大中型中间件软件公司:目录划分权限,职能长期固定
  • 也许还有其它,我不清楚

Git 真实的影响力

I’m an egotistical bastard, and I name all my projects after myself. First Linux, now Git.
—— Linus

  • 在 2016 问这个问题对互联网来说已是大势所趋
    • npm + pip 均托管在 Github
    • OpenJDK 使用 Hg(Mercurial)
    • Github 早已一统开源天下,Java 概莫能外
  • SVN 与 Git 二分天下,但 Git 占据的是互联网明星创业公司和 BAT 的优秀团队,为何?
  • 会 Git 找工作是加分项
  • 学习版本控制的另一种思维方式(学习一门语言就是学习一类思维方式)

一些问题

  • Git 不能直接解决目前后端的开发环境
    • code 规范的 branch 流程
    • 依赖,理清 maven 依赖和配合方式
    • 秒级上线(测试、预发布、正式)
    • 相应(尽可能少人力介入的)自动化测试、监控

Git + SourceTree + Gitlab 的使用演示

Git 使用流程示意图

local repo

  • 用 SourceTree 初始化一个本地项目
  • 小步提交,介绍 staged 的用法
    • stage or unstage or discard
    • stage 某一行代码
  • 一次提交
  • 介绍 master 分支及 branchs 界面
    • 留意 commit 编号
  • 创建一个分支 feature/login
  • 新建 login.java 文件,index.html 加一行代码
  • commit,观察分支树变化
  • 切换分支,观察代码变化
  • 创建一个分支 feature/share
  • 新建 share.py 文件,index.html 改一行代码
  • commit,观察分支树变化
  • 切回 feature/login,临时处理事情,再切回 feature/share
  • feature/share 分支删几行代码,再提交一次
  • feature/share merge to master 分支,观察分支树变化
  • feature/login merge to master 分支,处理冲突
  • 初始化 Git-flow
  • 新建 feature/school 分支,commit once
  • 用 Git-flow-gui 完成 feature/school 分支
  • 留意刚才的所有提交都在本地,思考与 SVN 区别(很像,只是分支极灵活)
  • Q&A

Gitlab

  • 注册帐号,ssh-key 授权(略)
  • 介绍 Gitlab 结构:team-project
  • 新建项目 test-repo
  • 留意空项目向导,复制代码库 url
  • 本地配置 remote-url,观察 remotes 变化
  • push,注意是 push 多条分支,留意 local-remote 分支对应关系
  • 再看 remotes 变化,分支树变化
    • remotes branchs 与 local branchs 一致
    • 分支树中多了 origin/*
  • 观察 Gitlab 界面变化
  • 介绍 Gitlab 单个项目界面
  • Q&A

来一次典型的开发过程(包含 code review)

  • 保证在 develop 分支,pull 最新代码
  • 网页端提交 readme 文件,造成 origin 变化,介绍 fetch,注意 branchs 变化
  • 重新 pull
  • 用 Git-flow-gui 建立 feature/news 分支
  • 产生两次 commit(包含 rename 文件)
  • 留意分支树变化,注意 Gitlab-web 端不存在刚才的提交,push,再对比
  • Gitlab-web 端新建 merge-request, 留意 source-branch, target-branch, title, ass
  • 被指派者 review 代码,web 端回复建议
  • 根据建议产生新的 commit,同时 push
  • 刷新 web 端观察变化:查看一次 commit,查看全部 changes
  • 被指派者回复 LGTM(look good to me),点击 merge
  • 回到 SourceTree,观察分支树变化,fetch 再看
  • 切回 develop 分支
  • Q&A
    • 有冲突 merge 按钮会不可点击,merge develop to work_branch ,在 develop 分支外解决冲突,commit&push,merge 按钮就可点击了
    • 可方便的与自动化测试等结合起来

提醒和推荐

一些提醒(坑)

  • 初次使用 Git,注意设置 git config --global email&name
  • Windows 下的 Git 使用的确有些不便(不过我不熟悉,也不清楚具体问题)
  • 授权方式推荐 ssh-key
  • 忽略文件的配置
  • 着手代码前思考终极问题:『我在哪里,要去何处?』(先认清所在分支,pull)
  • commit 已经 push 到远端了,这个时候不要想着再去修改了
  • 学习新东西,坑总是有的,多填了也就会了

一些推荐

  • 强调思路,记住秒级定律,效率为王
  • 小步提交,每小时至少提交一次
    • 一次任务的实现过程:整体设计,框架(类),接口,单元测试,实现
  • 完整提交,完整提交,必须完整提交!不该有任一个提交项目不可运行。
  • 保证非工作期间,工作区间干净
  • 不想提交但要切换分支处理事物用 stash 处理
  • Github 经常被 X
    • coding.net 代码私有库托管可以用。找开源代码,永远是 Github
    • Google 也是被 X。珍爱生命,自配 VPN
  • 国内 Git 私有库托管推荐 coding.net,国外推荐 bitbucket(支持hg) 和 Gitlab

其它

  • 依赖(迷信)工具不可取,但工具会影响思维方式,而思维方式非常重要

举例:
SVN 的思维方式决定了 diff patch 的开源合作方式。交流不顺畅,实现思路难以程现,这种合作方式很长时间里都是高端人士的特权。
Git + Github 催生了 fork,成就了最大的程序员社交网站,也极力促进了开源社区的发展。

周精益分享 - 前端入门篇2期

博客地址

Swift柯里化 Curring 学习

什么是Curring

维基百科解释
在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

概念看上去还是有点抽象,直接看代码

例子
注:所有swift代码都可以放到playgroun中运行查看输出

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

// 一个普通求sum函数
func add(a: Int, b: Int, c: Int) -> Int{
print("\(a) + \(b) + \(c) = \(a + b + c)")
return a + b + c
}

add(1, b: 2,c: 3) // 打印1 + 2 + 3 = 6

// 柯里化版本的求sum函数
func addCur(a: Int)(b: Int)(c: Int) -> Int{
print("\(a) + \(b) + \(c) = \(a + b + c)")
return a + b + c
}

addCur(1)(b: 2)(c: 3) // 打印1 + 2 + 3 = 6

你也许觉得Curring只是用一个特殊的写法
其他与普通函数无异
那么换一种curring的调用方式

1
2
3
4
5
6
7
8
9
10
// curring 另类调用方式
let funcWithA = addCur(1)
print("funcWithA type : \(funcWithA.dynamicType)")
let funcWithAB = funcWithA(b: 2)
print("funcWithAB type : \(funcWithAB.dynamicType)")
let resultCurring = funcWithAB(c: 3)
print("resultCurring type : \(resultCurring.dynamicType)")
```

此时你还是会觉得这只是把一个函数拆开来调用了 那么我们打印一下 每个调用步骤中变量的类型

let funcWithA = addCur(1)
print(“funcWithA type : (funcWithA.dynamicType)”) //
let funcWithAB = funcWithA(b: 2)
print(“funcWithAB type : (funcWithAB.dynamicType)”)
let resultCurring = funcWithAB(c: 3)
print(“resultCurring type : (resultCurring.dynamicType)”)

1
2

打印结果为

1 + 2 + 3 = 6
funcWithA type : Int -> Int -> Int
funcWithAB type : Int -> Int
1 + 2 + 3 = 6
resultCurring type : Int

1
2
3
4
5
6
7
8
9
10
11
12
13

可以看到 变量funcWithA 为 Int -> Int -> Int 类型
funcWithAB 为Int -> Int

这说明 curring函数在绑定最后一个参数之前 每个步骤返回的值都是一个函数

###Curring实现原理

Swift实现Curring的基础有两个
- 函数是一级公民
- 闭包

[啊崢的Swift Curring文章](http://www.jianshu.com/p/6eaacadafa1a)代码例子解释的已经很清楚了

class Currying
{
/ uncurried:普通函数 /
// 接收多个参数的函数
func add(a: Int, b: Int, c: Int) -> Int{
println(“(a) + (b) + (c)”)
return a + b + c
}

/*** 手动实现柯里化函数 ***/
// 把上面的函数转换为柯里化函数,首先转成接收第一个参数a,并且返回接收余下第一个参数b的新函数(采用闭包)
// 为了让大家都能看懂,我帮你们拆解来看下
// (a: Int) : 参数
// (b:Int) -> (c: Int) -> Int : 函数返回值(一个接收参数b的函数,并且这个函数又返回一个接收参数c,返回值为Int类型的函数)

// 定义一个接收参数a,并且返回一个接收参数b的函数,并且这个函数又返回一个接收参数c,返回值为Int类型的函数
func add(a: Int) -> (b:Int) -> (c: Int) -> Int{

    // 一个接收参数b的函数,并且这个函数又返回一个接收参数c,返回值为Int类型的函数
    return { (b:Int) -> (c: Int) -> Int in

        // 返回一个接收余下第一个参数c,并且有返回结果为Int类型的函数
        return { (c: Int) -> Int in

            return a + b + c;

 注解: 这里为什么能使用参数a,b,c?
       利用闭包的值捕获特性,即使这些值作用域不在了,也可以捕获到他们的值。
       闭包会自动判断捕获的值是值拷贝还是值引用,如果修改了,就是值引用,否则值拷贝。

       注意只有在闭包中才可以,a,b,c都在闭包中。

        }

    }

}


/*** curried: 系统自带的柯里化函数 ***/
func addCur(a: Int)(b: Int)(c: Int) -> Int{
    println("\(a) + \(b) + \(c)")
    return a + b + c
}

}

1
2
3
4

###OC版的Curring

大部分的iOS程序员都还是习惯写OC 找了一个[OC版本的Curring](https://gist.github.com/lukhnos/1771842)实现 我们可以理解的更深刻

include

int f(int x, int y)
{
return x + y;
}

int main()
{
// 闭包1 绑定第一个参数
typedef int (^int_to_int_t)(int);
// 闭包2 绑定第二个参数
typedef int_to_int_t (^int_to_int_to_int_t)(int);

int_to_int_to_int_t h = ^(int x) {
    int_to_int_t g = ^(int y) {
        // 将两个闭包持有的参数做处理 返回结果
        return f(x, y);  
    };

    return g;        
};

// 第一个闭包持有 5
int_to_int_t g = h(5);

int z;
// 第二个闭包持有10 并返回结果
z = g(10);
printf("%d\n", z);

}

1
2
3
4
5
6
7
8
9
10
11
12

###什么?你觉得然并卵!

你一定觉得这是一个然并卵的东西

好吧 我第一个看到的时候 只是觉得有趣 其实也没有想到较好的应用场景

[为什么要柯里化](https://gist.github.com/jcouyang/b56a830cd55bd230049f)这篇文章提供的例子给了很多启发

用Swift重新实现了一遍 又加了一点打印 豁然开朗

上代码

// 拼接字符
func concat(chars: Array) {
var resultString = “”
for char in chars {
resultString += char
}
print(resultString)
}

concat([“1”,”2”,”3”])
print(“**“)

// 每个字符代表的数字 + 1 后再拼接
func concatAdd(chars: Array, addNum: Int) {
var resultString = “”
for char in chars {
var num = Int(char)!
num = num + addNum
let newChar = String(num)
resultString += newChar
}
print(resultString)
}

concatAdd([“1”,”2”,”3”], addNum: 1)
concatAdd([“1”,”2”,”3”], addNum: 2)
concatAdd([“1”,”2”,”3”], addNum: 3)

print(“**“)
// 每个字符代表的数字 10 后再拼接
func concatMultiply(chars: Array, multiplyNum: Int) {
var resultString = “”
for char in chars {
var num = Int(char)!
num = num
multiplyNum
let newChar = String(num)
resultString += newChar
}
print(resultString)
}

concatMultiply([“1”,”2”,”3”], multiplyNum: 1)
concatMultiply([“1”,”2”,”3”], multiplyNum: 2)
concatMultiply([“1”,”2”,”3”], multiplyNum: 3)

print(“**“)
/
让我们使用柯里化吧
*
/
func add(a: Int)(b: Int) -> Int{
return a + b
}

func multiply(a: Int)(b: Int) -> Int{
return a * b
}

func concatByCurring(chars: Array)(caculateFunc: (Int)->Int) {
var resultString = “”
for char in chars {
var num = Int(char)!
var newNum = caculateFunc(num)
let newChar = String(newNum)
resultString += newChar
}
print(resultString)
}

concatByCurring([“1”,”2”,”3”])(caculateFunc: add(1))
concatByCurring([“1”,”2”,”3”])(caculateFunc: add(2))
concatByCurring([“1”,”2”,”3”])(caculateFunc: add(3))

print(“**“)
concatByCurring([“1”,”2”,”3”])(caculateFunc: multiply(1))
concatByCurring([“1”,”2”,”3”])(caculateFunc: multiply(2))
concatByCurring([“1”,”2”,”3”])(caculateFunc: multiply(3))

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

以上的代码用普通的函数 和 Curring函数 处理了同一个功能

Curring优势明显
- 重用了拼接代码
- 支持更为灵活的算法替换
- 写功能扩展的人不用怎么关心老代码的实现

###iOS开发中可以应用的场景

**使用Curring处理selector**
这个例子是我在看[王巍的Swifter](https://selfstore.io/~onevcat)时候看到的一个例子

不废话 直接上代码

// 打印func类型
class People: NSObject {
func speak(){
print(“hello”)
}

func printSpeak() {
    print("People func speak  : \(People.speak.dynamicType)")
}

}

People().printSpeak()
// 打印结果为 People func speak : People -> () -> ()
// 可见实例的方法就是一个Curring结构

1
2

下面是[Instance Methods are Curried Functions in Swift](http://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/?utm_campaign=iOS_Dev_Weekly_Issue_157&utm_medium=email&utm_source=iOS%2BDev%2BWeekly)中的例子 很开脑洞

//Selector demo
protocol TargetAction {
func performAction()
}

struct TargetActionWrapper : TargetAction {
weak var target: T?

// 此处的action类型是不是和我上面代码中打印的结构一致
let action: (T) -> () -> ()

func performAction() -> () {
    if let t = target {
        // Curring调用
        action(t)()
    }
}

}

enum ControlEvent {
case TouchUpInside
case ValueChanged
}

class Control {
var actions = ControlEvent: TargetAction

func setTarget<T: AnyObject>(target: T, action: (T) -> () -> (), controlEvent: ControlEvent) {
    actions[controlEvent] = TargetActionWrapper(target: target, action: action)
}

func removeTargetForControlEvent(controlEvent: ControlEvent) {
    actions[controlEvent] = nil
}

func performActionForControlEvent(controlEvent: ControlEvent) {
    actions[controlEvent]?.performAction()
}

}

class MyViewController {
let button = Control()

func viewDidLoad() {
    button.setTarget(self, action: MyViewController.onButtonTap, controlEvent: .TouchUpInside)
}

func onButtonTap() {
    print("Button was tapped")
}

}

// 调用
MyViewController().onButtonTap()

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

###小结
- Curring让计算过程更清晰 函数式编程的有点初见端倪
- Curring让计算过程更独立 可重用
- Curring让计算功能扩展更方便
- Curring让Swift中Selector的重构可以实现



## Umeng分享遇到的小坑 -- 张超耀
### 写在前面的话
- 在iOS9下,系统默认会拦截对http协议接口的访问,因此无法获取http协议接口的数据。对Umeng来说,具体表现可能是,无法授权、分享、获取用户信息等。
- iOS9新建项目默认需要支持bitcode,而不支持bitcode的SDK会导致无法编译运行。[App Thining](https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/AppThinning/AppThinning.html#//apple_ref/doc/uid/TP40012582-CH35))

[Umeng适配iOS9](http://dev.umeng.com/social/ios/ios9)

### 针对iOS9的Https特性,解决方案
- Plan A: 暂时退回到http协议:

>**1**、在项目的info.plist中添加一个Key:NSAppTransportSecurity,类型为字典类型。
>**2**、然后给它添加一个Key:NSAllowsArbitraryLoads,类型为Boolean类型,值为YES;

- Plan B:设置域。可以简单理解成,把不支持https协议的接口设置成http的接口


>**1**、在项目的info.plist中添加一个Key:NSAppTransportSecurity,类型为字典类型。

>**2**、然后给它添加一个NSExceptionDomains,类型为字典类型;

>**3**、把需要的支持的域添加給NSExceptionDomains。其中域作为Key,类型为字典类型。

>**4**、每个域下面需要设置3个属性:NSIncludesSubdomains、 NSExceptionRequiresForwardSecrecy、 NSExceptionAllowsInsecureHTTPLoads。
均为Boolean类型,值分别为YES、NO、YES。

### 针对App Thinning

### 添加Scheme白名单实现应用跳转(SSO等)
- 问题描述:在iOS 9下涉及到平台客户端跳转,系统会自动到项目info.plist下检测是否设置平台Scheme。对于需要配置的平台,如果没有配置,就无法正常跳转平台客户端。因此要支持客户端的分享和授权等,需要配置Scheme名单。
- 解决方案:

具体方法:
>1、在项目的info.plist中添加一LSApplicationQueriesSchemes,类型为Array。

>2、然后给它添加一个需要支持的项目,类型为字符串类型


### 注意
- 由于苹果审核政策需求,需要对未安装客户端平台进行隐藏,在设置QQ、微信AppID之后调用下面的方法,

[UMSocialConfig hiddenNotInstallPlatforms:@[UMShareToQQ, UMShareToQzone, UMShareToWechatSession, UMShareToWechatTimeline]];

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
 - but  这个接口只对默认分享面板平台有隐藏功能,自定义分享面板或登录按钮需要自己处理

- 对于自定义分享面板处理:
- UmengSDK已经嵌入相关API,直接用就好

### 只要按照官方文档来,基本上就能马到成功()



##MarkDown 的 CSS 实现配置 - 杨志平

####题目灵感来源
> 起源于我们现有的博客引擎主题交互很不错,但是排版烂的要死 ,我水平有限这里只是浅显介绍实现修改我们的markdown编译器的一些排版样式

#### 研究方向
> 自定义一个非标准化,有其他多元素的MarkDown解析器 如下几点:

- 可选框
`- [ ] `
- 本地图片索引,可控制对齐及大小
`![Alt text](http://path/to/img.jpg "optional title" 100x200)`
`![Alt text](./1449756974449.png)`
- 标签功能
`@(Share)[css, Markdown]`
- 代码高亮(不同语言)

`swift`

``` swift
private var majorModel = PickMajorModel()
private var subjectModel = OFFKeyNameModel()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

objectivec

1
2
3
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, kScreenW, kScreenH)];
scrollView.showsVerticalScrollIndicator = NO;
[scrollView addSubview:self.downMainView];

有一种惨不忍睹的即视感
Alt text

MarkDown来源及实现

Markdown is a plain text format for writing structured documents, based on conventions used for indicating formatting in email and usenet posts. It was developed in 2004 by John Gruber, who wrote the first markdown-to-html converter in Perl, and it soon became widely used in websites. By 2014 there were dozens of implementations in many languages.

见知乎上回答
实现一个markdown解析器需要具备那些知识

如何简单的改善文字编排的效果

更换博客主题(简单粗暴)

我们使用的博客引擎Hexo来举例,列举下面三个主题

  • landscape
  • hexo-theme-vno-master
  • hexo-theme-yilia-master
更换MarkDown编译器的主题

Mou 举例子,它提供了多套markdown语法下的排版样式
手动新创建一个CSS文本布局配置 Blog
Alt text

CSS配置文件修改

详细配置参数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
html { font-size: 62.5%; }
html, body { height: 100%; }

body {
font-family: Helvetica, Arial, sans-serif;
font-size: 150%;
line-height: 1.3;
color: #f6e6cc;
width: 700px;
margin: auto;
background: #27221a;
position: relative;
padding: 0 30px;
}

多级标题配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
h1 {
font-size: 28px;
color: black; }


h2 {
font-size: 24px;
border-bottom: 1px solid #cccccc;
color: black; }


h3 {
font-size: 18px; }


h4 {
font-size: 16px; }


h5 {
font-size: 14px; }


h6 {
color: #777777;
font-size: 14px; }

表格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
table {
padding: 0;border-collapse: collapse; }

table tr {
border-top: 1px solid #cccccc;
background-color: white;
margin: 0;
padding: 0; }

table tr:nth-child(2n) {
background-color: #f8f8f8
; }

table tr th {
font-weight: bold;
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }

table tr td {
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }

table tr th :first-child, table tr td :first-child {
margin-top: 0
; }

table tr th :last-child, table tr td :last-child {
margin-bottom: 0
; }

代码高亮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
code, tt {
margin: 0 2px;
padding: 0 5px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px; }


pre code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent; }

周精益分享 - 后端开发

Swift学习之二 - 张超耀

深度理解”?” && “!”

1
Optional其实是个enum,里面有None和Some两种类型。 nil就是Optional.None, 非nil就是Optional.Some, 然后会通过Some(T)包装(wrap)原始值,这也是为什么在使用Optional的时候要拆包(从enum里取出来原始值)的原因。声明为Optional只需要在类型后面紧跟一个"?"即可
1
2
var strValue: String?  //?相当于下面这种写法的语法糖
var strValue1: Optional<Int>

一旦声明为Optional的,如果不显式的赋值就会有个默认值nil。判断一个Optional的值是否有值,可以用if来判断:

1
2
3
if (strValue != nil) {
//do sth with strValue
}

怎么使用Optional值呢?
在使用Optional值的时候只前面需要加上一个”?”,就是这么简单。

  • 这个”?”什么意思?:
    1、 询问是否响应后面这个方法,如果是nil值,也就是Optional.None,就不响应后面的方法,直接跳过,
    2、 如果有值,就是Optional.Some,可能就会拆包(unwrap),然后对拆包后的值执行后面的操作,比如
1
let hashValue = strValue?.hashValue
  • strValue是Optional的字符串,如果strValue是nil,则hashValue也为nil,如果strValue不为nil,hashValue就是strValue字符串的值

  • 到这里我们看到了?的两种使用场景:

    1.声明Optional值变量
    2.用在对Optional值操作中,用来判断是否能响应后面的操作
    
  • 对于Optional值,不能直接进行操作,否则会报错
1
let hashValue1 = strValue!.hashValue

上面错误提示需要拆包(unwrap)后才能得到值,然后才能对其操作,那怎么来拆包呢?拆包提到了几种方法,一种是Optional Binding

1
2
3
if let str = strValue {
let hashValue = str.hashValue
}

还有一种是在具体的操作前添加”!”符号。

1
let hashValue2 = strValue!.hashValue

这里的”!”表示我确定肯定这里的的strValue一定是非nil的,随便用吧,木事de 就像下面:

1
2
3
if (strValue != nil) {
let hashValue = strValue!.hashValue
}
1
2
{}里的strValue一定是非nil的,所以就能直接加上!,强制拆包(unwrap)并执行后面的操作。
当然如果不加判断,strValue不小心为nil的话,就会出错,crash掉。

51offer例子解释『隐式拆包的Optional』

1
这种是特殊的Optional,称为Implicitly Unwrapped Optionals, 直译就是隐式拆包的Optional,就等于说你每次对这种类型的值操作时,都会自动在操作前补上一个!进行拆包,然后在执行后面的操作,当然如果该值是nil,也一样会报错crash掉。
  • “!”也有两种使用场景
    1、强制对Optional值进行拆包(unwrap)
    2、声明Implicitly Unwrapped Optionals值,一般用于类中的属性
    

swift 断言

1
2
3
4
断言(Assertions)
Optionals可以让我们检测值是否存在。在某些情况下,如果某个值不存在或者没有提供特定的满足条件,代码不应该继续往下执行。
在这些情况下,可以使用触发断言来终止执行并提供调试。
断言是在运行时检测条件是否为true,如果为true,就继续往下执行,否则就在这里中断。
1
2
3
var jobs = "我是一个好人"
assert(jobs == "我是一个好人", "我是一个好人")
assert(jobs == "我是一个坏人", "我是一个坏人么?")
1
2
3
4
5
什么时候使用断言呢?
包含下面的情况时使用断言:
1、整型下标索引作为值传给自定义索引实现的参数时,但下标索引值不能太低也不能太高时,使用断言
**2、传值给函数但如果这个传过来的值无效时,函数就不能完成功能时,使用断言。
3、Optional值当前为nil,但是后面的代码成功执行的条件是要求这个值不能为nil,使用断言

IM中输入框的优化方案实现

以下图的输入框为例

stand

输入框的呈现方式选择

以下两个方法对比

  • Keyboard的inputAccessoryView

相对推荐的方法,有更好的丰富的交互效果

可以与scrollView的一些属性直接绑定

  • InputView的frame适应

传统的方法

优点:灵活控制显示位置

缺点:过多的计算frame

InputAccessoryView方式

案例见GitHub的Demo

iOS的输入源都有输入源(keyboard及keyboard的配件InputAccessoryView),那通常的方法是输入源控件(Textfield、TextView等)使用一般的InputView的frame适应

解释几个技巧点

  • InputView( 输入源 )的父视图作为该InputView的InputAccessoryView,要避免相互引用
  • 巧用第三方不可见的InputView在适当时间点转移第一响应者给可见的InputView
  • 完成编辑时去除第一响应者(注意iOS9下键盘响应逻辑视图层级都发生了变化)

主要代码

simulator screen shot nov 27 2015 4 17 38 pm

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
// 单例初始化
+ (UUInputAccessoryView*)sharedView {
static dispatch_once_t once;
static UUInputAccessoryView *sharedView;
dispatch_once(&once, ^ {
sharedView = [[UUInputAccessoryView alloc] init];
sharedView->btnBack = [UIButton buttonWithType:UIButtonTypeCustom];
sharedView->btnBack.frame = CGRectMake(0, 0, UUIAV_MAIN_W, UUIAV_MAIN_H);
[sharedView->btnBack addTarget:sharedView action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
sharedView->btnBack.backgroundColor=[UIColor clearColor];
UIToolbar *toolbar = [[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, UUIAV_MAIN_W, 44)];
sharedView->inputView = [[UITextField alloc]initWithFrame:CGRectMake(UUIAV_Edge_Hori, UUIAV_Edge_Vert, UUIAV_MAIN_W-UUIAV_Btn_W-4*UUIAV_Edge_Hori, UUIAV_Btn_H)];
sharedView->inputView.borderStyle = UITextBorderStyleRoundedRect;
sharedView->inputView.returnKeyType = UIReturnKeyDone;
sharedView->inputView.clearButtonMode = UITextFieldViewModeWhileEditing;
sharedView->inputView.enablesReturnKeyAutomatically = YES;
sharedView->inputView.delegate = sharedView;
[toolbar addSubview:sharedView->inputView];
sharedView->assistView = [[UITextField alloc]init];
sharedView->assistView.delegate = sharedView;
sharedView->assistView.returnKeyType = UIReturnKeyDone;
sharedView->assistView.enablesReturnKeyAutomatically = YES;
[sharedView->btnBack addSubview:sharedView->assistView];
sharedView->assistView.inputAccessoryView = toolbar;
sharedView->BtnSave = [UIButton buttonWithType:UIButtonTypeCustom];
sharedView->BtnSave.frame = CGRectMake(UUIAV_MAIN_W-UUIAV_Btn_W-2*UUIAV_Edge_Hori, UUIAV_Edge_Vert, UUIAV_Btn_W, UUIAV_Btn_H);
sharedView->BtnSave.backgroundColor = [UIColor clearColor];
[sharedView->BtnSave setTitle:@"确定" forState:UIControlStateNormal];
[sharedView->BtnSave setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[sharedView->BtnSave addTarget:sharedView action:@selector(Done) forControlEvents:UIControlEventTouchUpInside];
[toolbar addSubview:sharedView->BtnSave];
});
CGRectGetHeight([UIScreen mainScreen].bounds);
return sharedView;
}

实现逻辑代码

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
+ (void)showKeyboardType:(UIKeyboardType)type content:(NSString *)content Block:(UUInputAccessoryBlock)block
{
[[UUInputAccessoryView sharedView] show:block
keyboardType:type
content:content];
}

- (void)show:(UUInputAccessoryBlock)block keyboardType:(UIKeyboardType)type content:(NSString *)content
{
UIWindow *window=[UIApplication sharedApplication].keyWindow;
[window addSubview:btnBack];

inputBlock = block;
inputView.text = content;
inputView.keyboardType = type;
assistView.keyboardType = type;
[assistView becomeFirstResponder];
shouldDismiss = NO;

[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardDidShowNotification
object:nil
queue:nil
usingBlock:^(NSNotification * _Nonnull note) {
if (!shouldDismiss) {
[inputView becomeFirstResponder];
}
}];
}

- (void)Done
{
[inputView resignFirstResponder];
!inputBlock ?: inputBlock(inputView.text);
[self dismiss];
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self Done];
return NO;
}

- (void)dismiss
{
shouldDismiss = YES;
[inputView resignFirstResponder];
[btnBack removeFromSuperview];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

InputView的frame适应

案例见GitHub的Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 键盘响应布局
@objc func keyboardFrameChanged(notification: NSNotification) {

let dict = NSDictionary(dictionary: notification.userInfo!)
let keyboardValue = dict.objectForKey(UIKeyboardFrameEndUserInfoKey) as! NSValue
let bottomDistance = mainScreenSize().height - keyboardValue.CGRectValue().origin.y
let duration = Double(dict.objectForKey(UIKeyboardAnimationDurationUserInfoKey) as! NSNumber)

UIView.animateWithDuration(duration, animations: {
self.inputViewConstraint!.constant = -bottomDistance
self.view.layoutIfNeeded()
}, completion: {
(value: Bool) in
self.chatTableView.scrollToBottom(animation: true)
})

}

ScrollView下拉动态修改keyboard(InputView)的frame

常见隐藏keyboard的一些方式

  • TouchBeigin
  • DidDrag
  • EndDrag
  • Interactive

iOS7 开始,ScrollView提供

1
2
3
4
5
6
@available(iOS 7.0, *)
public enum UIScrollViewKeyboardDismissMode : Int {
case None
case OnDrag // dismisses the keyboard when a drag begins
case Interactive // the keyboard follows the dragging touch off screen, and may be pulled upward again to cancel the dismiss
}

所以在ScrollView上添加scrollView.keyboardDismissMode = UIScrollViewKeyboardDismissMode.Interactive就可以了。
效果详见iOS7及以上原生设备的短信滑动消失键盘的交互

写负责任的代码之异常处理 - 曾铭

千万不要以为你可以忽视这个特殊的返回值,因为它是一种“可能性”。代码漏掉任何一种可能出现的情况,都可能产生意想不到的灾难性结果。

  • 重视代码(及业务)逻辑的异常情况,将其视为『正常流程』的一部分。
  • 重视函数的抛出异常,将其视为返回值之一(union类型)。

政策之后的对策

1
2
String foo() throws MyException {
}
  • catch 全部异常
  • try 全部代码
1
2
3
4
5
6
7
try {
...
...
... foo() ...
...
...
} catch (Exception e) {}

我们 API 为什么难调试

  • Service 层未声明抛出异常,也会抛出异常
  • 抛出的异常 API 层记入日志,向上抛时却丢弃了异常信息
  • API 层方法代码全部 try,很难定位到是哪一行出问题

推荐的对策

  • 只 catch 指定的 exception
  • try 进可能的小
1
2
3
4
5
try {
foo();
} catch (MyException e) {
Log.warning(e);
}
  • 源头思考全部可能性,内部的问题不抛给别人
  • 不要试图忽略(隐藏)错误,每次出错都是优化代码(反思自己局限)的好机会。否则,你终将付出代价。

周精益分享 - Swift入门专题

Swift初见 — 张超耀

1
2
3
4
5
6
7
8
9
10
// in objective-c, but in swift, #define can't be used any more  
// use let keyword to define a macro, look up original document:
/*
Simple Macros
Where you typically used the #define directive to define a primitive constant in C and Objective-C, in Swift you use a global constant instead. For example, the constant definition #define FADE_ANIMATION_DURATION 0.35 can be better expressed in Swift with let FADE_ANIMATION_DURATION = 0.35. Because simple constant-like macros map directly to Swift global variables, the compiler automatically imports simple macros defined in C and Objective-C source files.
*/
// in objective-c
// #define kCommonAPI @"http://xxxxxxx"
// but in swift, no #define, just use let to define
let kCommonAPI = "http://xxxxxxx"

注释 & 分号

  • 在Swift中,注释跟C/OC语言中的注释很像,但最大的不同点就是在Swift中多行注释可以嵌套
1
2
3
4
5
6
// 这是单行注释  

/* 这也是注释,但是多行注释
/*多行注释在swift中是可以嵌套的*/
/*原官方指导教程上说嵌套多行注释可以快速、简单地把大的代码块分成多块来注释 */
*/
  • 与其它开发语言不同的时,swift是不要求写分号的,当然如果想写,也是可以的。当你想把多个语句写到同一行时,这种情况下就一定要使用分号来隔开不同的语句了
1
2
3
let dog = "a gog" // 可以不添加分号  
let cat = "a cat"; print(cat)// 除了最后一条语句可以不添加分号外,其它都需要添加分号来隔开
let catTwo = "two cats"; let name = "Jobs"; print("\(name) has \(catTwos)")

类型转换

  • Swift不会像C、OC那样自动隐式转换类型,所以我们需要手动进行类型转换
1
2
3
4
5
6
7
8
let twoThousand: UInt16 = 2000   
// one是UInt8类型
let one: UInt8 = 1
// twoThousand是UInt16类型,one是UInt8类型,如果要执行相加,那么就需要进行类型转换
// 否则会报错的。
let twoThousandAndOne = twoThousand + UInt16(one)

** 浮点值转换成整型时,会截尾**

类型别名(Typealias)

  • 类型别名也就是给已经存在的类型起一个别名。定义类型别名是使用关键字typealias。 类型别名一般是为了让开发者更容易看出变量或者常量的类型或者是更好地归类某一个模块中需要使用到的类型,让开发者见名知意。
1
2
3
4
5
6
7
// 下面是给UInt16起一个别名,叫mySample  
// 然后就可以在其它地方使用这个mySample声明变量或者常量
typealias mySample = UInt16

// 由于前面已经定义了类型别名,那么这里使用AudioSample也相当于使用UInt16
所以mySample.min = UInt16.min,也就是0
var maxAmplitudeFound = mySample.min

“?” 和 “!”

  • Swift语言使用var定义变量,但和别的语言不同,Swift里不会自动给变量赋初始值, 也就是说变量不会有默认值,所以要求使用变量之前必须要对其初始化
    。如果在使用变量之前不进行初始化就会报错.
1
2
3
4
5
6
7
8
9
10
11
12
13
var stringValue : String?
stringValue = nil
print("\(stringValue)")
let hashValue = stringValue?.hashValue

// 这就是optional, strValue自动得到默认值:nil
// 这个nil跟Objective-C中的nil不同,不是指针,而是表示值不存在。
var strValue: String?

// 判断optional是否有值
if (strValue != nil) {
// do what you need to do here
}

下期预告

深度理解Swift的”?”和”!”

Swift之断言

浅谈Swift的closure

Dubbo 上手 — 陈奎

『图像和滤镜』 - 图像选择器

我们可以使用以下常规的图像获取方式

Alt text

图库与相册

Alt text

系统自带

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

let sheet = UIAlertController(title: "图片选择", message: "简单版的三种选择", preferredStyle: .ActionSheet)
// 判断设备是否支持相机(iPod & Simulator)
if (UIImagePickerController.isSourceTypeAvailable(.Camera)) {
sheet.addAction(UIAlertAction.init(title: "Camera", style: .Default, handler: { _ in
self.showPhotoes(.Camera)
}))
}
sheet.addAction(UIAlertAction.init(title: "PhotoLibrary", style: .Default, handler: { _ in
self.showPhotoes(.PhotoLibrary)
}))
sheet.addAction(UIAlertAction.init(title: "SavedPhotosAlbum", style: .Default, handler: { _ in
self.showPhotoes(.SavedPhotosAlbum)
}))
sheet.addAction(UIAlertAction.init(title: "Cancel", style: .Cancel, handler: nil))
presentViewController(sheet, animated: true, completion: nil)
}
1
2
3
4
5
6
7
8
func showPhotoes(source: UIImagePickerControllerSourceType) {
let controller = UIImagePickerController()
controller.delegate = self
controller.sourceType = source
controller.allowsEditing = source == .SavedPhotosAlbum ? true:false

self.presentViewController(controller, animated: true, completion: nil)
}

UIImagePickerController的代理

1
2
3
4
5
6
7
8
func imagePickerControllerDidCancel(picker: UIImagePickerController) {
dismissViewControllerAnimated(true, completion: nil)
}
// 非常坑,这个方法废弃了但代码提示只有它
// func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
// }
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
}

info字典介绍
1
2
3
4
5
6
7
8
9
10
UIImagePickerControllerMediaType: String
UIImagePickerControllerOriginalImage: UIImage
UIImagePickerControllerEditedImage: UIImage
UIImagePickerControllerCropRect: NSValue -> CGRect
// MediaURL只为视频提供
UIImagePickerControllerMediaURL: NSURL
// LivePhoto是一张图片,保留那个moment的前后动作和声音
UIImagePickerControllerLivePhoto: String
// 摄像摄影时返回media的信息字典
UIImagePickerControllerMediaMetadata: NSDictionary
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// SavedPhotosAlbum 的 info 示例

▿ 5 elements
▿ [0] : 2 elements
- .0 : "UIImagePickerControllerCropRect"
▿ [1] : 2 elements

- .0 : "UIImagePickerControllerOriginalImage"
▿ [2] : 2 elements

- .0 : "UIImagePickerControllerReferenceURL"
- .1 : assets-library://asset/asset.JPG?id=99D53A1F-FEEF-40E1-8BB3-7DD55A43C8B7&ext=JPG
▿ [3] : 2 elements

- .0 : "UIImagePickerControllerMediaType"
- .1 : public.image
▿ [4] : 2 elements

- .0 : "UIImagePickerControllerEditedImage"

自定义

Alt text

遍历相册的所有图片

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
// AssetsLibrary.framework
// ALAssetsLibrary 的使用,但是它慢慢的被放弃了
func loadLocalPhotoes(){
var countOne = 0
//ALAssetsGroupSavedPhotos表示只读取相机胶卷(ALAssetsGroupAll则读取全部相簿)
assetsLibrary.enumerateGroupsWithTypes(ALAssetsGroupSavedPhotos, usingBlock: {
(group: ALAssetsGroup!, stop) in
print("is goin")
if group != nil {
let assetBlock : ALAssetsGroupEnumerationResultsBlock = {
(result: ALAsset!, index: Int, stop) in
if result != nil {
self.assets.append(result)
countOne++
}
}
group.enumerateAssetsUsingBlock(assetBlock)
print("assets:\(countOne)")
self.startChangeLocalImages(0)
}
}, failureBlock: { (fail) in
print(fail)
})
}

// 展现本地图片
func startChangeLocalImages(var index: Int){
if index==assets.count {
index = 0
}
let myAsset = assets[index]
let image = UIImage(CGImage:myAsset.thumbnail().takeUnretainedValue())
self.backImageView.image = image

let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))
dispatch_after(popTime, dispatch_get_main_queue()) {
self.startChangeLocalImages(index+1)
}
}

iOS9 开始使用新库
PHPhotoLibrary

周精益分享 - 英语

参与开源提高英语读写 - 王胜

阅读能力

  • 泡开源论坛,查看讨论
  • 阅读源代码包括注解、代码提交说明

前期泡论坛,看注解、提交说明时需耐下性子,借助翻译工具。时间长了,久而久之,词汇量就上升了。

书写能力

  • 试图修复bug或者提交建议
  • 尝试翻译开源软件的使用手册

经历了阅读阶段的词汇量的积累,可以再通过书写来巩固词汇量的运用。此阶段要拥有儿童学语言的勇气,不要担心自己写错,被其他参与开源的人看到笑话。

MOOC 上手 - 曾铭

Massive Open Online Courses 大型开放式网络课程

  • Coursera
  • Udacity
  • edX
  • 中国大学MOOC

上手感受

  • 视频教程 vs 正规课程
  • 视频教程:目标不清晰、散漫不专注、孤单、没有回顾总结,易放弃(遗忘、注意力转移)
  • 正规课程:目标清晰(结课证)、专注(每周测验)、作业互评、论坛讨论,易放弃(压力)
  • 专业教育的未来(非基本教育)

建议

  • 根据兴趣上手,不必贪多,先坚持完成 1 门课(4-6h/w 并不容易)
  • 只对视频教程感兴趣?为网易公开课点个赞

dispatch_sync的坑 - 潘君

  • 官方文档

    1
    2
    3
    4
    5
    6
    dispatch_sync
    Submits a block object for execution on a dispatch queue and waits until that block completes.

    。。。。。

    As an optimization, this function invokes the block on the current thread when possible.
  • dispatch_sync 卡页面, 原因见官方说明最后一句,优化搞的鬼

    1
    (删除操作) dispatch_sync
  • 队列分为两种,一种是Serial Dispatch Queue,还有一种是Concurrent Dispatch Queue。

  • xun核心模块决定线程数

参考资料:

英语,程序员

网站

  • iTalki

    语言交换及专业老师订课辅导的网站

  • YouTube一个订阅号

    比较有吸引力的老师,他的同事频道也很不错

  • rayWenderlich

    主要是iOS方面的,安卓覆盖一些。涵盖文字、视频、播客三方面

APP

  • 飞鱼口语

    国内开发的一个及时练习口语的APP,主要是方便

  • 每日英语听力(欧陆词典推出的)

    radio 模块很不错,资源不算多但比较有质量。

  • Podcast

    • 圆桌会议(RoundTable)
    • rayWenderlich (程序员职业发展及新技术点探讨,英式口语)

工具

  • Skype 和志同道合的人交流,练习口语

EF Education

  • 网站地址

    想小试牛刀的记得想我拿一下账号密码

周精益分享-谈谈未来、梦想、职业规划

实现时间自由 - 王胜

  • 近期技术打工、为公司技术出一份力
  • 伺机合伙创业
    • 自己作为技术的Partner
    • 需求靠谱的产品经理合伙创业
    • 如果有靠谱的市场/资方人事的合伙人就更完美了
  • 财富自由和时间自由后
    • 周游开拓视野
    • 做一些自己感兴趣的东西,不求有市场,只求自己感觉有价值,好玩就行

品质生活 - 吴明

  • 短期目标
    • 学习和开发使用Material Design
  • 长期目标:以下目标之一
    • 技术专家
    • 管理和技术
    • 换行业
  • 第二职业
    • 除工作之外培养第二职业
    • 第二职业主要兴趣爱好方向
    • 不求工资和市场价值高低,只追求兴趣快乐

兴趣主导,快乐很重要 - 杨志平

  • 兴趣

    • 在失去当前工作兴趣之前,不会转行
    • 职业规划随兴趣走
  • 肉身翻墙

    • 出去走一遭,趁年轻(工作或打工旅行)
    • 去牛逼公司镀金
  • 终极目标

    • 年轻时,向往自由工作者的工作方式,能够养活自己的游行工作方式
    • 适当年纪收心回归平常心(保持感兴趣的工作即可)

一步一个脚印,顺其自然-王进

  • 近期目标,一步一个脚印,不断提高自己

    • 做好现在工作,打下坚实的基础
    • 专注于技术,近期不考虑创业,转行等
    • 顺其自然,当未来出现创业等机会,自己也准备好了,也不错过
  • 未来愿景:

    • 看好移动端的未来发展,以会后向智能家居,物联网方向发展
    • 最终希望能在未来能有时间与机会出去走走

周精益分享-设计规范交流

Label的一些使用规范 - 杨志平

动态适应字体大小

iOS系统自带的常见字体样式(字号及粗细等等)

  • UIFontTextStyleHeadline
  • UIFontTextStyleSubheadline
  • UIFontTextStyleBody
  • UIFontTextStyleFootnote
  • UIFontTextStyleCaption1
  • UIFontTextStyleCaption2

效果如下:

Font

代码

1
2
3
4
5
6
7
8
// 字体初始化
UIFont *font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];

// 通知需要刷新的文本字体
[[NSNotificationCenter defaultCenter]addObserver:self
selector:@selector(userTextSizeDidChange)
name:UIContentSizeCategoryDidChangeNotification
object:nil];

国际化支持(适配注意点)

工具:genstrings

参考:博客链接

1
2
3
先看看它的宏定义:
#define NSLocalizedString(key, comment)
[[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]

本地化对象:标签和按钮上的文本,或者在运行时获取的字符串和数据动态生成的字符串。

难点

不同语言的语法问题

语序,人称不一致:
「Paul invited you」和「You invited Paul」 -> 「%@ invited %@」,看似可以
以德语为例,「Paul invited you」译为「Paul hat dich eingeladen」,
而「You invited Paul」则译为「Du hast Paul eingeladen」。
正确处理其他语言的特殊语法方案:「%@ invited you」和「You invited %@」。

一词多意

1
2
3
4
5
6
7
8
run -> 
vt. & vi. 跑

移动
(使)流动
n. 跑, 奔跑
旅行, 旅程
行驶路线
时期; 一段时间

正确做法:

1
2
NSLocalizedString(@"activity-profile.title.the-run", nil)  
NSLocalizedString(@"home.button.start-run", nil)

或者:(完全没有试过)

NSLocalizedString 有一些变体能够提供更多字符串本地化的操作方式。NSLocalizedStringFromTable 接收 key、table 和 comment 这三个参数,其中 table 参数表示该字符串对应的一个表格,genstrings 会为表中的每一个条目生成一个以条目名称(假设为 table-item)命名的独立字符串文件 table-item.strings。
这样你就可以把字符串文件分割成几个小一些的文件。在一个庞大的项目或者团队中工作时,这一点显得尤为重要。同时这也让合并原有的和重新生成的字符串文件变得容易一些。

1
NSLocalizedStringFromTable(@"home.button.start-run", @"ActivityTracker", @"some comment..")

调试本地化字符串(拓展)

应用支持的语言版本越多,确保所有元素都正确显示就越难。但是这里有一些默认的用户选项和工具可以减轻你的负担。

NSDoubleLocalizedStrings
AppleTextDirection
NSForceRightToLeftWritingDirection
选项保证你的布局不会因为长字符串或者从右往左读的语言而混乱。

NSShowNonLocalizedStrings
NSShowNonLocalizableStrings
则可以帮助你找到没有翻译的字符串和根本没有制定字符串本地化宏的字符串。

单位和度量 -吴明

Android基本单位规范 -吴明

单位和度量
  • 像素密度
    • 每英寸的像素数被称为“像素密度”。
    • DPI =屏幕宽度(或高度)像素/屏幕宽度(或高度)英寸
    • mdpi-160dpi,
      hdpi-480x800-240dpi,
      xhdpi-720x1280-320dpi,
      xxhdpi-1080x1920-480dpi
    • mipmap-hdpi,
      drawable-hdpi区别:用mipmap会缩放图片并优化性能。
    • MacDown Screenshot
  • 密度独立像素
    • “密度独立”是指在屏幕上用不同的密度来统一的显示用户界面元素
    • MacDown Screenshot
    • 1dp 和 160 dpi 屏幕的一个物理像素相等。计算 dp 的方法:
      dp =(宽度像素160)/ dpi**
  • 可扩展像素(sp)

    • 在为安卓系统开发程序时,可扩展的像素(SP)提供和DP一样的功能,但只是在字体上。一个 SP 的默认值和 DP 上的默认值一样。
    • density==scaledDensity,即1sp=1dp,
    • 还是采用dp替代sp,为什么

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      	public static float applyDimension(int unit, float value,DisplayMetrics metrics)
      {

      switch (unit) {
      case COMPLEX_UNIT_PX:
      return value;
      case COMPLEX_UNIT_DIP:
      return value * metrics.density;
      case COMPLEX_UNIT_SP:
      return value * metrics.scaledDensity;
      case COMPLEX_UNIT_PT:
      return value * metrics.xdpi * (1.0f/72);
      case COMPLEX_UNIT_IN:
      return value * metrics.xdpi;
      case COMPLEX_UNIT_MM:
      return value * metrics.xdpi * (1.0f/25.4f);
      }
      return 0;
      }
  • pt(磅),标准的长度单位,1pt=1/72英寸,用于印刷业,买蛋糕的时候挺说最多

  • em,em指字体高,1em=16px,任意浏览器的默认字体高都是16px.
字体排版
  • 字体排版的缩放和基本样式
    • 同时使用过多的字体尺寸和样式可以很轻易的毁掉布局,最基本的样式集合就是基于 12、14、16、20 和 34 号的字体排版缩放,以下Google建议字体尺寸:
      MacDown Screenshot
  • 基本色/色彩对比度
    • 最基本的常识是,相同颜色的背景和文字是很难阅读的。但有些人不知道的是,带有过强对比度的文本会有些炫目,同样难以阅读。
    • 文本应当满足对比度7:1的是最适合阅读,最低的对比度4.5:1(依据明度计算),
    • Google对比度建议:MacDown Screenshot
  • 行高
    • 只有“主体”、“次要标题”、“大纲”等类似的样式中才允许使用自动换行。其它所有样式应当以单行形式出现。
    • MacDown Screenshot
    • For all styles, line height is 0.1em larger than the English-like languages. English and English-like languages mostly use a portion of the em box, often the lower portion below the x-height. Chinese, Japanese, and Korean (CJK) ideographic characters use the entire em box. Characters in tall languages often have long descenders and/or ascenders. To achieve the same design intention as English for CJK and to avoid potential text clipping between two lines next to each other for tall languages, the line height needs to be larger than in English for tall and dense languages.
  • 换行规则/连字符
    • 正确:
      MacDown Screenshot
    • 错误
      MacDown Screenshot
  • 每行字符

    • 每行应当保持在50-60个字符左右
    • 太长,用户的眼睛将难以找到在文本上对焦。这是因为过长的文字导致用户难以判断一行的起始点,甚至在大段文字中出现读错行的现象
    • 太短,会导致眼睛来回扫视过于频繁,破坏阅读的节奏。过短的内容还会给人压力,导致用户完成本行阅读前过早跳转到下一行阅读(因此会错过潜在的重要信息)
    • MacDown Screenshot

      • MacDown Screenshot
  • 字间距
    • MacDown Screenshot

设计规范之我见 - 张超耀

设计规范阅读心得 - 潘君

官方设计文档

参考资料:[ISUX转译]iOS 8人机界面指南(一):UI设计基础

  • 突出内容
  • 统一
  • 为变化做好准备
    • 压缩
    • 拉伸
  • 充足的互动空间
  • 路径合理
  • 注意层级
  • 使用标准的手势
  • 页面切换过渡需要有焦点
  • 反馈要准确

案例

参考资料:向用户征询iOS授权的五种常见设计模式

设计规范之奇妙的Z轴- 王进

Z轴组成:

z=elevation+translationZ;

  • eleavation是静态的成员,静止高度,是不会变化的。当一个对象的高度产生变化时,它将会尽快恢复到自身的静止高度。

translationZ是用来做动画。动态高度偏移

  • 在layout中使用 android:elevation属性去定义

在代码中使用 View.setElevation 方法去定义

  • 在layout中使用 android:translationz属性去定义设置视图的translation,可以使用View.setTranslationZ方法

  • 新的属性值:translationZ允许你创建一个动画暂时的反应出View的高度值(elevation)变化。

这对于响应触摸手势很有用处:

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

int action = motionEvent.getActionMasked();

/* Raise view on ACTION_DOWN and lower it on ACTION_UP. */

switch (action) {

case MotionEvent.ACTION_DOWN:

Log.d(TAG, "ACTION_DOWN on view.");

view.setTranslationZ(120);

break;

case MotionEvent.ACTION_UP:

Log.d(TAG, "ACTION_UP on view.");

view.setTranslationZ(0);

break;

default:

return false;

}

系统高度参考

高度特性:

  • “高度”的度量单位与 XY 轴的度量单位相同,主要是 DP。由于所有 Material 元素都具有 1 单位 DP 的厚度,所以“高度”度量的是从一个平面顶部到另一个平面顶部的距离。

  • 一个子对象的高度与其父对象的高度相关

阴影

“阴影”提供了对象深度和方向性移动的重要视觉线索。它们是唯一一种标示不同平面之间分离程度的视觉线索。某一对象的“高度”决定了其具体“阴影”的表现形式。

  • Z 轴影响阴影效果

z轴越大,阴影越明显,

  • Z轴影响View 的绘制顺序:

在同一个父view内部,z轴越小,绘制越早,z轴后绘制的会覆盖先前绘制的,只有Z轴相同,才按照添加循序绘制

  • Z轴属性会扩大view的显示区域,如果他的大小≥父控件大小,他的阴影效果就无法显示

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

<TextView

android:layout_marginTop="20dp"

android:layout_marginBottom="20dp"

android:gravity="center"

android:text="2dp"

android:textColor="#88000000"

android:layout_width="150dp"

android:layout_height="100dp"

android:elevation="2dp"

android:background="#ffffff"/>



<TextView

android:layout_marginTop="20dp"

android:layout_marginBottom="20dp"

android:layout_marginLeft="20dp"

android:gravity="center"

android:text="4dp"

android:textColor="#88000000"

android:layout_width="150dp"

android:layout_height="100dp"

android:elevation="4dp"

android:background="#ffffff"/>
  • 使用直接使用图片(除非shape),设置z轴,阴影效果不会显示,必须通过代码viewOutlineProvider指明轮廓

    • 在layout中使用android:outlineProvider属性

    none 即使设置里z轴也不会显示阴影

    background 按照background 设置背景

    bounds 按照view 大小设置背景

    paddedbounds 与bounds 类似,不过背景向右偏些

  • 代码设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

ViewOutlineProvider viewOutlineProvider = new ViewOutlineProvider() {

@Override

public void getOutline(View view, Outline outline)

//可以指定圆形,矩形,圆角矩形,path

outline.setOval(0, 0, view.getWidth(), view.getHeight());

}

};

fab.setOutlineProvider(viewOutlineProvider);?

两层阴影

  • 这两层阴影, 一层是环境光投射的阴影, 另一层是关键光照射在 material 上投射的阴影.这两种光线叠加在一起, 才能构成 material design 中的阴影.

  • 而理论上, 这两种阴影都应该是有平滑曲线的, 但是由于手机处理器性能问题, 实时计算平滑后的阴影会导致可怕的耗电与发热, 所以 Google 只能在这方面妥协, 将阴影曲线拉直, 直接导致某些阴影效果变差.通过叠加可以模拟真实的曲线阴影。

链接:http://www.zhihu.com/question/28865209/answer/42749018

周精益分享-自由主题

浅拷贝与深拷贝 - 杨志平

程序中经常会遇到集合类的传值

坑: 数组操作时对于数组中的对象拷贝

目的:

观察array1、mArrayCopy、mArrayCopy2 三者区别

1
2
3
NSArray *array1 = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *mArrayCopy = [[NSArray alloc] initWithArray:array1 copyItems:YES];
NSArray* mArrayCopy2 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject: array1]];

首先了解copy与retain的区别

OC对象引用计数器:屋里开灯规则

copy是创建一个新对象,retain是创建一个指针,引用对象计数加1。Copy属性表示两个对象内容相同,新的对象retain为1 ,与旧有对象的引用计数无关,旧有对象没有变化。

retain属性表示两个对象地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain值+1也就是说,retain 是指针拷贝,copy 是内容拷贝。

主角:copy、mutableCopy

注意:可变和不可变对象使用copy、mutableCopy的区别

遵守NSCopying 协议的类可以发送copy消息,
遵守NSMutableCopying 协议的类才可以发送mutableCopy消息。

默认的ios类并没有遵守这两个协议
自定义copy 必须遵守NSCopying,并且实现 copyWithZone: 方法
自定义mutableCopy 那么就必须遵守NSMutableCopying,并且实现 mutableCopyWithZone: 方法

实例

系统的非容器类对象

这里指的是NSString,NSNumber等等一类的对象。

示例1:
不可变string

1
2
3
4
5
6
7
8
9
10
NSString *string = @"origionStr";
NSString *string2 = string;
NSString *stringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];
[stringMCopy appendString:@"!!!!"];
// log
NSLog(@"string = %p %p %@ ",string,&string,string);
NSLog(@"string2 = %p %p %@ ",string2,&string2,string2);
NSLog(@"stringCopy = %p %p %@ ",stringCopy,&stringCopy,stringCopy);
NSLog(@"stringMCopy = %p %p %@ ",stringMCopy,&stringMCopy,stringMCopy);

结果:

1
2
3
4
string      = 0x1064e7088		0x7fff597190a0 origionStr 
string2 = 0x1064e7088 0x7fff59719098 origionStr
stringCopy = 0x1064e7088 0x7fff59719090 origionStr
stringMCopy = 0x7fafebdbb4b0 0x7fff59719088 origionStr!!!!

示例2:
可变string

1
2
3
4
5
6
7
8
9
10
NSMutableString *string = [NSMutableString stringWithString: @"origion"];
NSString *stringCopy = [string copy];
NSMutableString *mStringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];
[string appendString:@" origion!"];
[stringMCopy appendString:@"!!"];
NSLog(@"string = %p %@",string,string);
NSLog(@"stringCopy = %p %@",stringCopy,stringCopy);
NSLog(@"stringMCopy = %p %@",mStringCopy,mStringCopy);
NSLog(@"stringMCopy = %p %@",stringMCopy,stringMCopy);

结果:

1
2
3
4
string      = 0x7fafebdec820 origion origion!
stringCopy = 0x7fafebdcb8c0 origion
stringMCopy = 0x7fafebda4320 origion
stringMCopy = 0x7fafebd32890 origion!!

对于系统的非容器类对象,我们可以认为,如果对一不可变对象复制,copy是指针复制(浅拷贝)和mutableCopy就是对象复制(深拷贝)。如果是对可变对象复制,都是深拷贝,但是copy返回的对象是不可变的。

系统的容器类对象

指NSArray,NSSet,NSDictionary等。对于容器类本身,上面讨论的结论也是适用的,需要探讨的是复制后容器内对象的变化。

示例1:
不可变数组

1
2
3
4
5
6
7
8
9
10
//copy返回不可变对象,mutablecopy返回可变对象
NSArray *array1 = [NSArray arrayWithObjects:@"a",@"b",@"c",nil];
NSArray *arrayCopy1 = [array1 copy];
NSMutableArray *mArrayCopy1 = [array1 mutableCopy];
[mArrayCopy1 addObject:@"de"];
[mArrayCopy1 removeObjectAtIndex:0];
// log
NSLog(@"array1 = %p %@ %p %@",array1,array1,array1[1],array1[1]);
NSLog(@"arrayCopy1 = %p %@ %p %@",arrayCopy1,arrayCopy1,arrayCopy1[1],arrayCopy1[1]);
NSLog(@"mArrayCopy1 = %p %@ %p %@",mArrayCopy1,mArrayCopy1,mArrayCopy1[0],mArrayCopy1[0]);

结果:

1
2
3
array1      = 0x7fafebdeaed0 (a,b,c) 	0x1064e7228 b
arrayCopy1 = 0x7fafebdeaed0 (a,b,c) 0x1064e7228 b
mArrayCopy1 = 0x7fafebdfc010 (b,c,de) 0x1064e7228 b

示例:
可变数组

1
2
3
4
NSArray *array1 = [NSArray arrayWithObjects:@"a",@"b",@"c",nil];
NSMutableArray *mArrayCopy = [[NSMutableArray alloc] initWithArray:array1 copyItems:YES];
NSLog(@"array1 = %p %@ %p %@ %p %@",array1, array1, array1[0], array1[0], array1[1], array1[1]);
NSLog(@"arrayCopy1 = %p %@ %p %@ %p %@",mArrayCopy,mArrayCopy, mArrayCopy[0], mArrayCopy[0], mArrayCopy[1], mArrayCopy[1]);

结果:

1
2
array1      = 0x7fafebcb5ae0 (a,b,c)	 0x10afe1208 a 0x10afe1228 b
arrayCopy1 = 0x7fafebcb5b10 (a,b,c) 0x10afe1208 a 0x10afe1228 b

示例2:

1
2
3
NSArray *array1 = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSMutableArray *mArrayCopy = [[NSMutableArray alloc] initWithArray:array1 copyItems:YES];
NSArray* mArrayCopy2 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject: array1]];

mArrayCopy2是完全意义上的深拷贝,而mArrayCopy则不是

对于mArrayCopy内的不可变元素其还是指针复制。或者我们自己实现深拷贝的方法。因为如果容器的某一元素是不可变的,那你复制完后该对象仍旧是不能改变的,因此只需要指针复制即可。除非你对容器内的元素重新赋值,否则指针复制即已足够。

APP标注工具 - 王进

tracking-sdk之FMDB & common字段简单分享 - 张超耀

FMDB

  • cocoapods管理(优先考虑)
  • libsqlite3.dylib函数库支持
  • 怎删改 & 查简单实用
  • object转string时有转义字符,需要客户端进一步处理

common字段

  • IP & 经纬度获取(不经意间的惊喜)IP查询
  • 其他字段(略)

获取经纬度遇到的问题

  • 经纬度获取是异步,可能在实时数据上传前还没有获取到,所以暂时取巧用IP解析的地址作为经纬度处理。

tracking-sdk 分享 - 潘君

正则表达式入门 - 曾铭

目标

  • 原理我也不懂,但会用(知道能解决什么问题)能看懂(知道在解决什么问题)很重要
  • 高效
  • 碰到问题多”玩”, 玩玩就会了

罗列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
\b 单词分割
\d 数字
\s 空白符
\w 字母、数字、下划线、汉字
\B \D \S \W
[^xyz] 非 xyz
[abc0-9] 字符范围
^ 起始
$ 结束
{n} 重复 n
{n,} 重复 n 到无穷次
{n,m} 重复 nm
* 重复 0 到无穷次
+ 重复 1 到无穷次
? 重复 0 或 1 次
| 或
\\ 字符转义
(exp) 捕获
(?<name>exp) 捕获并命名
(?:exp) 不捕获
(?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp后面的位置
(?!exp) 匹配后面跟的不是exp的位置
(?<!exp) 匹配前面不是exp的位置

推荐工具

参考

那些年我们用过的第三方库

SnapKit(Masonry) 的使用 - 杨志平

这两个库的用法都是差不多的,只是由两个不同的人来主导开源

SnapKit是Swift版

Masonry是OC版

自动布局及交互式编程是iOS开发的趋势,同时Swift也会在不久将来替换OC语言。所以现在的iOS开发者可以开始学习Swift2.0 以及应用 Autolayout 来编程

代码对比(概况了解)

开始前OC原生布局代码
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

UIView *superview = self;

UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[superview addConstraints:@[

//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],

[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],

[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],

[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],
]];
使用Masonry

精简

1
2
3
4
5
6
7
8
9

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(- padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];

更加精简

1
2
3
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}]
;

同理使用SnapKit

精简

1
2
3
4
5
6
7
8
9
10

let box = UIView()
superview.addSubview(box)

box.snp_makeConstraints { (make) -> Void in
make.top.equalTo(superview).offset(20)
make.left.equalTo(superview).offset(20)
make.bottom.equalTo(superview).offset(-20)
make.right.equalTo(superview).offset(-20)
}

更加精简

1
2
3
box.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(superview).inset(UIEdgeInsetsMake(20, 20, 20, 20))
}

如何使用 && 原理

常见的约束类型对比

ViewAttribute NSLayoutAttribute
view.snp_left NSLayoutAttribute.Left
view.snp_right NSLayoutAttribute.Right
view.snp_top NSLayoutAttribute.Top
view.snp_bottom NSLayoutAttribute.Bottom
view.snp_leading NSLayoutAttribute.Leading
view.snp_trailing NSLayoutAttribute.Trailing
view.snp_width NSLayoutAttribute.Width
view.snp_height NSLayoutAttribute.Height
view.snp_centerX NSLayoutAttribute.CenterX
view.snp_centerY NSLayoutAttribute.CenterY
view.snp_baseline NSLayoutAttribute.Baseline

常见的用法

1
2
3
4
5
6
7
8
9
10
make.top.equalTo(42)
make.lessThanOrEqualTo.equalTo(SuperView)
make.top.equalTo(SuperView)
make.size.equalTo(CGSizeMake(50, 100))
make.edges.equalTo(UIEdgeInsetsMake(10, 0, 10, 0))
make.left.equalTo(view).offset(UIEdgeInsetsMake(10, 0, 10, 0))

make.height.equalTo(OtherView).offset(10)
make.trailing.equalTo(OtherView.snp_trailing).offset(10)
make.bottom.equalTo(-20).priority(250)

对比交互式编程的约束布局

image


JSONModel for swift 的探索 - 曾铭

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
// 可在 Playground 中尝试
import Foundation
import UIKit


// 基本的类型转换
let a:String = ""
let b = ""
var c:String? = nil
c = "test"

let d:String = c as String!
var e = Int(c!)
e = Int("123")

var f = Int("123")!

print("a type:\(a.dynamicType)")
print("b type:\(b.dynamicType)")
print("c type:\(c.dynamicType)")
print("d type:\(d.dynamicType)")
print("e type:\(e.dynamicType)")
print("f type:\(f.dynamicType)\n")


// KVC for NSObject

class FooKVC : NSObject {
var p1 : String = "s"
}

let fk1 = FooKVC()

fk1.p1

fk1.setValue("y", forKey: "p1")

fk1.p1

// reflect for swift2.0

class A
{

var name = "namevalue"
var age = 123
var some:(String, Int) = ("ming", 2)
}

let a1 = A()

a1.age = 456

a1.age


let r = Mirror(reflecting: a1)
for c in r.children {
print(c.label.dynamicType)
print(c.label)
print(c.value.dynamicType)
print(c.value)
print("===")
}
  • KVO for NSObject 略, 注意监听者与被监听者都要是 NSObject 子类

JSONModel for swift?

>
NB: Swift works in a different way under the hood than Objective-C. Therefore I can’t find a way to re-create JSONModel in Swift. JSONModel in Objective-C works in Swift apps through CocoaPods or as an imported Objective-C library.

JSONModel 做的事情

  • String(NSData) -> Dictionary(Array)
  • Dictionary(Array) -> Model-Object

对应实现

参考链接


RATreeView的简单使用 - 张超耀

RATreeView:实现树形结构的TableView;点击每个cell,都可以展开出现新的cell;可以自定义树的层数

主要方法介绍

  • cell的高度
    -(CGFloat)treeView:(RATreeView *)treeView heightForRowForItem:(id)item treeNodeInfo:(RATreeNodeInfo *)treeNodeInfo
  • 这个函数决定是否可以展开,通过设定我们可以设置哪些单元格可以展开到下一层,哪些不可以展开
    - (BOOL)treeView:(RATreeView *)treeView shouldExpandItem:(id)item treeNodeInfo:(RATreeNodeInfo *)treeNodeInfo
  • 这个看字面意识就理解了,在单元格显示之前(或者说将要显示时)我们可以做些设置,这里是设置相应深度的颜色背景
    - (void)treeView:(RATreeView *)treeView willDisplayCell:(UITableViewCell *)cell forItem:(id)item treeNodeInfo:(RATreeNodeInfo *)treeNodeInfo

  • 这里就是我们最熟悉的点击cell处理函数,看代码慢慢体会treeNodeInfo的使用,不想多说了
    -(void)treeView:(RATreeView *)treeView didSelectRowForItem:(id)item treeNodeInfo:(RATreeNodeInfo *)treeNodeInfo

  • 数据源处理,相当于UITableViewCell处理,关键还是理解treeNodeInfo概念
    - (UITableViewCell *)treeView:(RATreeView *)treeView cellForItem:(id)item treeNodeInfo:(RATreeNodeInfo *)treeNodeInfo
  • 返回每一层包含成员的个数,来制表
    - (NSInteger)treeView:(RATreeView *)treeView numberOfChildrenOfItem:(id)item
  • 返回cell对象
    - (id)treeView:(RATreeView *)treeView child:(NSInteger)index ofItem:(id)item

    RATreeView - github


Facebook 开源的图片加载库Fresco - 王胜

Fresco诞生背景

为提高Android中图片的加载速度,一般的图片库都采用了三级缓存:Memory Cache、Disk Cache和Network。但是,Android的系统层是将物理内存平均分配给每一个App。这样每个App所分配的空间都是有限的,早起的android设备,每个App只被分配16MB空间,这样,如果App中使用大量的图片,那么很容易因OOM而Crashes掉。Facebook App正是大量使用图片的App,面临这个问题刻不容缓,所以他们历尽艰难,开发了Fresco图片加载库。

Fresco诞生过程

内存区域分析:

  • Java heap

    每个厂商会为App分配一个固定尺寸的运行空间。所有的申请通过Java的new操作申请,操作相对安全,通过GC内存自动回收保证内存不被泄露。但不幸的时,GC不够精确化,回收得不够及时。因此还是会存在OOM。

  • Native heap

    通过C或者C++可绕过Java虚拟机直接操作物理内存,但Java程序员习惯了GC的自动回收,很难操作C++的手动操作内存。

  • Ashmen

    Android还有一块内存区域,叫Ashmen。这里的操作很像Nativew heap,但是这里是系统调用的。Java 应用程序是不能直接访问Ashmen的,但是一些例外的情况可以操作,图片就是一种例外。

    1
    2
    3
    BitmapFactory.Options = new BitmapFactory.Options();
    options.inPurgeable = true;
    Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);

难点突破:

尽管发现了Purgeable bitmaps,但是这个解码的过程是在UI线程操作的,因此他们又采用了异步实现,并保证了UI线程不引用时,unpin的区域不会被释放。

上层构建

提供给上层调用时,采用了MVC的架构:

  • Model:DraweeHierarchy
  • Control:DraweeControllers
  • View:DraweeViews

使用示例

  1. gradle配置中添加库引用

    1
    compile 'com.facebook.fresco:fresco:0.6.1+'
  2. xml中添加组件

    1
    2
    3
    4
    5
    6
    7
    <com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/sdv"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    fresco:roundAsCircle="true"
    fresco:roundingBorderWidth="1dp"
    fresco:roundingBorderColor="#00ff00"

  3. 代码中指定图片地址

    1
    2
    SimpleDraweeView sdv = (SimpleDraweeView) findViewById(R.id.sdv);
    sdv.setImageURI(Uri.parse("http://g.hiphotos.baidu.com/image/pic/item/2e2eb9389b504fc2b351980be7dde71190ef6db5.jpg"));

参考资料:


SIAlertView 阅读 - 潘君

  • 创建自定义alert view的流程

自定义window->添加自定义view controller->定制view->view引用window

  • @class
    1
    @class SIAlertView;

能不使用import的就不使用
用@class代替

  • 通知 和 Block

  • 层级

    1
    2
    const UIWindowLevel UIWindowLevelSIAlert = 1996.0;  // don't overlap system's alert
    const UIWindowLevel UIWindowLevelSIAlertBackground = 1985.0; // below the alert window
  • UIViewTintAdjustmentMode
    通过获取keyWindow的UIViewTintAdjustmentMode来设置alertWindow的
    该属性能够设置tint的调整模式

    1
    2
    3
    4
    5
    6
    7
    typedef enum {
    // 和父视图的一样
    UIViewTintAdjustmentModeAutomatic,
    // 不对tintColor做任何修改
    UIViewTintAdjustmentModeNormal,
    // 在原有tintColor基础上变暗
    UIViewTintAdjustmentModeDimmed,}UIViewTintAdjustmentMode;
  • iOS特有版本代码

    1
    2
    3
    4
    #ifdef __IPHONE_7_0
    //some code
    #endif
    此处填写iOS7才能运行的代码
  • initialize

    1
    2
    3
    4
    5
    6
    7
    8
    9
    + (void)initialize
    {
    if (self != [SIAlertView class])
    return;

    // 默认值赋值
    }

    一些值放在+(void)initialize;中赋值为默认值,这样不管通过何种方式初始化
  • UIAppearance
    SIAlertView *appearance = [self appearance];
    UIView符合UIAppearence协议,能够全局修改所有实例的UI

  • 调用Bundle中资源
    [UIImage imageNamed:@”SIAlertView.bundle/button-default”]
    bundle中资源的调用方法

  • _cmd
    oc特有的方法,无法通过c语言获取

  • [self invalidateLayout]

  • 合理利用循环引用

1
2
3
4
5
6
7
8
if (!self.alertWindow) {
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
window.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
window.opaque = NO;
window.windowLevel = UIWindowLevelSIAlert;
window.rootViewController = viewController;
self.alertWindow = window;
}
  • oldKeyWindow
    通知alert后面的视图转变方向
    取用一些oldKeyWindow的值

android饼图库 -吴明


HeaderFooterRecyclerViewAdapter——李仙鹏

HeaderFooterRecyclerViewAdapter,用于RecyclerView。可方便的添加header或者footer。使用非常简单,只需要按照实际需求,在对应的header、content、footer相关方法进行重写即可。

附上源码

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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
public abstract class HeaderFooterRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private static final int VIEW_TYPE_MAX_COUNT = 1000;
private static final int HEADER_VIEW_TYPE_OFFSET = 0;
private static final int FOOTER_VIEW_TYPE_OFFSET = HEADER_VIEW_TYPE_OFFSET + VIEW_TYPE_MAX_COUNT;
private static final int CONTENT_VIEW_TYPE_OFFSET = FOOTER_VIEW_TYPE_OFFSET + VIEW_TYPE_MAX_COUNT;

private int headerItemCount;
private int contentItemCount;
private int footerItemCount;

/**
* {@inheritDoc}
*/

@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

// Delegate to proper methods based on the viewType ranges.
if (viewType >= HEADER_VIEW_TYPE_OFFSET && viewType < HEADER_VIEW_TYPE_OFFSET + VIEW_TYPE_MAX_COUNT) {
return onCreateHeaderItemViewHolder(parent, viewType - HEADER_VIEW_TYPE_OFFSET);
} else if (viewType >= FOOTER_VIEW_TYPE_OFFSET && viewType < FOOTER_VIEW_TYPE_OFFSET + VIEW_TYPE_MAX_COUNT) {
return onCreateFooterItemViewHolder(parent, viewType - FOOTER_VIEW_TYPE_OFFSET);
} else if (viewType >= CONTENT_VIEW_TYPE_OFFSET && viewType < CONTENT_VIEW_TYPE_OFFSET + VIEW_TYPE_MAX_COUNT) {
return onCreateContentItemViewHolder(parent, viewType - CONTENT_VIEW_TYPE_OFFSET);
} else {
// This shouldn't happen as we check that the viewType provided by the client is valid.
throw new IllegalStateException();
}
}

/**
* {@inheritDoc}
*/

@Override
public final void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
// Delegate to proper methods based on the viewType ranges.
if (headerItemCount > 0 && position < headerItemCount) {
onBindHeaderItemViewHolder(viewHolder, position);
} else if (contentItemCount > 0 && position - headerItemCount < contentItemCount) {
onBindContentItemViewHolder(viewHolder, position - headerItemCount);
} else {
onBindFooterItemViewHolder(viewHolder, position - headerItemCount - contentItemCount);
}
}

/**
* {@inheritDoc}
*/

@Override
public final int getItemCount() {
// Cache the counts and return the sum of them.
headerItemCount = getHeaderItemCount();
contentItemCount = getContentItemCount();
footerItemCount = getFooterItemCount();
return headerItemCount + contentItemCount + footerItemCount;
}

/**
* {@inheritDoc}
*/

@Override
public final int getItemViewType(int position) {
// Delegate to proper methods based on the position, but validate first.
if (headerItemCount > 0 && position < headerItemCount) {
return validateViewType(getHeaderItemViewType(position)) + HEADER_VIEW_TYPE_OFFSET;
} else if (contentItemCount > 0 && position - headerItemCount < contentItemCount) {
return validateViewType(getContentItemViewType(position - headerItemCount)) + CONTENT_VIEW_TYPE_OFFSET;
} else {
return validateViewType(getFooterItemViewType(position - headerItemCount - contentItemCount)) + FOOTER_VIEW_TYPE_OFFSET;
}
}

/**
* Validates that the view type is within the valid range.
*
* @param viewType the view type.
* @return the given view type.
*/

private int validateViewType(int viewType) {
if (viewType < 0 || viewType >= VIEW_TYPE_MAX_COUNT) {
throw new IllegalStateException("viewType must be between 0 and " + VIEW_TYPE_MAX_COUNT);
}
return viewType;
}

/**
* Notifies that a header item is inserted.
*
* @param position the position of the header item.
*/

public final void notifyHeaderItemInserted(int position) {
int newHeaderItemCount = getHeaderItemCount();
if (position < 0 || position >= newHeaderItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for header items [0 - " + (newHeaderItemCount - 1) + "].");
}
notifyItemInserted(position);
}

/**
* Notifies that multiple header items are inserted.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyHeaderItemRangeInserted(int positionStart, int itemCount) {
int newHeaderItemCount = getHeaderItemCount();
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > newHeaderItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for header items [0 - " + (newHeaderItemCount - 1) + "].");
}
notifyItemRangeInserted(positionStart, itemCount);
}

/**
* Notifies that a header item is changed.
*
* @param position the position.
*/

public final void notifyHeaderItemChanged(int position) {
if (position < 0 || position >= headerItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for header items [0 - " + (headerItemCount - 1) + "].");
}
notifyItemChanged(position);
}

/**
* Notifies that multiple header items are changed.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyHeaderItemRangeChanged(int positionStart, int itemCount) {
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount >= headerItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for header items [0 - " + (headerItemCount - 1) + "].");
}
notifyItemRangeChanged(positionStart, itemCount);
}


/**
* Notifies that an existing header item is moved to another position.
*
* @param fromPosition the original position.
* @param toPosition the new position.
*/

public void notifyHeaderItemMoved(int fromPosition, int toPosition) {
if (fromPosition < 0 || toPosition < 0 || fromPosition >= headerItemCount || toPosition >= headerItemCount) {
throw new IndexOutOfBoundsException("The given fromPosition " + fromPosition + " or toPosition " + toPosition + " is not within the position bounds for header items [0 - " + (headerItemCount - 1) + "].");
}
notifyItemMoved(fromPosition, toPosition);
}

/**
* Notifies that a header item is removed.
*
* @param position the position.
*/

public void notifyHeaderItemRemoved(int position) {
if (position < 0 || position >= headerItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for header items [0 - " + (headerItemCount - 1) + "].");
}
notifyItemRemoved(position);
}

/**
* Notifies that multiple header items are removed.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public void notifyHeaderItemRangeRemoved(int positionStart, int itemCount) {
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > headerItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for header items [0 - " + (headerItemCount - 1) + "].");
}
notifyItemRangeRemoved(positionStart, itemCount);
}

/**
* Notifies that a content item is inserted.
*
* @param position the position of the content item.
*/

public final void notifyContentItemInserted(int position) {
int newHeaderItemCount = getHeaderItemCount();
int newContentItemCount = getContentItemCount();
if (position < 0 || position >= newContentItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for content items [0 - " + (newContentItemCount - 1) + "].");
}
notifyItemInserted(position + newHeaderItemCount);
}

/**
* Notifies that multiple content items are inserted.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyContentItemRangeInserted(int positionStart, int itemCount) {
int newHeaderItemCount = getHeaderItemCount();
int newContentItemCount = getContentItemCount();
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > newContentItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for content items [0 - " + (newContentItemCount - 1) + "].");
}
notifyItemRangeInserted(positionStart + newHeaderItemCount, itemCount);
}

/**
* Notifies that a content item is changed.
*
* @param position the position.
*/

public final void notifyContentItemChanged(int position) {
if (position < 0 || position >= contentItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for content items [0 - " + (contentItemCount - 1) + "].");
}
notifyItemChanged(position + headerItemCount);
}

/**
* Notifies that multiple content items are changed.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyContentItemRangeChanged(int positionStart, int itemCount) {
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > contentItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for content items [0 - " + (contentItemCount - 1) + "].");
}
notifyItemRangeChanged(positionStart + headerItemCount, itemCount);
}

/**
* Notifies that an existing content item is moved to another position.
*
* @param fromPosition the original position.
* @param toPosition the new position.
*/

public final void notifyContentItemMoved(int fromPosition, int toPosition) {
if (fromPosition < 0 || toPosition < 0 || fromPosition >= contentItemCount || toPosition >= contentItemCount) {
throw new IndexOutOfBoundsException("The given fromPosition " + fromPosition + " or toPosition " + toPosition + " is not within the position bounds for content items [0 - " + (contentItemCount - 1) + "].");
}
notifyItemMoved(fromPosition + headerItemCount, toPosition + headerItemCount);
}

/**
* Notifies that a content item is removed.
*
* @param position the position.
*/

public final void notifyContentItemRemoved(int position) {
if (position < 0 || position >= contentItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for content items [0 - " + (contentItemCount - 1) + "].");
}
notifyItemRemoved(position + headerItemCount);
}

/**
* Notifies that multiple content items are removed.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyContentItemRangeRemoved(int positionStart, int itemCount) {
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > contentItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for content items [0 - " + (contentItemCount - 1) + "].");
}
notifyItemRangeRemoved(positionStart + headerItemCount, itemCount);
}

/**
* Notifies that a footer item is inserted.
*
* @param position the position of the content item.
*/

public final void notifyFooterItemInserted(int position) {
int newHeaderItemCount = getHeaderItemCount();
int newContentItemCount = getContentItemCount();
int newFooterItemCount = getFooterItemCount();
if (position < 0 || position >= newFooterItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for footer items [0 - " + (newFooterItemCount - 1) + "].");
}
notifyItemInserted(position + newHeaderItemCount + newContentItemCount);
}

/**
* Notifies that multiple footer items are inserted.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyFooterItemRangeInserted(int positionStart, int itemCount) {
int newHeaderItemCount = getHeaderItemCount();
int newContentItemCount = getContentItemCount();
int newFooterItemCount = getFooterItemCount();
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > newFooterItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for footer items [0 - " + (newFooterItemCount - 1) + "].");
}
notifyItemRangeInserted(positionStart + newHeaderItemCount + newContentItemCount, itemCount);
}

/**
* Notifies that a footer item is changed.
*
* @param position the position.
*/

public final void notifyFooterItemChanged(int position) {
if (position < 0 || position >= footerItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for footer items [0 - " + (footerItemCount - 1) + "].");
}
notifyItemChanged(position + headerItemCount + contentItemCount);
}

/**
* Notifies that multiple footer items are changed.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyFooterItemRangeChanged(int positionStart, int itemCount) {
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > footerItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for footer items [0 - " + (footerItemCount - 1) + "].");
}
notifyItemRangeChanged(positionStart + headerItemCount + contentItemCount, itemCount);
}

/**
* Notifies that an existing footer item is moved to another position.
*
* @param fromPosition the original position.
* @param toPosition the new position.
*/

public final void notifyFooterItemMoved(int fromPosition, int toPosition) {
if (fromPosition < 0 || toPosition < 0 || fromPosition >= footerItemCount || toPosition >= footerItemCount) {
throw new IndexOutOfBoundsException("The given fromPosition " + fromPosition + " or toPosition " + toPosition + " is not within the position bounds for footer items [0 - " + (footerItemCount - 1) + "].");
}
notifyItemMoved(fromPosition + headerItemCount + contentItemCount, toPosition + headerItemCount + contentItemCount);
}

/**
* Notifies that a footer item is removed.
*
* @param position the position.
*/

public final void notifyFooterItemRemoved(int position) {
if (position < 0 || position >= footerItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for footer items [0 - " + (footerItemCount - 1) + "].");
}
notifyItemRemoved(position + headerItemCount + contentItemCount);
}

/**
* Notifies that multiple footer items are removed.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyFooterItemRangeRemoved(int positionStart, int itemCount) {
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > footerItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for footer items [0 - " + (footerItemCount - 1) + "].");
}
notifyItemRangeRemoved(positionStart + headerItemCount + contentItemCount, itemCount);
}

/**
* Gets the header item view type. By default, this method returns 0.
*
* @param position the position.
* @return the header item view type (within the range [0 - VIEW_TYPE_MAX_COUNT-1]).
*/

protected int getHeaderItemViewType(int position) {
return 0;
}

/**
* Gets the footer item view type. By default, this method returns 0.
*
* @param position the position.
* @return the footer item view type (within the range [0 - VIEW_TYPE_MAX_COUNT-1]).
*/

protected int getFooterItemViewType(int position) {
return 0;
}

/**
* Gets the content item view type. By default, this method returns 0.
*
* @param position the position.
* @return the content item view type (within the range [0 - VIEW_TYPE_MAX_COUNT-1]).
*/

protected int getContentItemViewType(int position) {
return 0;
}

/**
* Gets the header item count. This method can be called several times, so it should not calculate the count every time.
*
* @return the header item count.
*/

protected abstract int getHeaderItemCount();

/**
* Gets the footer item count. This method can be called several times, so it should not calculate the count every time.
*
* @return the footer item count.
*/

protected abstract int getFooterItemCount();

/**
* Gets the content item count. This method can be called several times, so it should not calculate the count every time.
*
* @return the content item count.
*/

protected abstract int getContentItemCount();

/**
* This method works exactly the same as {@link #onCreateViewHolder(android.view.ViewGroup, int)}, but for header items.
*
* @param parent the parent view.
* @param headerViewType the view type for the header.
* @return the view holder.
*/

protected abstract RecyclerView.ViewHolder onCreateHeaderItemViewHolder(ViewGroup parent, int headerViewType);

/**
* This method works exactly the same as {@link #onCreateViewHolder(android.view.ViewGroup, int)}, but for footer items.
*
* @param parent the parent view.
* @param footerViewType the view type for the footer.
* @return the view holder.
*/

protected abstract RecyclerView.ViewHolder onCreateFooterItemViewHolder(ViewGroup parent, int footerViewType);

/**
* This method works exactly the same as {@link #onCreateViewHolder(android.view.ViewGroup, int)}, but for content items.
*
* @param parent the parent view.
* @param contentViewType the view type for the content.
* @return the view holder.
*/

protected abstract RecyclerView.ViewHolder onCreateContentItemViewHolder(ViewGroup parent, int contentViewType);

/**
* This method works exactly the same as {@link #onBindViewHolder(android.support.v7.widget.RecyclerView.ViewHolder, int)}, but for header items.
*
* @param headerViewHolder the view holder for the header item.
* @param position the position.
*/

protected abstract void onBindHeaderItemViewHolder(RecyclerView.ViewHolder headerViewHolder, int position);

/**
* This method works exactly the same as {@link #onBindViewHolder(android.support.v7.widget.RecyclerView.ViewHolder, int)}, but for footer items.
*
* @param footerViewHolder the view holder for the footer item.
* @param position the position.
*/

protected abstract void onBindFooterItemViewHolder(RecyclerView.ViewHolder footerViewHolder, int position);

/**
* This method works exactly the same as {@link #onBindViewHolder(android.support.v7.widget.RecyclerView.ViewHolder, int)}, but for content items.
*
* @param contentViewHolder the view holder for the content item.
* @param position the position.
*/

protected abstract void onBindContentItemViewHolder(RecyclerView.ViewHolder contentViewHolder, int position);

}

聊聊提高代码质量

什么样的代码才是好的——李仙鹏

我所理解的好代码

  • 代码规范——可读性

  • 代码耦合低——可扩展性、可移植性

    • 尽量采用接口实现,减少继承
    • 通用功能尽量抽取作为一个独立的方法,避免重复造轮子
    • 设计模式并不能提高代码执行效率,但容易对代码进行模块切分,从而进行代码的解耦合。设计模式也可以算是程序员的一种通用“语言“,方便程序员之间的沟通.
      • 根据不同OS或者语言特性,从大的架构上遵循MVC或者MVP或者其它类似的层次分明的设计模式。
  • 严格的code review

    • 能够发现潜在的bug、不合理的实现和是否遵从代码规范
    • 有助于代码提交人员对自身代码质量的要求——面子问题
  • 性能

以上这些点都是为了程序的健壮性、可扩展性、可移植性。最终目标,在核心开发人员变动或者产品需求变动后,都不会对代码维护、版本迭代和程序稳定性造成重大影响。

51offer-v2.5.0重写中,我们是如何做的

  • 整体架构上采用MVP模式

    • V和P通过接口实现交互,M只被P处理,P处理完后通过接口反馈给V
      MVP
  • 代码可移植性和可扩展性

    • 按照是否通用原则,命名包名——非通用包名,放在offer包下;通用功能模块,放在非offer包下。这样有利于快速移植,或者打成JAR包
    • 接口实现,减少继承——在类的继承中,减少继承体系中每层父类的职责范围
  • HTTP请求

    • 底层请求采用OkHttpOkHttp默认实现的功能为:
      • 支持协议——HTTP/2SPDY, 可以合并多个到同一个主机的请求
        • 使用连接池技术减少请求的延迟
        • 使用GZIP压缩减少传输的数据量
        • 报文缓存响应,避免重复的网络请求
  • json解析

  • 图片加载

    • 采用Android-Universal-Image-Loader

      • 多线程图片加载
      • 可自定义的加载器
      • 可自定义的图片显示回调
      • 图片三级缓存——内存和硬盘缓存的图片通过多种数据结构管理
      • 监听加载过程
    • bitmap显示质量设置为RGB_565——一个像素需要16位表示,Android中默认为ARGB_8888——一个像素需要32位表示。这种方式在基本保障图片质量要求的同时,还能够大大减少手机的内存占用

  • 数据存储

    • 由于目前客户端还未涉及到大量数据和离线加载模式,所以我们暂不使用数据库(以后在做IM或者离线加载时,会考虑使用ORM来访问数据库),综合速度考虑使用Android的SharedPreference

组件通信库EventBus - 吴强

EventBus是一个Android端优化的publish/subscribe消息总线,简化了应用程序内各组件间、组件与后台线程间的通信。

三个主要元素:

  • Event:事件

    Event可以是任意类型的对象

  • Subscriber:事件订阅者,接收特定的事件

    在EventBus中,使用约定来指定事件订阅者以简化使用。即所有事件订阅都都是以onEvent开头的函数,具体来说,函数的名字是onEvent,onEventMainThread,onEventBackgroundThread,onEventAsync这四个

  • Publisher:事件发布者,用于通知Subscriber有事件发生

    可以在任意线程任意位置发送事件,直接调用EventBus的post(Object)方法,可以自己实例化EventBus对象,但一般使用默认的单例就好了:EventBus.getDefault(),根据post函数参数的类型,会自动调用订阅相应类型事件的函数。

ThreadMode

Subscriber函数的名字只能是那4个,因为每个事件订阅函数都是和一个ThreadMode相关联的,ThreadMode指定了会调用的函数。有以下四个ThreadMode:

  • PostThread:
    事件的处理在和事件的发送在相同的进程,所以事件处理时间不应太长,不然影响事件的发送线程,而这个线程可能是UI线程。对应的函数名是onEvent。
  • MainThread:
    事件的处理会在UI线程中执行。事件处理时间不能太长,这个不用说的,长了会ANR的,对应的函数名是onEventMainThread。
  • BackgroundThread:
    事件的处理会在一个后台线程中执行,对应的函数名是onEventBackgroundThread,虽然名字是BackgroundThread,事件处理是在后台线程,但事件处理时间还是不应该太长,因为如果发送事件的线程是后台线程,会直接执行事件,如果当前线程是UI线程,事件会被加到一个队列中,由一个线程依次处理这些事件,如果某个事件处理时间太长,会阻塞后面的事件的派发或处理。
  • Async:
    事件处理会在单独的线程中执行,主要用于在后台线程中执行耗时操作,每个事件会开启一个线程(有线程池),但最好限制线程的数目。

注册事件与解除注册

  • 通过EventBus.getDefault().register方法可以向EventBus注册来订阅事件
  • 通过registerSticky可以注册Stick事件处理函数
  • 通过EventBus.getDefault().unregister方法解除EventBus事件订阅

Post事件

  • 直接调用EventBus.getDefault().post(Event)就可以发送事件,根据Event的类型就可以发送到相应事件的订阅者。
  • 当通过postSticky发送一个事件时,这个类型的事件的最后一次事件会被缓存起来,当有订阅者通过registerSticky注册时,会把之前缓存起来的这个事件直接发送给它

缺点

无法进程间通信,如果一个应用内有多个进程的话就没办法了

注意事项及要点

  • 同一个onEvent函数不能被注册两次,所以不能在一个类中注册同时还在父类中注册
  • 当Post一个事件时,这个事件类的父类的事件也会被Post。
  • Post的事件无Subscriber处理时会Post NoSubscriberEvent事件,当调用Subscriber失败时会Post SubscriberExceptionEvent事件。

其他

EventBus中还有个Util包,主要作用是可以通过AsyncExecutor执行一个Runnable,通过内部的RunnableEx(可以搜索异常的Runnable)当Runnable抛出异常时通过EventBus发消息显示错误对话框。

参考资料:快速Android开发系列通信篇之EventBus

如何提高代码质量——吴明

Alt text

  • code review
  • 命名规范
    • java命名规范:
      • java类:m+模块+功能(如:mLoginRegester)
      • 控件类:m+模块+功能+控件缩写(如:mLoginRegesterBtn)
      • 常量:模块+功能(如:SCHOOL_COUNT_MAX)
    • xml命名规范
      • id:模块+功能+控件缩写
    • color,string,dimen,drawable
    • 图片:模块功能备注_状态
    • 注释
  • 好的框架
    • okhttp
    • Gson
  • 设计模式:
  • 处理异常
  • 优化性能

  • 代码测试工具:静态代码分析

如何写一份好的代码 - 张超耀

数据结构和核心算法

  • 数据结构的重要性:低水平程序员总在考虑代码,高水平程序员总在考虑数据结构及其之间的关系

  • 数据结构决定算法,数据结构考虑清楚了,核心的算法自然就出来了,这就是关于每个类的每个方法如何实现的问题

功能实现

  • 思路确定后,实现过程也需要大量的构思活动。碰到比较熟悉有经验的领域,自然可以轻车熟路,但难免会有一些你不太熟悉的技术需要尝试。作为一个程序员,最大的挑战也是最大的乐趣所在,就是不断学习新的技术,没有这样的心态,很快就会落后。

  • 那么遇到不熟悉的技术怎么办?Demo先行,这样做的好处是把单个技术问题和其他潜在的bug隔离开来,便于快速学习新技术。否则,直接在项目里写代码出错以后,要判断问题的源头都要多费好几倍的精力。

测试

  • 测试很重要,设计测试用例就像开发时设计数据结构一样,也是很关键的。

代码可读性

  • 要想自己满意,代码的可读性一定要好。要做到一年后甚至几年后你拿到自己写的代码,还能很容易看明白当时的思路和实现。这就涉及到命名和注释的问题

  • 命名就像超市里的商品标签一样,要让看得人一目了然就知道这是个什么东西

  • 注释也是很重要的,它可以用来说明一段代码的作用,算法的设计思想,或者是方法调用的参数格式要求等

最后总结一下:

  • 技术水平是可以慢慢提高的,但是好的编程习惯需要从一开始就养成,它会让你在前进的道路上事半功倍,受益终生。

怎样写好的代码 - 曾铭

有两种方式构建软件设计:一种是把软件做得很简单以至于明显找不到缺陷;另一种是把它做得很复杂以至于找不到明显的缺陷
——C.A.R. Hoare

谁来写?角色的定义

  • 程序员 vs 工程师
  • 实现功能 vs 解决问题
  • 搭个帐篷 vs 照顾孩子

好的代码?

一个程序员更希望接手一个有bug但是看的懂的工程,还是一个没bug但是看不懂的工程?

  • 代码跟人聊天,解释做什么,注释解释为什么这么做,要注意什么
  • 面向接口而不仅仅面向对象

举例:在 APP 开发和 API 开发之间,面向 API 文档做开发

写的过程?(推荐开发过程)

  • 需求明确(理解来源及演进)
  • 整体设计(外在联系,临界条件,错误处理)
  • 实现(验证思路,解决问题及优化)切记不要拿到需求直接跳到这一步

好?目标

  • 可运行,可读,可维护,可测试
  • 参见 TDD,BDD