flow

iOS

View 的操作

将一个后创建的控件插入到前面创建的控件的下面的方法insertSubview(通过该方法可以调整子控件在subView中的顺序)
bringSubviewToFront可以将一个控件放到subviews的最上面
sendSubviewToBack可以将子空间放到subviews的最下面

文/袁俊亮技术博客(简书作者)
原文链接:http://www.jianshu.com/p/d61de638aff0
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

创建一个项目时, 最好先在 finder 中把项目中的逻辑目录先设好



这里准备记录还没从代码中搬出来的笔记….

父类抽取:

在相似的界面中, 为了不重复造轮子, 可以看到 setting 界面下的各个跳转子控制器都类似, 就是显示的数据不一样, 我们可以把共同的东西抽取到父类, (其实就是数据不一样, 数据的处理也可能不一样, )

存储数据:

有些 cell 的选项是开关类型的, 用户使用开关的状态应该记下来, 没次进入肯定不能是重置啊, 对于开关数据应该存储起来.
    cell 的视图中, 对应需要存储的目前有开关类型, 可以在 cell 的类中,
    1. 创建 switch 的时候添加监听,
    2. 监听方法是当点击事件发生时记录传入 switch 的状态, 保存到用户配置中: NSUserDefualt
    3. 设置模型的时候, 是开关类型的就读取配置信息, 把状态设上去

帮助界面

帮助界面有很多条目 cell, 分别对应一个问题, 这个 tableView 的内容应该是从网上获得的, ?? 能不能用一个 json 练习保存数据, 再在 controller 里转换成字典模型, 在转成模型

点进 cell, 出现的是一个网页, 用的是 UIWebView, 只有一个 view, 应该让 self.view = webView, 在 loadView 里

webView 可以想 URL 发出请求, 请求内容, urlRequest, 那么就需要一个 url, 那么在本地的话就要知道这个 url 的全路径, 全路径可以从 bundle 获取, 最后由 webView 加载请求

默认 modal 出来的是不在 nav 框架内的, 所以就没有返回按钮, 在弹出的时候为了能回去, 一般将 view 封装在 navController 里, modal 这个 navC, 再在 topView(view)里拿到导航条设置一个按钮, 监听点击, dismiss 自己
        在点击 cell 的时候, 因为默认的 baseC 不是 modal , 有2种方案: 模型设置一个属性判断那种弹出方式, baseC 里选中方法中判断模型这个属性 /  重写选中方法
            显然选第二个好, 反正自己继承的 Controller 下的 cell 都是 modal 状态,

果然是 Json 转模型, 在 modal 的界面那里传入模型, 里面就包括有标题和目标网页,

弹出的视图显示页面位置不一样, 像早就滚动在特定的地方是用 js 弄的, 在 OC 中可以执行一些 js 的代码(对 js 代码的调用), js 在做前端比较多
        html 中用 js 的window.location.href = "..."; 来做一些网页定位的事, 可以定位 url 资源, 可以定位本页面, 本页面要#id...
    webView中有执行 js 代码的方法: stringByEvalua...JavaScriptFromString:@"这里放代码"]; @""里一样可以用拼接方法把需要的 id 传进去

关于-分享

在关于界面有 cell 是有显示联系方式的, 这个可以用 detailLabel 来展示, 在模型中添加一个 detail 属性, 在 cell 中将这个属性赋值给 detailLabel, 将创建 cell 的时候默认创建的是 Value1类型,
cell 上面那一大块是 headereView, 可以用 xib 来弄就好

iOS6iOS7 的适配

在默认情况下, 系统自动帮我们做的一些设置在 iOS6和 iOS7之间不同, 这就导致了在2个系统中 UI 出现不一致的情况, 为了识别性和美观统一, 应该设置一样的 UI, 系统默认设置搞的不一样, 我们就要重新搞过来适配

首先, tableView 的背景颜色不一样, --- 其实是iOS 中自动设置了背景图片, 所以我们在 baseViewC 上设置背景颜色是没用的,
        在设置规则图片时, 利用小图片平铺可以生成一个颜色来设置背景色, 同时要 nil 系统背景 View, 才能显示出背景色来, (背景 View > 背景色)

        cell 的选中颜色也不一样, 在 cell 一开始初始化时就设置它的选中颜色, (可以将设置 rbga 颜色设置个参数宏)(设置背景 View, selected.view.backGroudColor)

        头尾的 cell 是圆角的, -- 系统默认设置 nor 背景, 自己设置一个view 设置到self.back...就好

        在 title 和 detalLabel 有背景颜色, 应该去掉, 不然显示不和谐(是背景色, 非背景 view)

        cell 的左右有间距, --- 其实系统在设置 cell 的时候自动设置了其 frame, 调用 cell 的 setFrame 方法, 将 frame 自动的传入, cell 的 frame 是 ok 的, 显示有间距是由于 cell.contentView, 自动收缩了,
                2方案: 调整 contentView 的 frame, / 加个 cell 的 frame 拉长(可以直接在 setFrame 中设置. 设置 contentView.frame 的话还要在重新获取>设置>赋值回去)  注意:! iOS7中并没有拉伸, 这样统一调整会使得 iOS7变形, 应该加以判断 iOS 版本

        分割线: 在初始化 cell 的时候自己定义一条线显示字啊 cell 的下方, 添加到 contentView
            注意有辅助视图的话线会显示的短, 因为辅助视图不是 contentView 的一部分, 可以设好子和屏幕一样宽,

            在组的最后一行时没有分割线, 可以在能知道最后一行的地方(cellforrow...)判断下, hide 分割线, (需要提供方法/属性给外部)\
             iOS7不需要设好子分割线

在那个”上一期真机调试视频”

网络数据抓取

方法有两种

正则表达式

第三方工具(框架)抓取

​ 一般抓去网页的时候都选择用火狐浏览器, 因为有一个插件fireBug, 可以查看网页中指定位置的代码, 还能在弹出的代码块中指向那块代码, 在网页上就会高亮对应的区域

1. 找到需要抓取的数据
2. 提取数据源码(拷贝代码)**抓取下来的数据还要抓取网页中的头尾结构代码来补全**
3. 通过第三方工具抓取需要的数据_JAVA 已经有做有很好的工具和调用代码, 比oc 方便

用 JAVA 的话, 需要用到其他 IDE, eclips 为例: 创建工程, 添加类,

利用三方工具(框架):jsoup. 将 jsoup.java 拖进工程(建议文件夹分类), 相当于在 xcode 中拖入 MBPrgressHub 框架一样, 在 java 中 , 想使用还需要添加到环境中 — 右键>build Path>add to build path

然后是将从网页上拷贝下来的代码加载进内存

要先获取路径(目前写死吧…): String path = “…”>>> 用三方框架 jsoup 加载网页进来: Jsoup.parse(new File(path), 编码): 返回 document, 保存网页的内容

会碰到点异常: 暂时用 try{ 包起来}可以无警告

网页源码中每个应用的信息都在一个 li 里面, 用三方框架把它抓取出来

将所有 li 抓取: 选中 li, 返回这些 li 到数组: Elements list = doc.select(“li”), 遍历数组, 取出 li 里面保存的各种元素, 一样可以通过.select()来抽取, 获取元素 Element, 转文本的话就是.text()

通过获取对象中的属性键对应的值: .attr(“key”), 图片名, 地址, 描述等

然后是一个非常蛋疼的方法,,,,在 java 中获取信息之后, 用打印来拼接和输出对应的@[ key: value…], 再直接全部拷贝到 oc 的数组中…在 oc 中生成plist 文件….

用 UI 搭建一个简单的应用是可以的, 数据来源就从网上爬下来, 然后看看需要搭建什么结构的应用, 把数据源传进去就可以了

真机调试:

打电话

​ 在需要执行打电话的 cell 的 model 上, 加上 code 代码, 点击后执行代码, 因为是应用间的跳转,

1. 直接获取 Application. open URL 打电话协议: tel://number#简单, 但是打完电话后会停留在电话界面会不来#
2. 另一个协议:telPrompt://number (苹果私有协议, 上架审核会不通过) # 私有协议#
3. 利用 webView 来打开一个 URL, 就和1.的 URL 一样, 但是打开的是modal出来的界面, 可以在挂断电话后回到应用

发短信:

1.  直接打开 URL: sms:// number  #不会回到应用#
2.  用 MFMessegeKit(MessageUI/MessageUI.h) 框架, 添加MFMessage,,,控制器, 设置各种信息后 modal 出来

发邮件

1. 直接打开 URL: mailto://address  #同上#
2. 同上框架 , MFMail....对象,,,,,MFMail...控制器, 设置各种信息 modal 出来

评分支持

1.  直接用Application 打开 URL: itms-apps:// 应用地址,  可以直接拷贝, 不同的就是 appleid, 这个在将应用提交到苹果后给出的一个 id

应用推荐:

在应用推荐中点击推荐的应用应该跳转到该应用, 可以用 Application 打开URL, 一个应用对应的 URL 是在工程.taget里面设置的,
参考
http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=APPID&pageNumber=0&sortOrdering=2&type=Purple+Software&mt=8"

应用内打开参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let storeVC = SKStoreProductViewController()
storeVC.delegate = delegate
storeVC.loadProduct(withParameters: [SKStoreProductParameterITunesItemIdentifier: "1062781515"]) { [weak self] (success, error) in
guard let me = self else {
DDLogDebug("self 被释放")
let url = URL(string: "http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=\(1062781515)&pageNumber=0&sortOrdering=2&type=Purple+Software&mt=8")!
UIApplication.shared.openURL(url)
return
}
if error != nil {
DDLogDebug((error?.localizedDescription)!)
let url = URL(string: "http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=\(1062781515)&pageNumber=0&sortOrdering=2&type=Purple+Software&mt=8")!
UIApplication.shared.openURL(url)
} else {
fromVC.present(storeVC, animated: true, completion: nil)
}
}

转盘

​ 难点: 界面的搭建
​ 对于不动的圆盘, 可以直接用 xib 描述, 关键是星座按钮, 需要有旋转的角度:
​ 方法, 设定按钮的锚点为最底部中间, 将位置设为圆盘中心,
​ 在创建 xib 的时候就应该有按钮 — 本来就是界面搭建, xib 不能加的, 在对应的类中加上
​ 在 awakeFromNib 中创建按钮, 设定位置, 图片, 按下图片, 根据第几个图片, 设定角度[累加30℃, n*30], 监听按钮点击
​ 按钮的 bounds 应该和图片的大小一样
​ 监听方法里设置按钮选中状态, 并以属性记录, 撤销上一个选中按钮的状态
​ 注意设置圆盘图片可交互, 不然按钮是子控件, 也不可交互了

​ 用 CGImage 来按 rect 切割出各个部分的图片, 注意: CGImage 是像素坐标, 而 UIKit 是点坐标, 所以设置 rect 时需要转换值,
​ [UIScreen mainScreen].scale 可以知道当前设备的比例(是否 retina 屏), 再对像素进行处理

​ 设置 ImageView 的时候位置不能在想要的地方, 因为按钮本身就是矩形的, 可以用 contentInset 设置内边距把 ImageView 压缩到想要的位置, 或者自定义 Button, 重写 rectForImage…方法, 设定好 ImageView 的位置和大小

block 循环引用

因为在设置block 属性的时候设置的是 copy, 这就会使得传入的 block 都是 copy 到这个属性, 而 block 属性又归 self 所有, 最终在 block 里使用 self 的时候会强引用 self, 这就形成了循环引用, 设都释放不了.
解决>>>>> 在 block 引用 self 之间做一个中介, 中介弱引用 self, 但是还是指向 self, 操作同一个内存.
    __unsafe_unretain 类名 *变量 = self.  就做了一个弱引用 self 的变量. 在 block 中用这个变量, 就不会强引用 self 了,
    __weak 异曲同工, 只是 weak 会在内存释放时自动给变量设置 nil, 而__unsafe 不会, 所以 weak 更安全, 不会有野指针的问题
    注意: _age 这种方式访问属性相当于 self -> _age, 也会强引用 self
 block 默认创建的时候是爱栈内存中, 当被 copy 引用后被赋值到堆内存进行强引用

autoResizing

在设置 View 的时候, 尺寸那一栏的设置里有个伸缩图, 里面的箭头的意思可以简单的看图猜到, 上下左右的线代表控件距离父控件上下左右的距离变不变, 里面的是宽高要不要随着父控件的改变而等比例改变. 可以设置几个 View 实验一下
用代码实现的话就是 view.autoResizing 属性的设置, 有几个值, 对应 图里的几个线, 注意: 代码中设置的是可拉伸. 和图里的是相反的
以后(现在)大力使用 autoLayout, 这个只需要简单了解即可

试了之后: 四顶角: 有 autoLayout 的话会保留上面的, 没有的话就是取中间, 上下两头会截断
中间的2个箭头: 是否随父控件拉伸改变
四周 : 是否和父控件的四边保持距离

strong - weak

控件和代理应该用 weak
    只要没有强指针引用内存对象, 对象就会被释放
    所以: *循环引用会造成内存对象都不释放, 但是又没有变量指向他们了, 最终会造成内存泄露*

    控件: 控件添加给父控件后, 父控件中有一个 subViews 属性保存这些子控件, 是个数组, 数组里的每个元素都指向对应的子控件存储空间, addto 也是个强引用, 这里如果直接想通过一个属性来操作子控件, 设置 strong 和 weak 是一样的, 因为父控件一旦被释放, 顺下去都没有强引用了, 也就没有任何强引用子控件, 子控件也会被释放, 但是一个循环不应该被闭死, 所以直接操作子控件的自定义属性应该设置 weak

    代理: 一个控制器中有 view, 有些 View 里面有代理属性, 而 View 的代理一般是自己的控制器. 控制器对 View 是强引用的, 此时如果代理对控制器强引用的话, 就会循环引用了, 如: UITableViewController.tableView.delegate

多线程

###进程
每个程序运行, 都会在内存中开辟一个空间, 来执行程序的运行, (正在进行的程序), 内存中的每个进程都互不相关, 相对独立.

###线程
每个进程要执行什么任务, 都要开一个线程来执行, 每个进程只要要有一个线程

    * 串行: 当只有一个线程时, 需要执行几个任务的时候, 任务的执行是排队按顺序来的, 一个个执行下来, 称为串行(类似一个端口传送信息, 任务只能一个个来)
    * 并行: 开了n个线程之后, 可以在同一时间执行n 个任务, 每个线程执行一个任务. 其实 单个 cpu 在一个时间点只能处理一个线程, 因其处理速度快, 可以高频的在不同的线程间切换, 形成了同时执行多线程的假象.   快速在多线程之间调度
        多线程优点: 提高 CPU 利用率, 加快多任务的执行效率, 提高程序的执行效率
              缺点: 在线程过多的时候, 每个线程切换的频次降低, 会显得任务卡顿, CPU 负荷高
开启线程需要占用一定的内存空间, (默认情况下, 主线程是1M, 子线程默认是512k), 线程开的过多, 内存就占用大, 降低程序性能
线程越多, CPU 在调度线程方面的开销就越大
程序设计会更加复杂, 如: 线程键通信, 多线程数据共享

在 iOS 中, 程序运行会开辟一个线程–主线程, 也称为 UI线程,
程序对 UI 的各种操作, 拖动控件, 创建控件, 点击等等和 UI 有关的操作都是在主线程中执行.
一条线程只能处理一个任务, 任务多的话就要排队, 所以, 如果在主线程中进行一些耗时操作, 就会阻碍后面的操作, 在进行耗时操作时, 用户还进行了 UI 交互的话, 就会排队等待, 会造成操作卡顿的体验
如果能把耗时操作放到子线程, 那么在执行子线程耗时操作的时候, 主线程有 UI 事件要响应, 那么在同一时间会执行2个线程, UI事件会立即相应, 不会让用户等待.
————-如果以后有耗时操作的话, 放到子线程会是比较好的选择

开启线程:

实现多线程的方案:
* | pthread : POSIX thread | C 语言 | 几乎不用 | 跨平台, 比较接近底层, 但是不面向对象, 不好操作, 线程的各种操作需要程序员手动管理 |
* | NSThread : NS 框架 | OC | 少用 | OC 方法, 面向对象, 可以直接操作对象, 设置对象属性等, 如拿到当前线程: [NSThread curremtThread]; 也是需要手动管理线程 |
* | CGD |   C 语言    |   常用  | 可以调用多核来处理线程, 可以自动管理线程|
* | NSOperation |OC | 常用 | 基于 GCD, 面向对象, 自动管理线程的生命周期 |

####pthread:
        开启线程: 传入线程 id 地址, 好让函数可以将线程放到指定的空间,
                    传入调用的方法, 线程开启后执行的任务写在这个方法里,
        主线程开启子线程后继续自己的线程的任务, 然后子线程的事就不关主线程的事了

####NSThread:
        纯纯的 OC 面向对象, 像创建其他对象一样创建就好, 传入调用谁的什么方法, 还有一个传入方法的参数就好
        创建对象之后还要调用 NSThread 的开始方法才会开始这个线程,
        之后便是完整的开启了子线程

        对于主线程的一些操作

        对线程的属性的操作

        创建 NSThread 的三种方法和不同

        线程的创建都是在栈中完成的





    >_打印对象时, 会调用对象的 description 方法_
    >改项目名称的时候, 字啊项目名要改, 在上面的运行环境中的 scheme 也要改

线程状态

线程的生命周期, pthread 和 NSThread 都需要手动写代码来管理线程的生命周期,
了解线程的状态
    线程有5种状态:
        当创建一个新线程时[还未执行]:  New (新建), 这时仅仅是开辟了个内存
        开始线程(start)               :  runnable(就绪), 这时线程进入可调度线程池, 随时待 CPU 调度
        Cpu 调度当前线程            :   RUNning(运行) , 执行这个线程中的任务
        CpU 调度其他线程            :   重新回到 runnable(就绪), 等待下一次调度
        运行中调用了 sleep/等待同步锁: Blocked(阻塞), 线程阻塞后不做任何任务了, 所以就移出了调度池, 不可调度了,
        sleep 到时/得到同步锁        : 回到调度池, 进到 Runnable 状态, 等待调度
        当线程任务执行完毕[正常退出] 或 异常[到某行代码时意外退出] 或 强制退出[调用方法强制退出线程] :  Dead(死亡),  这时线程移出调度池, 但是还在内存中, 且没有方法回到调度池, 等待手动代码释放 release, 当 release 后释放内存后线程才从内存中移除

控制线程状态

控制线程的:    新建,  >>>>   开始  >>>>    睡眠    >>>>    强制退出
    新建: alloc.init .etc
    开始: start
    睡眠: sleepFor...
    强制退出: exit
    当线程 Dead 之后便不能在重新开始, 会报错
创建线程来实验下这些状态, 特别是 sleep 和 exit

多线程引发的安全问题

当有多条线程同时操作一个数据的时候, 会引发数据安全的问题
    同一时间(CPU 上如 A 比较块一点), A读取了数据 data, 迟一点, B 也读取了, 然后 A 改动后写回去, 迟一点, B 也改动了写回去, 最终只有 B 操作的结果有效, A 就白操作了, 数据就不对了

方法: 给数据加锁, 当 A 读取时, 先加一把锁, 再读取, 加锁的数据同一时间只能有一个线程读取, 只有线程对数据解锁后, 其他的线程才能操作读取这个数据. 这就相当于是串行了, 但是并行会有问题, 没办法

买票的例子 :
    多条线程同时操作同一个方法, 操作同一个数据, 没加锁, 在 CPU 调度的时候修改同一个数据会以最后一个结果为准, 显示出来就是几个结果都是一样的

    在 OC 中锁是锁代码, 在同一时间只能有一个线程执行某段代码,  代码中有执行操作数据, 所以同一时间只有一个线程操作数据        给代码加锁, 只能是用同一把锁, 这样在其他线程访问的时候才能知道这把锁有没有上锁, 如果没词都新建锁, 没次都是不上锁.
        加锁:
            NSLock tryLock   |  Lock    -------   太繁琐, 不建议
            @synchronized(锁对象) {   需要上锁的代码   }    ------   任何一个对象都可以是锁对象, 而一般本控制器就只有一个, 所以一般用 self 就好
            设置优先级: 优先级高的会比较大概率被 CPU 调度

        加锁的优点: 能有效防止多线程抢占资源造成的数据安全问题
                缺点: 需要消耗大量的 CPU 资源
                            上锁需要记录状态, , 其他等待的线程也需要消耗资源
                    多条线程抢夺同一资源时才要加锁, 不要乱加

    线程同步: 多条线程按顺序的执行任务
        线程同步方案: 加锁

原子属性和非原子属性

原子属性: atomic        |   会为 setter 方法加锁, 在 OC 中所有的属性都是 atomic 的, 性能较差,
非院子属性: nonautomic |   不会为 Setter 方法加锁  |  苹果官方推荐 nonatomic 提高性能, 减少资源占用

iOS 开发建议
    * 所有属性都声明为 nonatomic
    * 尽量避免多线程抢夺同一块资源
    * 尽量将加锁, 资源抢夺的业务逻辑交给服务器端处理 , 减小移动客户端的压力

线程间通信

*在一个进程中, 多线程往往不是孤立的 , 多个线程之间需要经常通信

 *线程通信体现:
    * 1个线程传递数据给另一线程
    * 在一个线程执行完特定任务后, 转到另一个线程继续执行任务

    下载图片的例子
        下载图片依赖网络状况, 是比较耗时的操作, 应该放到子线程中,
            开辟一条子线程来下载图片, 不影响主线程的 UI 操作
        iOS 规定, 对 UI 的刷新, 修改等操作必须在主线程中完成, 不然是不安全的, 我们设置的属性是 nonatomic

    传递数据给主线程: performSelectorOnMainThread....  waitUntil: No, 表示不等待, YES 表示等待, 一般是 NO, 直接结束子线程, 主线程等待返回数据
对于 NSTread, 只要了解基本用法, 熟练掌握 GCD 和 NSOperation

GCD

* 苹果官方做的一个针对多任务处理利用多核的技术
* 纯 C, 不用管线程的生命周期, 会自动管理,
* 只有两个概念, 对这两个操作就会自动进行线程的操作
    * 任务: 首先要定制一个任务
    * 队列: 将任务添加到队列中排队, 添加到不同的队列会有不同的处理

GCD 在一个libdispatch,
GCD 的函数基本都以 dispatch 开头

GCD 执行任务:
    开启任务函数类型:
        * 同步: 在当前线程中排队按顺序执行任务, 不能开新线程
        * 异步: 另外开线程来执行任务, 可以开新线程
    队列类型
        * 并行: 同时将队列中的任务拿出来并发执行
        * 串行: 将队列中的任务拿出来按顺序执行

    GCD 在开始就准备好了全局并发队列待调用, 传入队列优先级(一般是默认), 还有个0
            串行队列需要create 一个  |   使用主队列(跟主线程相关联的队列)

同步:
    * 并行: 不会开新线程
    * 串行: 一样更不会开新线程
异步: 具备开线程能力
    * 并行: 根据任务开 N 个线程
    * 串行: 开一条线程, 按顺序执行任务
一般我们用 GCD 是用异步, 因为用 GCD 就是要开新线程才用的

一般在 iOS 中, C语言的框架里, 看到 create/copy/new/retain 的, 都要 release
    在 arc 环境中就不需要手动管理了
        例外: CF 框架的还需要手动管理
反正看到那些关键字的都写一下, 报错就不写了呗

主队列是 GCD 自带的一种特殊串行队列, 对应全局并行队列
    主队列不管是加到那种函数(异步/同步)都不会开启新的线程, 全部任务都是扔到主线程中执行
        主队列的意义: 当子线程执行完后, 需要与主线程通信的, 可以在主队列中添加任务, 就会把任务加到主线程

注意!!!
    在同步函数中在主线程往主队列添加任务: 会死锁, 任务排队在最后, 但是前面调用任务的方法又在等待任务执行完, 最后谁都通不过
    在异步函数中的情况不同: 任务依然排在最后, 但是非同步, 前面调用的函数可以先执行完, 不用等待调用任务执行完毕

常用的情况:
    1. 在一个方法中, 需要耗时操作的时候, 开个异步全局并行(可根据需要开串行还是并行)执行耗时操作,
    2. 然后在 dispatch 异步(相对于子线程, 主线程也是异步)主线程队列执行 UI 刷新的操作, 这时, 代码都是卸载一起的, 不同线程的操作写在了 dispatch 的 block 封装的任务里
        同时, 在子线程的 dispatch 中的变量都可以直接在回到主线程的 dispatch 中的 block 中 使用

GCD 其他一些好用的函数
    * 延迟操作, 填入延迟的时间, 参数中有函数会根据这个时间算出需要延迟的具体时间, 然后就是操作的代码
    * 组队列,: 当需要在一些线程都执行完后, 所有队列都执行完后才去到其他线程做一些事情的话, 用组队列,
        可以将异步并行开启的线程 队列加到组队列中, 这个组就会在线程中分一个组 , 调用组队列的函数, notify 当组队列执行完后会通知, 然后执行某队列的任务
        dispatch_group_async_...(group , 队列, block) ,    组: 要先创建一个组

NSThread 和 GCD 的总结

这里总结些需要掌握的, 重点掌握, 其他的先了解即可

* NSThread
    * 线程开启的几种方法
        1. 先创建后启动, alloc] initWith...] -> start
        2. 直接启动: perform... / detach...

    * 其他用法
        1. [NSThread currentThread];
        2. + mainThread

    * 线程间通信:
        1. perform...InMain...

* GCD(重点)
    * 队列类型
        1. 并发: 获得全局并发队列: dispatch_get_globle...
        2. 串行: 自己创建: dispatch_queue_create  /  dispatch_get_main_queue
    * 任务类型(函数类型)
        1. 同步: dispatch_sync...
        2. 异步: dispatch_async
        3. 以上两个配合使用
        4. 线程间通信: dispatch_async....(globleQueue, ){  dispatch_asycn(主队列)}
        5. 其他用法: ..._once / after / group_async
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

单例模式

有些情况对于某个类自始自终只需要创建一个对象就好, 如资源工具, 想加载某个mp3, Application, .etc, 这时候就需要单例模式

设计模式: 长期以来某些方法/工具/经验的总结

不管别人怎么 alloc, init, retain, copy, 都只返回一个对象
    要做到这个, 需要控制几点
        1. 只创建一个存储空间  :: 用 dispatch_once,  将allocWithZone包起来, 为了试别人 alloc 时候返回同一个存储空间, 定义 static id _instance变量来保存 alloc 一次的对象
        2. 只初始化一次资源     :: dispatch_once将初始化资源的包起来// 或者将super init]也做 once 操作, 虽然苹果官方中没有这些, 建议还是包的好,
        3. 自己开的类方法访问获取单例时也做好控制
        4. copy 方法, 建议重写的是 copyWithZone, 也是什么都不做
        5. 对于非 ARC, 还需要自己做些内存管理
            5.1 retain 方法不加1, 返回自己就好
            5.2 release 方法. 什么也不做, 不释放对象
            5.3 retainCount 方法, 返回1
            5.4 autorelease 方法, 返回自己

    这些方法的重写几乎哪里都一样, 可以做个参数宏, 以后需要单例的时候直接调用就好, 注意: init 方法中有加载资源, 不一样, 就不纳入宏了

###适配 ARC 和非 ARC 的单例模式,
    在ARC 和 MRC 中内存管理不同, 除了 ARC 中要重写的方法之外, 还有其他的,
        适配同一个宏, 可以用条件编译, 看编译环境是不是 ARC, 是的话是什么宏, 不是的话是什么宏, 判断: __has_fearues(arc_)

NSOperation

墙裂推荐的多线程技术, 面向对象, 基于 GCD, 并封装了更多方法,
了解:
    OPeration: 操作, 将要执行的任务封装在里面, 相当 GCD 的任务
        是个抽象类, 需要子类才能封装任务: * InvocationOperation: 回调, 将任务封装在另一个方法里, 调用时会调用 target 的那个方法
                                         * BlockOperation: 块操作, 将任务封装在 block 里, 还可以用 add 在同一个 blockOperation 对象中封装多个
                                         * Operation 有对象方法取消本 Operation
                                         * 设置操作的队列优先级, 线程优先级
        单独执行需要 start, 单独 start 的话不会开线程, 只在主线程中同步串行
    Queue: 队列 ---
         NSOperationQueue:
                        * 可以直接通过 block 给对象添加 operation 任务
                        * queue addOperation] 添加 Operation 对象
                        * 根据任务的数量开线程, 不是 Operation 的数量, 有些 Operation 封装了几个任务, 还根据执行效率, 执行快的可能会线程复用
                        * 设置最大并发数, 不让线程过多而损耗 cpu 资源, 影响 UI 线程
                        * 操作监听: 可以给操作封装完成后的任务, 在 Operation完成后(里面封装的任务都完成)再执行某个任务, operation.completionBlock
                        * 设置依赖: 设置某个 Operation 在哪个 Operation 之后才执行, 叫依赖, A 依赖 B, A就在 B 之后执行, 注意: 不能相互依赖!!!
                        * 挂起/继续: 暂停整个队列的线程, 在某些动作时, 不想有线程耗 CPU 资源影响体验, 可以在动作时暂停队列中的所以线程(肯定都是子线程),
                        * 取消掉所有线程的 Operation [queue cancelAllOperation]

自定义 Operation

觉得系统自带的2个 Operation 不够用, 想自己写一个满足自己需求的
    e.g.tableViewZ 展示应用列表,
        一般 exception 中有 nib问题的在 storyBoard 里出问题或其他 xib
        不让任务栏和 tableView 重叠, 可以设置
        可以将 tableVie 的数据源方法和代理方法拖到代码库中,,,以免重复敲...

            在主线程(UI 线程)中下载图片显示的话,,,会先下载完显示 cell 的所以图片才能显示出 tableView 来....卡死...
            自定义 Operation 继承自 Operation, 设置好需要的属性, 然后重写- (void)main 方法, 当队列中的 Operation执行, 就会调用这个方法
            方案1:
                 cell 中下载图片肯定是要放到子线程的,
                 自定义的 Operation 只管下载图片, 传入 url, 返回给代理一个图片, 代理将图片设置给 cell, 属性 tableView
                 问题:
                    如果是相同的图片, 只用一个 Operation 下载就行, 不需要浪费资源, e.g.微博主页, 头像都是一样的, 不用每个 cell 都开线程下载
                        所以需要根据图片 url 确定一个下载 Operation, 这个 url 图片已经有 Operation 下载, 就不用重复下载
                            设定一个 Operations 字典记录正在执行下载操作的 Operation, key:URL, value: Operation
                    图片下一次就好, 下在的图片保存到一个字典中, 取这个 url 的图时先看看有没有缓存下来, 有就直接用, 没有在判断有没有 Operation去下载
                        key: URL, Value: img
                    在OPeration 下载完通知的代理方法中, 移除 字典中对应Operation[不然检测总有正在下载这个 URL 的],  将下载的图片保存到图片字典中缓存好, 刷新表格行,
                 这样就保证了对应行的 cell图片只下载了一次, 刷新时会判断查找对应行在图片字典中的图片, 只进行一次下载图片的操作
                 一定要下载完成后移除 Operation, 不然, 如果下载失败, Operation 还存在, 就不会去下载图片, 将永不可能下载了, 在移除下载操作缓存后判断 Image 是否有值
                    因下载可能会失败, 返回的 image = nil, 再赋值给 image 字典时, 对应 key: nil, 字典中不能存 nil, 就会报错
                    还要做性能优化, 拖拽开始挂起队列, 拖拽结束恢复队列
                    这个没次还要从新下载图片, 还需要将图片保存到本地
                    缺少本地离线缓存
                    在自定义 Operation 时在- (void)main方法中要自己创建自动释放池, autorelease, 因为子线程是不能访问主线程中创建的自动释放池的
                    而且每每耗时操作后要检测下是否被取消, 取消就直接 return,


        技巧:
        ####全局断点: 在导航区ccommand+6, 下面选择Add Exception BreakPoint: 异常断点, 会在报错的那行代码停下

        方案二:
            第三方框架: *SDWebImage, 一行代码, 就可以将方案一中的多线程, 加载一次, 下载一次, 离线缓存, 保存本地都做好...
        ####建议下载 github desktop 客户端管理下载的项目, 一般我们是需要文件夹里面的源代码, 它帮我们异步下载好图片展示, 同时做好了内存缓存和硬盘缓存
                        这个里面还有很多强大的方法, e.g.设置图片的时候可以看进度, 还能拿到图片, 同时设置当前下载的缓存策略
                注意: ! 这个框架因为可以把图片缓存下来, 所以内存要把关, 在设置图片的时候还能使用缓存策略, 如果一个项目都是用这个框架缓存图片, 要做好策略
                            设置 ImageCach 的策略 : 大小/ 时间/ ...
                        还必须做一件事! 在内存警告的方法中, 如果调用, 就是说发生了内存警告, 要立即清空所以的内存缓存!! , 同时停止所有正在下载的操作

技巧: 保存自己设计的类是, 把不一样的需要特殊这只的用\<#name#> 起来

提取代码

如果一个程序写的是比较正规的, 耦合性不强, 可以将其某些功能提取出来, 单是耦合性很强的话, 几乎整个项目都拿过来了, 等于没提取
通过先跑起来看看想要的功能模块在那个控制器, 然后找里面的代码, 相关的联系, 然后找到对应的类后通过注释代码, 看看是怎么做的, 理解作者程序, 理解模块运行

网络环境搭建

客户端(前端) >>>>>发送网络请求 .>>>>>>  服务器端..>查询数据>>..数据库
            <<<<<..响应请求, 返回数据.<<<<      ..<返回数据  <<<..>

服务器端用什么的都可以....教程里竟然用了 JAVA....
用 eclipse, 配置教程:/Users/mac/IOS

> 安装 IDE[ 选择 workspace(存放服务器代码的空间, 即项目的位置) >> ] -> 编写服务器程序(或者拿其他的来用) -> 跑起服务器程序(将别人的服务器程序扔到 workspace) -> 在导航栏右击.import.import.general.exsistProject.选择程序 -> 打开看可能会报错, 因为没有配置服务器环境 - (如果乱码: Preference.General.ContentType.JAVASourceFile.defualtEncoding = UTF-8)
> 服务器程序需要有个容器才能跑起来, 一般用 apach, window.showView.Server, 在 debug 栏有 Server 窗口, 右键.new.Server.apache.tomcat.选择tomcat 文件夹容器
> 完成后用 debug 运行, 可以在 consol 中看到
> 服务器跑起来后, debug 运行还是显示不出来的, 先停掉, 在项目顶文件夹右击.debug as...选中要运行在的服务器, 勾选总是使用这个服务器, 完成
            服务器要运行, 接着服务器程序要运行在上面, 所以有两次运行, 保证在服务器上运行
    * 通过服务器中文件地址(url)来获得服务器中的资源文件
    * 通过服务器告诉我们帐号密码正确与否  --- 通过返回的 data
    * 本机的服务器地址: 127.0.0.1  |  路由分配的 ip | localhost | + 端口号
    * 资源位置: 服务器地址 + 资源目录位置
    * 通过服务器的各种接口想服务器发送请求, 获取资源. 或者是做什么事
    * ping 本机是看自己的网卡有没有问题 : ping 127.0.0.1
    * ping 路由: ping 192.168.x.x
    * 真机调试时, 用路由分配的地址, 且是局域网内

http:
    是种网络传输协议, 在不同客户端, 主机, 客户端和服务器之间, 是通过网络连接, 之间的通信不能鸡同鸭讲, 需要遵守协议, 来完成信息的交流和简析
    有很多种网络协议: http: 超文本传输协议, 可以传输任何数据类型, 协议简单, 快速
                        ftp: 共享文件协议<只能访问文件>
                        file:// 访问本地资源
                        ...  只要遵守的协议, 就可以解析传输过来的数据, 进行通信
    协议内容
        请求:
            请求行: 请求方法, 请求资源路径, http协议版本
                GET /.../.../....jpg http1.1
            请求头: 服务器主机地址, 客户端的环境信息,  接收的配置
            请求体: 客户端需要发给服务器的具体数据

        相应: 一个http 相应中应该包含以下信息
            状态行: 协议版本, 状态码, 状态英文名称: http1.1 200 OK
            相应头: 服务器的描述, 返回数据的描述
            实体内容: 返回给客户端的具体数据

利用 iOS 封装的方法发送请求

一般公司里才用第三方框架, 加快开发进度
    ASI  |   AFNetwork

自带: NSConection, NSURLRequest(默认都是 get 请求)
            get 请求的话在 iOS 中的 NSURLRequest 都不用写请求头和请求体, 直接传入 URL 就好了

可以在服务器代码中打断点, 模仿请求延迟,
在子线程中请求数据, 回到主线程来处理返回的相应数据, 并进行蒙版提示操作, 蒙版的操作一定要放到主线程中执行,
    利用方法[NSURLRequest sendAsync...]向服务器发送请求, 这个方法会把 block 奋发到 queue 中执行, 所以 queue 应该传入主队列 NSOperationQueue mainQueue]
    默认是异步执行, 同步方法 sendSync 不推荐使用

客户端的检测操作, 保证输入框中有值, 没有就直接返回, 并提示输入

接收服务器端的数据, 一般网络通信用的多的格式是 json | xml, 有几种解析 json 的方法:
                    * 第三方框架 JSONKit > SBJson > ... (性能排序)
                    * NSJSONSeries  (性能最好)   JSONObjectWithData:///  |   DataWithJSON...

获取输入框的数据, 传给服务器的url 占时是拼接输入框的写死 URL
将获取到的数据解析后, 按照规范会有"error":""""  |   "success":"""", 取出这些数据判断并展示
#GET 请求
    三个方法发送 GET 请求
        * 同步发送请求: sendSync...
        * 异步发送:     sendAsync...
        * 上面两个是类方法直接发送, 这个是创建对象, start, 设置代理, 通过代理来做一些事情
    设置请求超时: 请求响应的情况依据网络环境的不同而不同, 要考虑到极端的情况, 在服务器代码中设置断点模拟超时, 超时返回重试的提示


#POST 请求:
    请求的 URL 中只有路径, 没有拼接用户名和密码, 用户名和密码是放到请求体中发送过去
    在 iOS 中请求行和请求体一般不用我们发, 这里 POST 要发请求体


如果发送的请求中有中文, 还要对中文进行转码, 在拼接成字符串时, 用stringByAdding...Encoding:UTF8String...];

数据加密

直接传送的到服务器的连接, 如果用 http 直接传的话, 连接和其请求头请求体都是明文传输的,
    起码在密码账户方面, 最好不要明文
        不应该用 GET 请求传送密码帐号,
        POST 中是将密码帐号放到请求体中的, 单是如果被抓包的话数据也会暴露
    应该对传输的用户数据进行加密
    一般用非商业性的加密, 很强大, MD5[现在已经不在安全, www.cmd5.com 存储了很多 md5字典, 可以通过 md5值找出对应的密码],
        利用算 md5的分类来计算 md5或者其他算法对数据进行加密, 再传输, 而服务器端就不能再检验明文, 而是也应该是对应的值
    所以服务器一开始存储的就是暗纹, 在用户注册的时候, 发过去的信息就是加密的, 存储也是加密的
结论: 用户的隐式数据, 只有在输入的时候是明文, 进行处理还是传输什么的都是暗纹操作
可以在算出加密暗纹后, 对暗纹进行处理在传输, 那么解密出来的东西就不是原来的明文, 除非知道处理方法, 数据没有绝对的安全, 只能操着就算数据库被破解, 也不能从数据来算出明文
    [但是...然并卵...数据库都被破解了,,,,里面存储的东西也都暴露了...有没有明文密码有什么重要的....]

加载视频数据

通过发送请求, 获取 data, 对 Data进行数据解析, 拿出字典后转换为模型, 其他的和以前一样, 在设置 cell.imageView 时将 url 补全, 通过 SDWebImage 来设置图片,
想做播放的可以试试 meadiaPlayer 库里的类, 或者 AVFoundation
传输的数据可能是 xml, 可以试试解析 xml, 可以用 NSXMLParser

注意点: 设置 cell 的图片时不要有中文名

封装 UIView 分类, 方便设置 frame 或者其他结构体的属性, 因为这个很常用,

完善播放器的基本细节

当应用退到后台时, 播放器会自动dismiss,    --- 当程序进入后台的时候, 会发送通知, 当 movieController 收到通知后就会自动销毁自己, 不想这样的话可以自定义这个 controller, 来移除对这个通知的监听观察
*** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <YCMovieController 0xb6aa0e0> for the key path "UIApplicationDidEnterBackgroundNotification" from <NSNotificationCenter 0xb718180> because it is not registered as an observer.'
取消监听的方法应该是消息名:name, 不是某个 keyPath 的监听
    移除通知后, 每次进去视频播放还在, 只是进度会退回去1,2秒, 其实这个还不错, 不想这样的话, 需要做点具体的操作
    在 MPMoviePlyerViewController 中, 有 MPMoviePlayerController, 因为它才能播放视频, 很多设置其实是对它的设置

完善: 将常用的比较繁琐的代码封装起来, 弄成一个分类放到自己的工具类中, 以后就会很爽了

小结:
    http 请求的过程  及 方法
        GET: 请求行中包含资源连接, 用户数据, 参数有限制
        POST: 请求体中包含用户数据, 参数无限制
    客户端 ---发送请求---> 服务器
        信息包括:请求行: 请求方式, http 协议版本, 资源链接
                 请求头: 客户端的一些数据
                 请求体: 客户端发送过去的实体数据, 二进制
    客户端 <--接收响应---  服务器
                状态行(叫法而已, 相应行也行): 状态码, 状态英文, 协议版本
                相应头: 服务器信息, 返回数据的一些信息: 类型, 长度
                实体内容(响应体): 返回的具体数据
    发送请求的方法,
    NSURLConnection
        同步,
        异步
            直接发送
            代理方法: 代理方法其实都一样, 除非指定Immediatly = NO, 不然创建好对象就会直接发送请求,

XML 解析

与 JSON 同为数据交互的数据格式, 组成部分
    * 文档声明: <?xml version = "1.0" encoding = "UTF-8">, 声明文档版本, 编码类型
    * 元素(Element) <标签></标签>, 标签一般承兑出现, 有内容的: <标签>内容</标签>, 无内容的<标签></标签> = <标签[这里可以添加属性]/>
    * 属性(Attribute)

规范:
    * 必须要有文档声明
    * 最多只有一个根元素, 其他的都是其子元素

语法:
    * 标签成对出现 <></>, 无内容的还可以这样<标签/>
    * 一个元素可以嵌套若干个子元素, 不能交叉嵌套
    * xml 中的所有空格和换行, 都会当做具体内容来处理
    * 一个元素可以拥有多个属性, 属性值必须有引号(单双皆可)
    * 属性的信息也可以用子元素来表示

xml 和 JSON 比, JSON 的比较小, 因其不用写标签名, 体积会更小, 所以公司中数据交互一般哟给你 Json, 节省带宽

解析 xml:
    一般有两种方法:
        * dom: 一次性将 xml 数据加载进内存再解析, 可以获取里面所有任意的节点, 对小 xml 文档比较适用
        * sax: 分步解析, 一条条解析下来, 不能回退, 对大 XML 文档比较适用
    工具:
        * NSXMLParser: SAX 模式, 系统自带
        * libxml2: 第三方, 但是开源, iOS 中继承在框架里了, 纯 C, 性能好, 但是数据类型复杂, dom 和 sax 模式都有
        * GDataXML : 第三方, google 做的, 基于 libxml, DOM 模式
        一般用第一第三种方法
    SAX:
        NSXMLParser:
            创建解析器, 传入数据, 设置代理, 因其是事件驱动型, 所以没有特定事件都会调用代理的方法
            扫到文档头开始会调用, 扫到元素会调用, 元素结尾会调用, 文档结束会调用
        可以把请求数据的方法和处理数据的方法封装到一个工具类中, 减小控制器压力
    DOM:
        GDataXML:
            用到了 libxml2的库, 项目本身没有默认导入头文件, 但是在 iOSSDK 中已经集成有: 按照最上面注释的提示, 在导入框架后, 因为是动态库, 所以里面没哟头文件,
            要自己在提示中的 HeaderPathSearch 中添加这个的路径,
            在OtherLinker,,,中加上-lxml2
            因为 CDataXML 是以前做的框架, 没有 ARC 特性, 还要在 BuildPhase 中设置这个框架为-fno-objx-arc
            里面的类:
                GDataXMLDocument : 代表整个 xml 文档
                GDataXMLElement  : 代表里面的元素
                用 attributeForName来获得元素中的属性节点, 再取出其中的值
                1. 先从 data 中加载出整个 xml 文档,
                2. 取出 root 元素
                3. 取出 root 元素中某个标签的所有子元素数组
                4. 遍历元素数组 -> 元素转模型[取出属性节点->转换为值->赋值到模型的对应属性上]
                5. 将转换好的模型加到数组中,
                6. 返回这个数组, 控制器拿到数组后刷新 tableView

文件下载

文件下载有很多小细节在里面, 如果文件一大, 就要优化处理,
    小文件下载可以使用 nsdata datawithContentOfURL:], 或者 NSURLConnection 来弄,

大文件下载

大文件下载需要实时监控下载进度, 所以需要代理来弄, 用 NSURLConnection 创建对象, 设置代理, 接收数据,
    注意: 直接在内存中拼接数据, 连接结束时再写到 rom 中, 会使得数据留在内存中, 文件多大就占用多大内存, 在手机上会崩的...,
            应该边接收数据边写到 rom 中,
            大文件不要放到 Document 中, 会和 profile 一起被同步到 iCloud 上

将传回的数据立即写到 ROM 中, 不要占用内存
    1. 发送请求, 如果错误就返回报告
    2. 当接收到响应时, 用 fileManager 创建一个空文件, 在创建一个句柄即控制把手, 来对这个文件操作
    3. 接收到数据时, 通过句柄将 data 写入到文件的最后,
    4. 资源加载完毕时, 关闭句柄, 并移除

暂停, 恢复, 进度条
    1. 设置属性在接收到响应时记录文件应该的大小(即总大小), 在接收 data记录累加当前下载的大小, 相除得进度, 赋值进度条
    2. 按钮暂停后取消连接, connection 对象起作用. 并将对象 = nil. 因为 cancel 后就不能继续, 应该释放
    3. 重新继续时, 重新创建链接, 并在请求头中设置属性 Rang, 告诉服务器从当下下载的大小开始发送数据.

封装,
    将下载操作封装到类中, 每下载一个资源就创建一个下载器下载, 控制器不用关心过程,
    传入控制器必要知道需要告诉下载器的参数
    下载器通过 block 属性, 将需要传递给控制器的数据通过 block 参数传递, 当 block 过程可以由控制器设置来做什么事
压缩
    如果图片多的话, 一般服务器是压缩发送, 在客户端再自行解压使用, 指定哪个 zip 解压到哪
    用 ssZip...可以, 记得导入 libz 框架 , 将指定文佳家压缩到哪, 或者路径数组压缩到哪,

多线程断点下载

一样的道理, 只是将下载的文件分成几个部分来下载, 每个本分用一个线程下载, 每个线程用 donloader 管理, 所有线程管理的 downloader 放到 multidownload 来管理,
面向控制器只用一个 multidownload 就行,
    multidownload 开开启和停止所有的下载,
    线程的具体下载放到 singleDownLoad 弄,
    都有的属性可以抽取到父类中
注意: 拼接 Data 时, 是在之前断点的地方, 所以在写入 data 之前不要累加 data 长度, 在写入 data 后在累加 data的长度, 以待之后定位写入

文件上传

等下的上传同 POST 的 Http 协议的用法不用掌握, 等到使用第三方框架的时候才需要掌握他们的用法

在 上传文件的操作中, 一定要设置请求头, 描述上传文件大小, 文件类型, 表明要上传文件, 以及参数分割线: boundary=--------------2892198随机数2199819
    在请求体中 , 先用分割线来标示一个参数传递, 所以分割线就代表了一个参数传递,
        分割线下面跟着参数的描述, 然后换行, 再换行空一行再接着参数
         再传递参数的话就要先分割线, 再传参数,
    (分割线可以是任意字符串, 但是绝对不能有中文和空格)

POST 发送文件,
    connection  Request, -> 设置 请求头, 内容描述, 文件类型, 然后是文件实体
    按照 http POST 上传文件所要求的格式, 将变动的地方转为参数, 将这一大堆东西封装起来使用, 提高代码的重用绿

    注意: 分割线的设置不要带-- , -- 在使用分割线的时候再拼接上去, 以标识参数开始或结束

请求缓存

有些资源相对比较固定的, 我们会将返回的相应和 data 缓存起来, 下次用的时候就不用去服务器发送请求, 直接拿缓存的用
一个 request 对应一个 NSCacheURLResponse, 可以通过 NSCach 对象获得这个 request 的缓存, 还能通过 standare...拿到缓存管理, 删除某个缓存或者全部缓存
可以对 request 设置缓存策略, 来决定怎么使用缓存

网络状态

网络应用需要在某些时候知道当前手机的网络状态, 好对应用的使用做出使用策略,
check 网络状态  :
            /**  看看是否联网  */
          if ([Reachability reachabilityForLocalWiFi]) {
              NSLog(@"可以连接 wifi");

         }else if ([Reachability reachabilityForInternetConnection])
         {
             NSLog(@"可以连接流量");

         }else
         {
             NSLog(@"不可以联网");
         }
不仅这样, 还要在状态改变时能做到监听, : 注册消息通知, 当网络状态改变的时候, reachability 会发出通知, 接收到通知时就用上面的方法判断是真没状态, 再做相应的操作

ASI

使用第三方框架可以更加高效的开发应用, 缩短开发流程
ASI的使用和自带的 NSURLRequest 差不多, 但是封装了很多好用的简单的方法

值得说的是: 异步请求中, 如果直接获取 respones 是没有东西的, 因为在异步, 数据没返回,
            设置代理的话, 不要重新实现代理方法, 这是内部自己调用的, 重写的话就会认为是你想要自己处理 data, response 等数据, ASI 将不会去处理, 最后传入的 request 里面也是没有 data 的
            要想在完成后获得异步处理的 data, 需要设置代理, 并且在 request 对象设置 didFinishSelector属性, 传入一个方法, ASI 将在请求完成后调用代理的这个方法, 传入数据

POST:
    使用 ASIFormDataRequest * 专门用来发送 POST 请求, setPostValueForKey就可以给 POST 的 Body 中添加参数 , 添加多值参数可以用 add

文件下载:
    因为封装了起来,所以文件下载我门不用关心太多, 类似我们封装的NSURLRequest 一样, 只用告诉上传什么文件, 服务器地址, 对应文件名, 类型即可

    它封装了很多东西, 只暴露出简单的接口 , 我们只用简单对 request 设置, 就能达到很多目的
        断点下载: allow...   除了设置允许回复外, 还要设置一个临时存储文件夹, 回复时会去这个文件夹拿到之前的数据
                                request.temporaryFileDownloadPath = [path stringByAppendingPathComponent:@"vid.zip.download"];
        多线程下载:
        直接设置代理拿到下载进度: (可以直接将进度条设置为代理, 因其内部有 setProgress:方法, 正好)
        可以那网上封装好的控件, 呵护规范的话, 很容易拿过来用

文件上传
    使用也很简单, 使用 ASIFormData...    之前的文件上传失败: 服务器中文件保存文件夹路径最后没有/, 它文件名是拼接前面的保存的, 没有/就会保存到上一级, 命名也会拼接最后一个文件夹
        设置上传的文件: 从沙盒中或者项目中的文件上传, : 需要知道上传文件的具体路径, setFile:forKey:
                        从内存中上传二进制文件(如上传相册或者相机图片): setData:forKey:
                            用 UIImageWriteToSavedPhotosAlbum(image,nil, nil) 可以将图片写入相册
                        拿到UIImagePickerController, 设置来源, 可以调出相册或相机, 设置代理, 可以将选好图片传到代理方法(设置代理后熬自己 dismiss 控制器)
        设置其他的参数: 和普通的请求一样, setPostValue:forKey

    直接上传大文件:
        和上传文件的设置几乎一样

ASIHttpRequest : NSOperation, 意味着可以将其加到队列中,
    这样就能用 Operation 的特征操作 Request,
        通过队列对整个的草走, 挂起停止等等,
        设置依赖
        线程控制,
        多线程控制

还能通过 ASI 来判断是否正在处理网络请求
    设置是否显示联网状态 , setShouldUpdateNetworkActivityIndicator:
        是否后台后继续处理网络请求: ShouldContinueWhenAPPEnterBackground
        设置重试次数: numberOfTimesToRetryOnTimeout

wiarning 之后会学到静态库的方式, 包装项目中都是非 ARC, 不用一个个项目去设置-fno-objc-arc

ASI 的缓存机制

获得 download 缓存对象, 设置默认策略, 在将这个对象赋值给请求的策略

AFNetworking

简单, 基于  NSURLConnection 封装了很多简便的东西给我们用,
    GET 请求:
        获得一个 HttpOperationManager, 直接 GET...就可以, 需要参数的可以拼接参数, 不需要的传 nil 就行,

log 日志增强

在数组和字典中打印对象不能很好的支持中文, 需要写个分类来重写 description 方法, 在里面将要打印的内容拼接起来, 可以在 Foundation 的分类中写, 这样在程序一运行加载框架时就会加载这些分类了



HTTP 协议总结:
    http: 超文本传输协议, 定制网络端点之间tong通信的一种规范, 只有按照一定的规范来, 信息才能交流 , 不至于鸡同鸭讲
        请求: 客户端向服务器发送请求:
            * 请求行: 请求的资源路径地址
            * 请求头: 客户端的信息, 接收什么类型的参数, 系统信息,
            * 请求体: (一般 POST 请求才有, 携带一些文件或者其他的参数信息)
        响应: 服务器对客户端的响应, 回传一些信息
            * 状态行: 对这个请求做出的响应状态, 成功还是失败, 使用 的协议版本, 状态码, 状态英文
            * 响应头: 服务器的信息, 返回数据的类型, 长度等信息
            * 实体内容: 返回客户端的实体内容

    发送请求的方法:
        * NSURLConnection: 同步请求, 异步请求(代理/block)

UIWebView

功能强大, 可以打开多种格式的文件

想要在 OC 中动态改变网页的内容, 需要 JavaScript.
    关键点, 在 OC 中调用JS: webView stringbyJavaScript...
            在网页的 JS 中调用 OC. 搭建桥梁, 当 webVIew 要加载某个 URL 时, 会调用 webVIew 代理的一个方法, 并传入这个 URL, 这时, 在 JavaScript 里调用打开一个 URL, 这个 URL 就会传到代理方法中, 可以截取里面的关键字, 然后取剩下的, 将信息提取出来封装成 SEL 消息,再调用这个消息就好, 之前实现这个消息, 完成某个功能

项目

在打开不同 Xcode 版本创建的项目时, 可能会有一些混乱, 这时候打开项目中.xcode 文件包内容, 删除 xcuserdata文件夹就好

看下å其他项目的操作, 实现了什么功能和效果, 看能不能实现这些效果

刚进去时有需求的时候, 要怎么整理, 规划, 从哪里下手

规范是项目组的话, 有移动开发组(Android/iOS), 服务器组. 一般有接口文档的话不需要过多的交流, 规范化开发可以减少沟通成本, 产品需求组(确定需要什么功能, 什么特色, 产品经理)
    接口文档:
        * 说明接口作用
        * 接口的 URL
        * 请求方式
        * 需要传递的参数,
        * 返回的类型, 内容, 格式.etc
        * 会返回怎样的数据类型, 返回的字段, 键值对应是什么意思

项目组:
    * 移动开发组       开发客户端
    * 服务器开发组        提供接口
    * 产品需求组       设置需求
    * 测试组         测试 bug
    * 美工组         界面, 一般也是产品经理
项目文档
    * 需求文档      需求组
    * 接口文档      服务器组
    * 产品文档      对外界介绍产品的价值, 优势.etc  对拉投资有用
    * 开发文档      开发时的注意

项目架构    看公司的规模
    * UI 界面层
    * 业务类层
    * 网络处理层
    * 工具类层
分工
    * 按功能和模块分
    * 按项目层级分

项目搭建

* 一般, 按照规范的话是先有人打一个框架, 然后将不同的功能模块分配给其他人做, 或者按层级划分, 独立的话, 还是自己来把, 先搭个框架, 按模块一个个做,
* storyBoad 在很多老项目中没有, 而且有些情况下也不好试, 微博项目用代码搭建, 在项目的 Target 中把 MainInterface 清空, 不然系统找不到会报错
* 纯代码的话, 要从 application.m 开始, 自己创建 window, 然后将根控制器放上去
* 项目的图片有时候没有对应的图片显示的话,

注意使用对齐,

设置导航栏主题: 使用 attribute 属性字典来设置各种主题中的配置, 如: setTitleAttribute… dict:{NSFont…, NSShadow…NSForegraound…}
这样就不用在另外用 KVC 啊什么的去改变这些属性了, 用它本来就提供的接口

自定义搜索栏: 继承自 UITextField, 才能自己定义背景图片, 记得设置好内容, leftView 的 contentMode ,

设置 titleVIew, 将按钮设置上去, 增加其 title 的功能, 文字和图形的左右互倒, 直接填充式的布局

弹出菜单,

设置弹出菜单可以自己定义类, 由 cover(隔绝其他控件的操作), container(装载内容的容器), contentView(暴露出来, 给外面设置内容的属性, 即最终展示的 view)
    可以增加一些功能
        为了保证操作的层次不同, 不互相影响, 就不能在父控件来操作, 可以自己加个控件来操作(如 cover),

         封装的东西应该有扩展性, 易用性, 不应该写死

研究 UITabBar 结构

从 tabBar 自身修改它的属性看看能不能改到我们想要的效果
因苹果闭源, 有些控件不开放接口, 我们不能直接使用. 可以通过继承的特性, 用父类指针指向他们, 然后看看他们的属性和类,
    在 viewDidAppear方法里打印能获得的对象, 看看里面有什么, 还可以继续深入
        通过从 NSClassFromString 来获取类名判断某个子控件是什么类, 然后符合的话再继续遍历打印看里面有什么,
        分析子控件里的子控件的某些属性, 看能不能为我所用, 用了能不能达到效果
        通过打印私有类的 superclass, 看看这个私有类继承自什么, 我们就可以强转这个类型来做一些操作
        通过逐级遍历, 判断下是否我们需要的类, 是的话就进行相关操作, 在不确定能进行什么操作时, 可以打印看看继承自哪个类, 就可以利用父类的属性来进行操作
   ~~ 在 iOS7中, 选中的 tabBarButton 没有背景图片的属性, 不能判断是否被选中, 有个方法
            通过获得当前选中的 item.title, 判断是否与 tabBarButton 中的 lable 一样, 则可知是否被选中, 然后修改选中按钮的字体颜色~~

自定义 UITabBar

默认情况下, tabViewController.tabBar是 readonly 的, 不能直接改, 但是, 我们可以通过运行时机制, 在运行的时将其 tabBar 改成我们自定义的 tabBar
    #import <objc/runtime.h>
    /**
 *   在运行时将自定义 tabBar 替换自带的 tabBar
 *   一般可以在运行时替换的话, 也可以通过 KVC 将自定义的 tabBar 设值到对应的 key 上, 即 key 为其属性名 :"tabBar", 因为 KVC 是直接赋值给_成员变量
 */

// objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
可以通过打印内存地址查看系统的 tabar 是否换成了自定义 tabBar

看看能不能在数组中通过获取某个类的对象出来 , 不行

 遍历私有类型的子控件, 判断需要进行操作的类, 通过使用父类指针来获取子类成员, 通过父类的属性进行操作, 如果还是不兴的话, 再继续遍历, 知道想要的类出来, 强转为父类尽心操作

靠靠靠! 本来就有可以设置 item 的字体的属性, attribute.

可以设置字体, 颜色, 状态, 像之前设置主题一样, 虽然不是什么黑科技,,,但是一样的效果, 实现更简单易懂...这才是王道啊...
试试: 将 item 的设置 title 属性封装起来, 内部自己设置, 统一起来就好

监听加号点击:

设置 tabBar代理, 监听点击, 内部在加号点击后传给代理, 然后 tabBar 控制器 modal 一个 navC 包着的控制器, 内容在自己的模块设置,

图片拉伸细节:

图片的拉伸方式, 和以前想的一样, 按线来拉伸的话, 拉伸过大, 会造成一个截面横拉. 所以有箭头的地方, 不要拉伸太大, 尽量使用箭头所在线是长的, 这样就不会过多的拉伸箭头

版本新特性

当第一次使用一个新版本时, 会有个版本新特性界面, 根据对版本的判断, BundleVersion = ? . 来加载 rootViewController, 首次就显示版本新特性, 不然就是 tabBarViewController
从沙盒中获取上一次保存的版本号: [NSUserDefult...] valueForKey:];
获取当前运行软件的版本号: [NSBundle mainBundle] infoDictionary]; key:CFBundleVersion...
因版本号可能是1.1.1....所以不能转化为数字, 可以直接判断字符串是不是相同. 一般来说更新版本号都是往上的,
    不相同就显示版本新特性, 然后在将当前版本好保存, 点击后显示 tabBarController
    相同的就直接显示tabBarController
    记得 makeKeyAndVisible!!

从 info.plist 中获取版本号对应的 key, 还可以 用 kCFbundleVersionKey, 可以获得 key, 但是是 CF 类型的, 转换成 NSString *类型需要桥接__brige_type

不同屏幕适配图片

在不同的屏幕间适配图片时, 加上去的图片有时候美工做的命名有568@2x, 意味着在4寸屏上使用, 但是加上去的时候下面会显示unassighn, 表示这个图片项目不可用,
    这样在编译的时候就不会把这个图片放到沙盒中, 应用就不能找到这个图片, 自动加载@2x的只有启动图片

可以将设置颜色的代码封装成宏或者分类

pageControl设置跟随滑动来指示分页时, 过半分页可以使用四舍五入的方式, 在算出第几个点时+0.5, 就不用在开始时加上半个屏幕

图片的加载:
[UIImage imageNamed:@”home”]; 加载png图片

一、非retina屏幕
1、3.5 inch(320 x 480)

  • home.png

    二、retina屏幕
    1、3.5 inch(640 x 960)

  • home@2x.png

    2、4.0 inch(640 x 1136)

  • home-568h@2x.png(如果home是程序的启动图片,才支持自动加载)

    三、举例(以下情况都是系统自动加载)
    1、home是启动图片

  • iPhone 1\3G\3GS – 3.5 inch 非retina :home.png
  • iPhone 4\4S – 3.5 inch retina :home@2x.png
  • iPhone 5\5S\5C – 4.0 inch retina :home-568h@2x.png

    2、home不是启动图片

  • iPhone 1\3G\3GS – 3.5 inch 非retina :home.png
  • iPhone 4\4S – 3.5 inch retina :home@2x.png
  • iPhone 5\5S\5C – 4.0 inch retina :home@2x.png

    3、总结

  • home.png :3.5 inch 非retina
  • home@2x.png :retina
  • home-568h@2x.png :4.0 inch retina + 启动图片

创建了一个控件,就是看不见

1.当前控件没有添加到父控件中
2.当前控件的hidden = YES
3.当前控件的alpha <= 0.01
4.没有设置尺寸(frame.size、bounds.size)
5.位置不对(当前控件显示到窗口以外的区域)
6.背景色是clearColor
7.当前控件被其他可见的控件挡住了
8.当前控件是个显示图片的控件(没有设置图片\图片不存在,比如UIImageView)
9.当前控件是个显示文字的控件(没有设置文字\文字颜色跟后面的背景色一样,比如UILabel、UIButton)
10.检查父控件的前9种情况

一个控件能看见,但是点击后没有任何反应:
1.当前控件的userInteractionEnabled = NO
2.当前控件的enabled = NO
3.当前控件不在父控件的边框范围内
4.当前控件被一个背景色是clearColor的控件挡住了
5.检查父控件的前4种情况
6.。。。。。。

文本输入框没有在主窗口上:文本输入框的文字无法输入

warning Note: 注意!!!注意!!!!, 在设置 center 之前!!需要先设置好 size!! 不然 center 没有参照, 会设定为原点!!!

不同个分辨率和屏幕大小会加载不同的图片, 根据@2x 来区分, @2x 肯定是加载 retana 屏的, 其他的3.5寸都是加载一般图片, 4寸会加载-568h@2x 的

控件中的三种 inset
contentInset: 整个内容的 inset
imageInset: 图片区域的 inset
titleInset: 文字部分的 inset

切换到主界面:

显示新特性在显示后不用返回的, 也没有设置有按钮让他返回, 所以它会一直:
                            modal: 一直是 window 的根控制器, 不释放, 浪费内存
                            push: 一直在栈底, 也不可释放,
            应该直接替换 window.rootViewController

OAuth 授权

应用要访问某个用户的数据, 就需要用户对应用进行资源授权, 也就是要用户登录, 而为了数据安全, 应用不应该知道用户输入了什么,
    这时登录界面应该由新浪提供, 一个登录网页, 我们就等着接收验证结果, 用户的过程我们不知道, 这个授权就是 OAuth 授权

     * 获取未授权的 request token: 由新浪发来授权登录页面
     * 登录成功后, 获取授权的 request token
     * 用登录成功的 request token 来获取 Access token: 一般登录成功后会跳转到回调页面, 后面有 code, 通过这个来获取Access token

要想获得用户来授权访问用户的数据, 得先要成为新浪的开发阵营的一员,
  * 在 open.weibo.com 中加入开发者-> 创建应用, 填写信息, 填写应用名, 回调页面,
  * 查看开发文档的接口, 看获取授权界面的接口, 需要传递的参数, 然后发送请求过去
  * 获得 response 未授权Request token -> 输入帐号密码登录后会出现授权界面(未授权过的只会出现一次, 当授权后应用获得 Accesstoken 后就不出现了) -> 回调页面
  * 上一步都有 web 端完成, 应用不可知(另外可以一直监听键盘输入, 或者无限截图来获取看输入的密码这种无良的事情),完成后会回调页面, 应用的 webView 代理调用 shouldLoad..
  * 调用代理的 ShouldliadURL...方法时, 判断有没有回调页面的 code 字符串的 rang 来获得 code
  * 提取出 code, 利用 code, 查看 API中获取 Accesstoken 的接口, 传入参数, 发送请求
  * 新浪服务器返回一个用户数据的字典. 里面就包含了 AccessToken, 应该将 AccessToken 保存起来, 之后的访问数据什么的都用到, 直接保存用户字典, 在默认的 AFN 请求中接收的 response 不处理 text/plain 类型的内容, 所以还要做处理,

控制器逻辑的搭建
    * 进到应用首先判断有没有授权, 有了再进到是否新版本的判断, 没有就去加载授权页面获取授权,
    * 用户字典可以用一个模型封装起来, 还要加上过期时间, 返回用户字典时判断过期没, 过期就返回nil,
    * 可以将加载哪个根控制器的功能封装到一个工具类来, 需要判断加载的时候就调用这个工具类就好了
    * 保存和获取用户字典可以交给一个工具类来处理, 统一管理用户字典的获取, 外界不用关心细节,
    *
    * 查看获取home的文档接口, 发送请求, 获取数据(在tableview里, viewDidLoad), 获得数据候记得刷新tableView

内容获取:
    * 查看API文档获取数据的时候, 发送的参数, URL, 返回的数据类型, 结构, 字段, 字典对应的解释等
    * 获取数据候根据数据的格式进行解析, 将字典中相关的数据放到相关的view的控件中展示, 应该将数据封装成模型,
    * 将获得的微博数据字典转成模型
    * 如果属性太多, 模型又很多的话, 一层层转换模型会很....所以用第三方框架来转模型. MJExtension 一行就可以转换多层模型, 简单设置就可以, 很方便
                转换的模型里有数组, 数组里需要A 模型时, 需要在拥有这个数组属性的类的实现里重写上- (NSDictionary *)objectClassInArray 中设定字典, key = 属性名, Value = 类 class
                还可以从文件中的字典加载进来了自动转化为模型
                模型嵌套模型也可以不用管了, 除了模型中的数组也需要模型的需要重写方法声明下模型

检测运行时的代码是 C

OC 的底层是 C, 靠编译器 clang 将 OC 转成 C
将 main.m 坐在路径在终端打开 ->  clang -rewrite-objc main.m -> 生成一个 cpp 文件, 就是运行时的 c 代码
    -> 将 main()中的强转类型小括号都去掉, 可以发现都是 C语言函数, 像某个"对象"发送"消息"
可以在官方文档中搜索 runtime -> The objc_msgSend Function
运行时强大到可以动态增删改方法, 因为它是在运行时操作底层, 因为程序跑起来是在内存中, 修改内存肯定是用 C

现在微博有 SDK, 权限更多, 之后好好了解下, 即 OSS

监控网络状况

为了更好的应用交互, 在不能联网的时候要做提醒, 不能让用户认为是软件的问题, 使用 AFN 来监听网络状态
    AFNetworkReacherbilitymanager    可以将监控网络状态的代码抽取出来....反正也不依赖什么东西...

集成下拉刷新

iOS 的 UIKit 中已经自带有一个刷新控件: UIRefreshControl, 里面有使用说明
刷新的时候调用微博 API 获取最新的数据, 如果直接调用homeLine 方法的话, 会请求最新的 n 条数据直接覆盖原来的模型, 就会有些数据重复加载, 理想状态应该是加载我没有的新的数据, 并把数据放在最上面
每个微博都有个 id, id 自增, 所以最新的微博 id 最大, 且在最上面, 我们应该调用 since_id 这个接口, 获取我们最大 id 后面的微博
    发送请求获取新的 id, (当本地模型有值时才赋值 since_id), 将最新的模型数组 insert 到原来数组的最前面, NSIndexSet *, 确定范围
    刷新 tableView, 刷新后调用 refreshCOtrol 的 endRefresh 回复到默认状态

提示最新微博数

当加载了最新的微博数据后, 获得新模型数组,可以看看里面有多少条, 将新微博数量传递给一个展示新数量的方法, 可以设置一个属性 Label, 当需要展示的时候就 transform 出来, 然后在收回去, 也可以需要时候 创建, 显示完后销毁
过程大概是这样... 进入微博, 来到 home -> 自动调用刷新微博 -> 获取新微博拼接 -> 展示新微博数量 -> 刷新tableVIew -> 回复控件默认同时收起提示
效果是在导航栏后面出来, 所以不能是 tableView 的子控件(会随之滚动), 也不能是 window(会覆盖导航栏), 应该是导航控制器.View, 这样就可以管理导航栏和提示 Label 的层级关系

集成上啦刷新

想看更早的微博的话,在滚到底部的时候, 上啦加载更早的微博, 首先要有个上啦刷新的控件,
    做一个有 Label 和菊花的 view, 添加到 footerView, 这样每次滚到底部就可以看到
        问题: 在刚加载 view 的时候就能看到了, 应该设置隐藏, 监听 tableView每次刷新数据的过程, 判断有没有数据, status.count == 0. -> hiden
    恩...妹的,,,视频少了一集2集, 添加控件后怎么让它上啦刷新和滚动到底部刷新仍然是迷....幸好哟代码..自己看看代码来实现一下,...总结总结...
分析代码: 通过监听 scrollView 的滚动, 获取 OFfset 的位置, 当大于某个值时就判断松手刷新, 显示 footerView 正好是 contentSize-屏幕高度+tabBar 高度

loadMoreData 好像有点问题………使用了不可变数组的方法, 返回了不可变数组实例, 实际上已经不是可变数组了, 报错找不到方法

自定义 UITextView

有时候自带的控件有功能上的不足, 我们就可以自定义控件,
    如果还想拥有地带控件的某些属性, 仅仅是想补充一些功能的话, 应该用继承
UITextField: 有 placeHolder, 不可换行, 不可滚动
UITextView: 没有 placeholder, 可换行, 可滚动
    我们自定义 UITextView
        可以试听一个 Label 来展示 placeholder, 这样我们操作也方便, 面向整个 view 对象
        首先要考虑严谨, 当外界改动的地方时, 什么地方需要重新适配
    控件创建时就初始化label, 的一些属性, 在 layoutSubviews 设定位置
    修改 placeholder 时要重新算 frame
    设置 self.font 的地方也要同时设置 label 的 font

    监听事件三种方式
        代理: 只能有一个代理, 通知外部, 不建议自己做自己代理, 只有一个同代理
        addTarget: UIControl 才有的方法,
        通知: 一对多, 可以监听通知然后做点什么,

    在文本改变时也要做出相应的隐藏 Label 的反应

自定义 toolBar

布局按钮, toolbar 背景色, 按钮的布局平局分配, 监听点击将

> 打开系统相册方法: UIImagePickController

通过将toolBar 上的按钮点击事件传递给代理, 让代理控制器知道点击的按钮, 然后做些控制器级别的操作
控制器中对传入的按钮的判断不应该用其image 来判断, 因为这是封装在 toolBar 里的, 可以通过 enum 来传到代理,

将 toolBar 放到发微博控制器上, 不作为键盘的辅助视图, 可以一直保留. 通过监听键盘弹出通知来随键盘一起移动

textView 完善

* 拖拽
* toolBar 本来就在底部
* 将弹出键盘成为第一响应者放在 viewWillAppear 里,快点弹出来...

选中图片

@@ 将九宫格的代码抽取出来....
通过 UIImagePickerCpntroller 来打开照片选择器或者是相机, 选中图片或者拍照完成确认使用后会回调代理方法, 传回一个信息字典, 图片就包含在字典里info[UIImagePickerControllerOriginalImage];
因为自定义的 textView 在其他很多地方可以用, 所以没必要放一个展示图片的 View 上去, 图片 VIew 另外定义, 有发微博控制器自己添加使用

记得在控制器中 dealloc 里移除对键盘时间通知的监听

已完成因目前模拟器打不开相册, 展示自己放入图片实验, 之后更新后在试一下/// 是模拟器版本或者是xcode版本的问题(xcode7正常)

发送微博,

发送微博的接口对于开放的接口来说分为2种, 一种发纯文字的, 一种带图的, 必需分开使用,
传入要求的参数, 将上传的图片转为 data 拼接上去

发送就好了.....成功的话会返回发送微博的详细数据

首页完善

界面完善
    首页标题: 用户昵称
        调用 API 获取用户信息, AccessToken, uid, 将按钮的标题设为呢喃, 大小根据传入的文字来动态改变(好像我已经做了...)
        在获取呢喃后将呢喃存到账户的属性中, 每次加载首页的时候取出保存的名字显示, 一开始没有的时候默认显示首页, 属性中保存了 name 的话就显示 name,
        (我觉得吧, 在应用加载能访问用户数据的时候就顺便获取了信息保存下来, 加载首页的时候就直接获取账户中保存的 name 来使用, )

bug: 登录时弹窗不退出, 在加载页面的时候有加载两次….准备:运行程序, 跟踪断点

解决: 在认证成功时hide
原因: 点击登录后还会加载某个网页, 加载授权页面的默认页

bug: 获取用户名设置后会显示了名字, 但是按钮的大小没有及时改变, 要重新打开才行

暂时解决: 在获取名字的时候不直接设置 titleView, 而是重新调用 configNav 方法

封装网络请求:

当前写的代码中有很多地方用到了 AFN, 这样存在风险: 当 AFN 停止维护之后, 依赖的项目就没有更多的保障了,
所以应该将业务的逻辑封装起来, 里面实现的技术细节封装, 项目中不需要知道实现的技术细节, 以后更换实现的技术就可以, 而且也更方便维护
技术的更新迭代迅速, 不应该让应用项目强依赖某个技术, 应该设置一个管理类来实现具体使用的技术细节,这样收外界影响的因素就只有自己的类
通过传递 block 形参, 在方法调用完成网络请求后调用这个 block, 并传递一些参数. 这些参数要先判断有没有值, 有值才调用, 不然报错
不要直接将自己方法中传入的 block 传给具体实现框架, 自己用的 block 可能参数的数量类型不一样, 应该在实现技术的调用 block 中调用自己传入的 block
关于发送图片的封装: 参数里面有 formdata 的东西, 还需要研究一下

封装微博业务

* 请求参数面向模型: 请求时传入的 URL, 参数等对控制器来说了解过于清楚, 参数又是字典, 不利于维护
* 请求结果面向模型: 返回的结果也是一个转换 json 的字典, 应该也转换为模型, 在返回给控制器
* 对控制器来说. 应该屏蔽业务细节
* 总的来说, 将微博额定业务逻辑封装起来, 控制器只管做事, 不管其中的细节, 这样在微博的网络逻辑中要改什么具体也只需要在一个地方改, 其他地方不会改动, 屏蔽业务细节, 控制器只需要知道要去做什么, 不用关心怎么做

封装一个业务处理类来专门处理微博的业务细节
    在模型转字典时用 MJ 的框架的话, 对模型的设计不能是多个参数通用, 不然在转成字典时会将不需要发参数发送给 API, 可能会报错
        将请求参数中的基本数据类型转成对象 NSNumber, 这样当没有设置的时候默认是 nil, 不能成为字典的一部分, 就不会将这个键值对传出
        这样可以解决参数通用, 不需要的参数就不设置, 转成字典的时候不会转换 nil 的键值对, 只有设置值的键值对,

    分层封装的原则: 上层只能依赖于下层, 不能跨层依赖, 而且不能反向依赖, 下层不应该依赖上层

之前将获取用户信息封装到了YCAccountTool 里, 这个应该属于用户工具类UserTool, 不应该是授权账户工具

封装业务逻辑:

    * 新建一个模型类封装请求参数
    * 新建一个模型类封装请求结果(返回结果)
    * 新建一个业务类封装专一的结果

不用想着一次就把所有相似的东西都放到一个类里, 为了扩展性和适应性, 可以分类, 之后在将相似的抽取到父类即可, 更加面向对象, 不同的对象,业务逻辑清楚
抽取获取 AccessToken 的业务逻辑: 因为也是想服务器发送请求, 直接可以在业务方法中继承 baseTool, 传入模型, 设定返回类型, 调用就完了, 连 API 的地址都不需要知道, 这个业务逻辑知道就可以了,  httpTool 也不需要知道, 因为url 是不定的

bug: 请求失败: Error Domain=com.alamofire.error.serialization.response Code=-1011 “Request failed: method not allowed (405)” — 请求方式错误, 应该是 POsT

bug: 请求成功后获取用户信息失败: 原因: 读取保存的用户信息时获取到 expire_end = nil; —在归档时只设置了通过字典保存时才计算 end 的值

解决: 重写 in 的 set 方法, 在设置 in 的时候就计算并设置好 end

优化: 可以在父类 tool 方法中直接传入外面传入的 successblock

显示消息未读数

微博提供了接口来获取当前各种未读的信息数, 这些由服务器维护, 我们调用就可以
设置新的参数模型个结果模型, 传入方法(在用户工具类中新建一个方法, 来获取未读数), 获得未读数模型后将对应的数字设定到tabBar 标签上 bageValue, 设定定时器定时刷新获取未读数.
在程序进入后台后会处于挂起状态, 定时器停止, 就不能调用方法获取未读数, 我们应该在它后台时能继续运行来获取未读数, 但是移动设备内存有限, 不能随便后台, 我们需要向系统申请, 来开启后台
UIBackgroundTaskIdentifier taskId = [application beginBackgroundTaskWithExpirationHandler:^{
    [application endBackgroundTask:taskId];
}];
系统会自动判断应用可以留存后台多久, 这个我们不能确定,系统会将优先级高的留存久一点
我们可以做一些配置欺骗系统, 来燃应用留存久一点: 配置info.plist 文件, request Background...声明本应用是音视频应用, 需要后台, 而且在代码里循环播放一个0k 的mp3文件, 调用播放接口. 就可以留存久一点
停止后台会调用一个 block, 我们要在调用时立即结束这个后台

完善细节

NSTimer 在加入到 NSRunLoop 时, 默认是 default 模式, 这个在主线程执行 ui 操作时会停止其他事件, 应该设置为 commend 模式, 这样在主线程执行 UI 事件时也会尽量抽时间去执行 Timer
timer 刷新的间隔时间应该设置长一点, 不然会耗电量大,
间隔时间长的话, 在刷新微博时, 还有未读显示在下面----在刷新显示新微博数的时候清空 tab 数量, 顺便减去应用图标显示的数量
最好还能当点击 tabBar 按钮的时候回到顶部, 如果有未读消息的话就刷新...这个用系统自带的 tabBar 的话有很多的不足

连接抓包软件, 监听手机的网络请求, 看看什么软件发送了什么请求到哪里, 返回什么数据, 我们可以截取下来自己做一个东西,

* 电脑装 charles, 开启代理,
* 将手机连上的 wifi 中设置手动代理, 地址设为电脑 ip, 端口随意
* 在手机上访问点什么, Charles 就能拦截到这些请求和响应

完善主页 cell

* cell 中有多种显示模式, 但是其实就是一种, 可以先把可能要显示的所有控件都加到 cell 中, 再根据某些属性决定要不要隐藏这个子控件

自定义 cell 的步奏

1. 新建一个继承自 UITableViewCell 的子类

2. 在initWithStyle 方法中进行子控件的初始化
    * 将所有可能显示的子控件都添加到 contentView 中
    * 顺便设置子控件的一些属性(一次性的属性, 文字, 颜色, 背景等)

3. 提供2个模型
    * 数据模型(文字+图片数据)
    * frame 模型(里面包含了数据模型, 根据数据模型算出对应的 frame, 对应的模型只要算一次就好, 不然 cell 重复加载会重复计算耗能)

4. cell 提供一个属性传入 frame 模型
    * 将 frame 模型传递给 cell
    * cell 根据 frame 模型给子控件设置 frame, 根据数据模型设置 cell 的数据
    * cell 根据数据模型决定显示和隐藏那些子控件

5. 在 tableView 的方法但会 cell 的高度

对 cell 中的控件进行分层, 可以便于维护, 清晰业务逻辑
    原创内容层
        头像, name, 来源, 时间, 微博内容
    转发内容层
        转发用户 nameLabel(button), 内容
    toobar
        三个按钮
cell 的 frame 分层
    根据内容的分层来对 frame 模型分层
    cell.frame 模型
        原创内容 View.frame
        转发内容View.frame
        toolbar.rect(按钮不用根据什么来该拜年 frame, 可以固定提供)
        ContentView.rect(包含原创和转发的内容)
frame 的模型和 cell 的 view 一一对应, 这样方便管理, 逻辑清晰
每个层只管管理, 到最后一个拥有具体子控件的层时再做计算

完善细节

在 UIView 当中, 需要用到背景色是一张图片拉伸的话, 可以在控件的 drawRect 方法中将图片drawInRect 的方法绘制上去
                        或者这个 retweetView 继承 ImageView, 设置 image...

在 cell 之间空出空隙能看到背景色, 可以设置 cell 的高度高一点, cell 的背景色 clear, 在将整个 detailView 设置背景色
可以设置里面分层的 view 是 imageView, 这样可以设置高亮和一般的颜色

添加会员显示图标, 根据会员的等级来确定图片

------------------------------------------------------2016.6.19

时间处理

在数据模型中重写 get 方法, 这样可以保证通过模型来操作的其他事情都是以我们返回的为准, 在get 中进行转换, 判断
使用时间格式类将字符串转成某个格式的 NSDate, 转换钱要先设置格式者的时间格式, 转换成 NSDate 后, 再设置时间格式者的格式转成我们通常的格式
或者, 在不转换的时候, 用 NSCalender 类, 根据 NSDate 来取出 calender, 可以取出其中的 compennet, 里面包括了日期的各种信息
我们就可以比较年/月/日/时间
下一步对这时间进行分层处理, 排除>1天的, 排除>1h 的, 排除>1m 的...
在每次 get 数据的时候, 都判断当前时间和数据的时间的 delta, 来返回需要是刚刚/几分钟钱/几天前/ 那一年
    用 calender 从 date 获取出需要的组件 Component, 根据需要进行比较, 还能获得两个 date 的差值
在设置 frame 时, 每次赋值 cell.frame 都会拿到时间的 frame 设置到子控件上, 所以在时间.frame 的 get 方法中再次重新计算当前时间的 frame

来源处理

新浪传回的数据是一个超链接字符串, 我们要截取其中的客户端文字,
在转换模型的时候就进行提取保存, 这个数据不变, 我们只需要提取一次
    可以通过获得">的位置获取文字的起始位置 NSRangeof...
    再获得</ 的位置location, 减去之前获得的位置, 就是需要的位置的 range
    取出这个 range 拼接其他文字来返回这个来源属性
但是这里有个注意点, 这个不同与时间, 需要每次 get 的时候都判断, 来源都是不变的, 所以在 set方法将目的字符串抽取出来做一次保存就行

在没词 cell 拿到 frame 时, 因为来源是依靠时间 frame 的, 时间改了, 来源也应该跟着缩进, 在来源 frame 的 get 方法中重新根据时间的 frame 来计算自己的 x就行, 其他的不变

toolBar

在 toolBarView 的创建中直接就跟着创建子控件---三个按钮, 两个分割线
toolbar 也应该继承自 UIImageView, 它也有背景图片
记得调整按钮高亮不调整图片
将创建按钮的方法封装起来, 还有创建分割线的
让 toolbar 交互
按钮的 title 缩进
分割线的中心处在按钮的分割点
在按钮的显示上优化, 有数字显示数字, 没有就显示默认的, 当数字范围是多少时应该友好显示

可以尝试

在用 calender 的时候, 有个不用转成年月日来比较, 直接用 date 比...反正都是 date....
改下返回的数据, 模拟测试环境, 测试返回数据的不同有什么反应,是否有 bug

显示配图区间

* 原创或者转发中都应该有配图属性, 位置就在正文下方, 自己调整, 这个整个 View 里在放各个图片, 统一管理
* 宽高的设置要各部图片的个数来确定, 可以先定死图片的宽高, 最大列数, 总列数即为超过最大列数就是最大否则按图片数
* 知道最多显示多少列后, 可以用公式来知道一共显示多少行:(总个数+每行几个-1)/每行几个 = 多少行
* 图片区域在转发中也有, 应该把计算尺寸Size抽取出来, 可以自己定义一个专门对图片的 frame, 或者封装在图片 View 中
* 将模型中的 urls 传给大的 view, 再取出每个 url 中保存的 pic 模型传给小的 imageView,
* 由 imageView 拿出送略图显示图片, 并且判断是否 gif,决定是否展示 gif 标志图片
* 判断后缀前缀: hasPrefix....转小写: lowerString
* 拿出路径扩展: String.pathExtension  ,

放大图片

* 让 imageView 响应点击, 要先让它使能交互, 还要添加手指识别器, 而且它的父控件都需要支持交互
* 可以通过设定 tag 来识别是哪个 imageView 被点击, 识别器中拥有锁添加到的那个 view.
* 一个手势识别器只能添加到一个 view 上
* 在 photosVIew 中添加手势, 因为在监听点击中获取所有本 cell 的配图, 用 Photo 自己 监听的话太局部了
* 思路: 在点击时立刻有蒙版上去, 且图片显示在最上层.
    * 可以在点击时创建一个蒙版, 然后创建一个与点击 view 一模一样的 view 为其子控件, 在显示后让这个子控件放大至填充
    * 子控件 imageView 的坐标和原来的不同,不再是以 photosView 为父控件,而是整个屏幕的蒙版,所以可以用iOS 的方法转换坐标系
        * 转换坐标系,就是转换参照的原点, 将原来父控件的参照点改为某个, 就可得出转换坐标后的 frame,
        * 元父控件 convertRect:要转换的frame TOView:目的父控件
    * 点击的时候显示出来否放大: 就将整个宽度设为整个屏幕的宽度,教程是直接设置 frame,我用 transform试试,
    * 拿到显示在 cover 上的那个图片, 并且保存转换坐标后的 frame, 在点击回去的时候, 将图片设回原来的位置, cover 慢慢透明,最后完成时移除, 设定中间 frame 时用 x,y 设定也是可以的
* 其实,显示多张图片还有很多都东西要完善, 比如显示下载进度, 展示的图片不应该是缩略图, 应该将Photo 模型加一个原图属性, 点击后加载原图, 缩略图做占位图, 有多张时候可以滚动, 滚动时候同时下载要显示的那张, 还有图片数, 滚动后回去还是对应的位置,
* 涉及到图片浏览器, 合格的应该节约内存, MJ 封装有一个处理很多细节的图片流量其类, 拿来用就能实现基本功能, 可以参考写一个

添加表情键盘

当点击表情按钮的时候, 切换成自定义键盘, 可以先用一个 View 来测试, 需要使用输入框的 inputView, 才能让他自动相应输入框的点击时间, 切换输入框,就可以切换到自定义的 view, 在改变 inputView 的时候, 是没有立即反应的,要重新成为第一响应者的时候才会看到效果, 先登出,在注入第一响应者.
点击时改变按钮的图标, 可以在监听点击事件时, 判断是表情按钮, 就将表情按钮的图片改变. 或者提供一个方法,显示 emotionImage,内部将图片更换,
当点击切换的时候, 不应该让工具条下去, 而是保持: 可以设置一个是否切换的 BOOL, 监听 hideKeyboard 的时候判断这个 BOOL. 是切换状态的话就不做反应
添加表情键盘的时候, 分为两部分, 上半部是表情列表, 下面是工具条(可以直接写在 emotionView 里, 用 view 封装在一个就行, 不用在弄个类,)
设置工具栏按钮的时候, 抽取出方法来, 因为每个按钮的文字都不同, 在添加方法里判断当前添加的是第几个: 看toolBar 里的按钮有几个,添加进去后肯定是最后一个, 所以个数即是它的第几个.新浪根据按钮的位置有不一样的图片, 所以这里要判断,

表情键盘的建立 TT2016.06.25 ~~

给自定义键盘传入 emotion 模型后, 计算出有多少页, 根据模型设定指示器数量, 创建 scrollView 的子 View,数量岁模型,
注意这里是在没词传入 emotions 的时候, 都会为 scrollView 创建子 VIew 添加到上面, 这样会累加, 应该每次添加前先清掉之前的 .
再首次创建 scrollView 的时候才会调用 layoutSubViews,所以在点击切换表情的时候,子 view 是没有 frame 的, 没词创建好后要带哦用 setNeedLayout
每个子 view 都有很多内容和操作, 且都应该自己管理好里面的控件, 应该单独封装起来,
注意, 在最后一页可能会有不够每页的数量,直接取 range 的话会发生数组越界...需要对越界进行判断处理,
每个表情的按钮用 button, 因为 emoji 表情是字符,在系统里转码显示的.每个button应该仅仅挨着,图片直接设置 Image 居中,这样在滑动选择的时候就能一过就是另一个
在用 NSBundle 来获取内容时资源可以是带路径的资源(用与在 bunble 里存在的文件夹里的文件)...图片的话直接 Imagename 传入带文件路径的 string+name.png`也可以拿到 bundle 中文件夹中的资源....
运用数组的 makeObjectPerform...可以让所有数组元素调用某一个方法.设定好文件夹属性后, 就能从 BUndle 里获取资源了,
在点击不同的标签时, 应该让 page 回到第一页:在每次点击时, 设定为第0页, 滚到最开始,
显示 emoji: emoji 在内存中是以十六进制存储, 要把它转为 unicode 字符,可以用一个别人写的分来来转换
emoji 的大小取决于字体大小, 值相当于图片的宽高

生成随机数: arc4Random()

性能优化

* 在表情键盘中每次都要销毁和创建 scrollView 的子控件 gridView 很耗性能, 我们应该对差不多的东西重复利用,
* 在 gridView 中, 每个其子控件 button 也没必要重复创建, 或者,当 gridView 重用后,里面的 button 还在, 不做覆盖处理的话没词传入 emotion 模型又会创建 button 添加到上面, 最后的结果就是刚添加的 Button 被排到很后面, 而滚不过去, 我们也应该对 Button 进行覆盖重用
* 可以用这个思路, 需要的 view 和拥有的 view 哪个多就遍历哪个数量, 当遍历到多过拥有的时->创建, 不然就取出重用(当超过需要的时->隐藏并 continue), 最后拿到的 view 都要取消隐藏
* 每个 button 对模型还做了一些处理, 之后可能还会做跟多处理, 应该把其单独抽出来作为一个类

封装 emotionView

将 Button 封装起来, 传入模型, 在里面判断就可以了

挖掘子控件,

有时候为了调试, 我们可以将整个 window 的 subVIew 打印出来, 看下层级关系, 在遍历的时候将子控件的关键信息拼接成字符串给 xml, 在浏览器中打开就可以清楚的看到层级关系

单击表情按钮弹出提示 View

监听按钮点击, 用 gridView 监听, 因为还要用到一个 popView 的东西, 没必要重用,
将点击按钮整个传入, 就能知道按钮的内容和位置
创建一个 popView 将点击的按钮显示出来, 里面要有一个 emotionCell 来展示点击的按钮
在设置按钮的时候 去掉动画, 设置完后拿回动画

长按表情

添加手势监听长按
判断当前点是否在 emotionCell 的范围, 拿到判断返回的 emotonCell
没抬起手时, 结果传给popView,  nil 就不做操作, 不然显示出来,
当收抬起时, 发送通知, nil 的话不做操作,
点击删除按钮也发送通知
在发微博控制器监听通知进行操作

最近表情

当点击表情能发送通知时才是确实发了表情, 这里应该放到最近表情中保存一份
    可以使用单例类, 使数据在内存中只有一份, 这个类可以做一个管理者
    也可以使用工具类, 创建几个 static 全局属性, 也可以保证全局中只有一份. 这种做法好像比较方便
最近表情可以从沙盒中获取这个最近表情数组, 通过 NSCoding 将 emotion 模型保存起来, 之后拿来调用放到 emotionCell 中
记得保存自定义对象时还要用 NSKeyedArchive 类
???? 解码过程中碰到有的关键字是没有对应到的

bug: 在重新打开应用后表情键盘添加到最近不能判断出原来已经有了,

---- 添加的时候判断手否存在时是靠传入对象的-isEqual 方法, 逐个传入自己数组中的每个元素进行内存地址判断,一样的就返回 yes, 但是重新打开后是不一样的地址.
重写-(BOOL)isequal:方法, 其中判断改为判断 png 或者 code

bug: 在没有键盘时点击表情按钮键盘出来, 再拖拽文本框键盘下去,但是 toolBar 没有跟着下去

--- 在按钮点击那一刻 isChanging = yes -> 退出第一相应者 -> 因为还没有键盘不会发 hide 通知 -> changing=No 不能调用, -> 代码过不到收回 toolbar 那里,
    ---- 应该把 changing = NO 卸载退出第一响应者后, 不管能不能收起键盘, 操作完成后都回复 changing 状态





小问题, 实时刷新的话, popView 还在显示本来 emotionView 时那个就跳到前面去了, 应该等 popView 消失时在刷新

1

图文混排

图文混排的几种方案
    * coreText, 非常蛋疼, 纯 C
    * NSAttributeString, 属性文本, 付文本, 里面提供了很多设置文字各种属性的接口, 使用简单
    * TextKit, 使用简单, 封装好, 好用
    * 第三方框架, 大多数是基于 CT 的, 提供了比较便利的接口, 能在富文本中显示 View 控件

NSAttributeString: 属性文本

   * 对文字操作, 只需要将对应的属性设置给对应的文字(一般通过 range)
   * 创建可变属性文本, 之后就可以对这文本设置一些属性
   * 看接口有一些常用的属性: 颜色, 大小, 背景色, 下划线,
通过附件简单添加图片:
   * 通过 attachment 属性给属性文本添加附件, 附件可以显示出来, 和文本排在一起
   * attach.Image 可以设置图片.
   * 通过 attach.bounds 可以设置附件大小, 要和文字一样就设成文字大小即可

UIWebView 显示图文混排

利用  HTML 的标签来给 HTML 文本设置属性
    * HTML 本来就是标签语言, 通过标签元素来给其中的内容设置属性,
    * 但是 webView 有缺点, 会有内存泄露, 但是通过这个排版代码量少, 不过需要知道 html 语言, CSS, 等网页相关的知识,设计连接简单

富文本的简单应用

* 发微博控制器的标题, 换行, 大小不一样,
        直接用不好用, 可以设置 titleView = Label, 在用 Label 设置富文本
* 将表情点击后添加到文本框,
        当表情点击后通过通知传递给控制器, 拿到 emotion 模型创建好 image, 创建 attach -> attributeString -> 添加到文本框拼接
        最好在从 textView 抽取一个子类, 将传入 emotion 后拼接上去的方法封装起来.
        文本和富文本的 font 控制方式不同, 要统一起来
        对文字输入的判断, 代码直接设置的不会有通知调用, 需要在设置表情时自己调用, 并检测富文本的长度, 它包括的范围广, TextView 中的都包括
        在拼接表情时, 还需要判断是 emoji 还是图片, 处理方式不同,
        取出当前富文本, 拼接表情, 设置回 TextView 的富文本

* 表情中的删除功能
        textView 自带有 deleteBackspace, 调用删除功能删除

* 插入表情到文字中间
        需要获取光标, 虽然有 insertText: 但是只能插入 Text, 不是富文本. 但是我们发送给新浪的数据又是字符串的, 所以我们应该是插入文本, 解析字符串中的 chs, 来显示成图片,
        先拿到当前的文本, 要通过正则表达式来匹配出其中的 chs, 不然用 range 搜范围会很麻烦
            正则表达式:
            > 有一定的字符串表达式, 根据规律来才能匹配一定格式的字符串
            > 作用: 匹配
            > 创建正则表达式 -> 设定匹配规则 -> 拿去匹配字符串 -> 返回数组, 数组.count 有值就是成功, 失败会=nil
        系统自带的正则方法封装用用, 但是想抽出匹配的字段会比较麻烦, 推荐用老外的第三方.需要导入 libicu,,,,
        封装了很多简单调用的方法, 可以容易获取到匹配的字符串, 还能获取根据匹配规则来分割的字符串

    attributeString 有获取当前光标选中范围的方法, 还可以插入到某个范围, 完了后把富文本传回给 textVIew 的富文本属性

    发送微博时发送的是字符串, 不是附件图片, 所以应该把文本框中的富文本中的附件图片转成对应的描述文字, 再发送
        * 用富文本自带的方法遍历其自身中的每个区域, 会用字典传出, 并且带有范围,
        * 用一个普通可变字符串拼接其所有转换的字符串返回
        * 取出附件字典对应的值, 空则为普通字符串, 直接拼接, 非空就取出附件, (我们重写附件让其带有 emotion 属性) , 拿到 emotion.chs 拼接到字符串
        * 或者这个 textView 再设置一个 text 属性, 没词点表情时在这个里面保持纯文本

    显示表情在其他 Label 中, 即将里面的表情描述转换成富文本显示出来
        * 这么弄的话显示出来的肯定是富文本, 那我们直接展示富文本到 Label 或其他地方上,
        * 可以在模型中添加一个富文本属性, 当 cell 拿到模型直接设置富文本上去.
        * 当转换到模型时设置 setText方法, 里面将纯文本转换为富文本.
    解析文本为富文本
        * 用正则遍历匹配字符, 再遍历正则分割的字符串,这样就能拿到原文本中整个文本分割后的纯文本和表情描述.
        * 拿到文本的直接拼接进富文本, 插入到rang.loc 位置,长度是 loc+len, 表情的话找到对应的表情,拿到 attach,插入 rang.loc,len=1, 但这个应该有一个问题, 当插入的位置是当前 range 中不存在的时,可能会有数组越界错误
        * 或者,保险一点的 将匹配遍历出来的文本和图片描述都保存到一个对象模型 result 中, 在通过对数组进行位置判断的排序, 得出元素按照位置重写排列好的数组, 再遍历数组,是文本的话就拼接富文本, 图片的话要遍历 emotions 模型,看看是那个图片的描述,取出对应的模型,拿到图片弄给 attach
显示@...#...#等关键字高亮
    * 这些关键字必然存在存文本里, 所以在之前将 string 分割后, 得到的数组中遍历里面result.string, 当每个元素 string 中存在正则匹配时,转成的富文本中将对应 range 的文字设置属性为蓝色, 或者好像可以设置连接属性
    * 其他的关键字匹配也是用这种方式
    * 注意: 富文本的文字大小哟给你同样的 font计算出来可能 和纯文本的不太一样, 导致显示在用 font 计算出来的 label 里显示不全

bug: 当收到的 emoji 中有不能识别的 emoji 时, 同时还带有中文紧贴着,如😍✌🏻️✨在 xcode 中和在这里显示不了那个未识别的字符,隐藏了, 调用 boude…计算 rect 时,会报错访问坏内存. 和中文隔空格,或在未识别的 emoji 前或后隔空格,可以正确通过, 本身打印没问题,主要是调用方法上有问题

估计是在 xcode 的编码中的设置问题. 或者是NSString 在转码的时侯和调用 bounds…的时候有点问题

小问题: 长按最近表情, 会有隐藏的表情弹出来: —- 长按后的判断条件加一个非隐藏才做反应

处理关键字点击事件

* 自定义控件来显示文字并处理关键字的点击事件,
* 将显示文本的 textView 作为控件的属性, 里面提供了一个方法来获得某个 range 的范围
* 封装在控件里而不是继承可以不将 textView 的接口暴露出来, 不让别人对其设置
* 尝试下有没有属性可以设置 textView 的内切设置为毫无内切, 即便是0000也没有, 注意是 textView 的 containerInset, 不是 UIView 的 ContentInset
* 设置 textView.userInteracte = NO;  但是可能会有显示不了某些控件属性的问题,需要设置其他的禁止滚动和禁止编辑才可以减少这种情况的发生
* 实现这个自定义控件的触摸方法, 拿到当前触摸点, 判断触摸点与关键字所在的范围是不是相交, 相交的话就高亮并跳转
* 拿到当前点击 View 中关键字所在范围:
    > 因为 TextRange 是抽象类, 不能直接用, 我们可以先选中某 range 文字 -> textView.selectedTextRange拿到选中文字的 textRange -> selectedRect...TextRange拿到选中文字的 rect -> 看看是否相交, (有时候会计算出宽 or 高为空的情况, 这种要进行排除)
    > 要这样, 首先要知道那些需要选中的文字: 关键字, 拿到关键字:在正则匹配的时候, 多设置一个属性(可以自定义key)来保存关键字的位置和文字, 之后拿到这个属性的时候, 对应的会有这个属性设置文字的 range, 拿到 range 就可以进行上面的步奏
* 设置一个数组属性, 将当前控件中属性文本中的所有链接保存起来, 不要重复遍历寻找, 链接对象要在这里算出 rect, 不能在 status 模型中国年算, 模型中不能根据当前 label 控件算出对应的 rect 值. 在重新设置了属性文本后, 要把数组 nil, 因为里面的链接已经不一样了
* 当点击时, 遍历数组, 看看有没有和其中连接中的 rect 相交的, 找到选中链接 -> 将选中链接中遍历 rect 设置上背景
* 在结束点击抬起手的时候移除背景: 在 touch end 方法中遍历数组, 将其中的所有 rect 设置空背景,或者将选中连接中的 rect 清空或者将设置的背景保存起来, 还可以将背景 view 设置 tag, 移除时遍历 self.subViews 找 tag 来移除,结束点击的时候移除. 触摸取消时也要移除
* 当触摸 end 时, 判断触摸是否还在链接上, 是的话就发送通知告诉外界链接被点击了, 让控制器好做事.
* 控制器监听通知, 链接被点击时, 判断链接是 http 的话就用应用打开直接打开,会跳转到 Safari, 不是的话就用可能是跳转控制器, 以后处理
* 话题和@的链接可以用新浪的接口, 通过控制器展示.

发现和我的事件处理

* 因很多个控制器的界面差不多, 仅有几点控件和数据不同, 可以做一个父控制器, 共同拥有的元素放父控制器里, 再子类化出具体功能的控制器,
* 数据模型也要进行子类化, 父模型决定一样的东西, 子类模型决定模型的类型和特有的东西, 分类处理可以功能清晰, 逻辑清楚
* 模型分类使用子类化, 辅助视图判断模型的类型来做区分显示不同的控件
* 数量显示: 当优先判断 cell 模型的 bageValue, 有就显示, 没有再做类型的判断
* 最后一个退出标红的比较特别, 可以直接用 footerView 放个按钮.
* cell 的背景设置: 一般来说应该提供4种背景, 有四种不同情况的 cell, 第一行, 最后一行, 中间的那些行, 仅有一行
* item模型中的属性可以设置一个类属性, 即这个item要跳转的类
* 还可以设置一个block的属性, 即这个item点击候要进行的操作

bug: url 的匹配不正确, 当 url 后面跟着字词之后不会隔开, 会被误视为 URL 的一部分— 找另一个正则来替代

coreText 初试

用显示 string 做例子, CT 的方法很麻烦…..
147.72.4.211

微博详情页

可以将 cell 中的 detailView 拿给详情控制器的 headerView 用
将不同的东西做一个分类判断决定要不要显示.
传入模型 frame, frame 中加入对多出来控件的计算

可以将 toolbar 共同的东西抽取到一个父类里, 不同的东西在自己设置, 和对 StatusToolbar 的设置差不多
根据是否显示在详情页来显示详情的 toolBar, 这个 toolbar 可以作为转发 view 的子控件. 详情中有三种 toolbar,
API 获得某条微博的评论数,转发数,  显示在评论的 tableView 里,
每个 toolBar 的功能都很像, 获取所在微博的三个数字来显示出来,
topToolBar 在获取用户评论的时候还能获取总数

当被点击时, label会拦截点击事件来判断是否点击到链接, 这样没点击到的时候就会判断, 我们在判断完都不相交的时候发出通知被点击

应用发布与打包测试

未做

#未做: 将发图片的操作封装到工具类中

bug(访问 http 出错): 用xcode7之后, 会默认拒绝自认证的链接, 导致微博项目不能访问授权页面, 在info.plist中加入:

NSAppTransportSecurity


NSAllowsArbitraryLoads


或者想添加例外或者更多连接网络方面: http://stackoverflow.com/questions/31216758/how-can-i-add-nsapptransportsecurity-to-my-info-plist-file/31629980#31629980

SQLite

* 当做离线缓存的时候或者保存数据到沙盒中时, 之前学的 NSKeyedArchive/NSUserDefault/Plist都不适合存储大批量的数据
* 用在手机上等嵌入式系统的应该用轻量的和快速的, SQLite就很适合,
* 用SQL语言写出来的SQL语句来在代码中在内存中操作数据库
* 本质上讲, sqlite是无数据类型的, 就算设定来数据类型还是会存什么都可以,但是数据类型显示可以比较规范, 易于沟通
    * 数据定义语句: DDL>>用来创建和删除表: create if_Not_exsist 表名  |  drop if_exsist 表名
    * 数据操作语句: DOL>>用来对数据表中的数据进行增删改查的操作(CURD)
            insert into 表名 (字段) value(值)
            drop
    * 改删操作没有条件限制的话会对整个表格操作, 改就全改完, 删就全删完
    * 条件语句:where : 当> 给前面的改删添加限制条件, 限制记录. where 字段 = 值,
    * 查询语句select 运用最多的SQL语句, select (字段(*表示通配) 字段) from 表名
    *   排序, orderby 字段 升序降序
* 在iOS中使用和操作数据库
    * 增加表字段: ALTER TABLE 表名 ADD COLUMN 字段名 字段类型
    * 删除表字段: ALTER TABLE 表名 DROP COLUMN 字段名
    * 修改表字段: ALTER TABLE 表名 RENAME COLUMN 旧字段名 TO 新字段名
    > 导入libsqlite.dylib库, import主头文件, 就能使用其中的所以函数
    > 创建数据库, 传入操作句柄和文件路径名
    > 使用执行语句exec可以执行任何sql语句,拿来创表
    > 调试技巧: __LINE__ : 表示这个所在的行, __FILE__当前所在的文件
    > 模糊查询: 调用查询语句, 将要查询的字符串搭配通配符来查询
    > 纯C的函数写起来比较复杂, 可以将这些操作封装在工具类中, 方法根据需要传入参数和返回类型. 根据外界沟通的必要来设置就行, 不必要就封装, 不一定要每个参数都暴露在外界. 对于内部重复利用的C代码, 看情况来封装.


    试试查询语句在nativeCat上怎么样,  可以, 就是用错来like成=
    实现模糊搜索  使用like而非=

直接删除记录的话再添加新的记录上去, 自增的主键id会跟着之前的id增加, 因为记录id的文件还在, 要像重新开始, 要drop原来的表, 再创新表再插数据, 才会从1开始

FMDB的基本使用

* 里面所有除查询的操作都视为update, 用upte来执行sql语句
* 查询会返回一个result, 通过result next来获取查询到的每行数据, 里面可以用int/text forColum:来获取某个字段值


* 使用线程安全的数据库: 用fmdabQueue 对象
* 传入一个路径文件, 会自己会创建一个数据库, 这个数据库是线程安全的, 提供方法来对这个数据库进行线程安全的操作, 并不能直接拿到这个数据库
* 在block中对提供的db操作是线程安全的
* 即可认为是拿到来操作这个数据库的队列来操作数据库

事务

有时候操作数据库时, 需要多条都执行成功才算是正确, 但是有时候执行到一半就因谋些问题断掉了, 这样就造成来数据错误或丢失.
这就要有个事务的概念, 当事务中的事情都完成时, 才算是完成事务, 要是美完成, 就回滚到原来的状态, 数据不会丢失.
在FMDB中begin transaction  /  commint transaction 来包装一个事务, 事务没有完成会自动回滚.她封装来这两个代码, 直接begin和commit就行
我们也可以在中途自己判断错误手动回滚. rollback

使用数据库完成微博的离线缓存

* 因为之前已经把加载微博数据的代码封装在业务类里, 我们只需要改动返回微博数据的业务类对应的方法即可.
* 先从沙河中加载数据: 判断, 有取到数据:显示 ? 没有取到:发送请求加载并保存到数据库, 存入的是字典
* 缓存方案, 主键id, 微博用户标识:access_token, 微博标识:id_str, 微博:statuse_dict
* 字典直接存入的话会被转换成字符串, 应该先把字典转成二进制数据, 然后在存入数据库, 取出数据库的二进制, 再转回字典. 字典转模型添加到数组返回
* ????能不能把模型直接存进去, 取出模型也直接一点 //// 最好不要, 要用data'存取, 还要在模型中coder和decoder, 属性一朵就回很麻烦!!

bug: 排序查询错误: desc asc排序要在查完表后首先做, 然后才是where来筛选

清除图片缓存

* 直接在设置里添加一行, 点击即清除缓存, 行中显示缓存大小
* 拿到缓存大小: 首先要拿到文件路径, 通过SDWebImage可以拿到她缓存文件的路径, 再用这个路径, 通过fileManager来获取其中的所有子路经,拼接出文件夹中所有文件的全路径, 计算文件的大小并加起来, 就是总大小. 返回显示.
* 有时候子路经也有可能是文件夹, 要做点判断: 传路径来, 判断是否存在, 不存在直接返回0, 存在判断是否文件夹, 文件就返回大小, 文件夹就递归调用
* 可以将这个计算路径大小的方法写成分类
* 清除缓存就直接拿到这个缓存文件夹删除就可以, 之后重新缓存的时候会再创建
* 可以把中介弱指针的创建方法写成宏来创建弱指针

建议: 不要使用宏和pch文件了. 每个.m文件中包含所需的文件, 这样耦合减小, 编译加快. 要是嫌太烦可以手动添加pch文件. 建议用静态常量替代宏

4.) 找到 Project > Build Settings > 搜索 “Prefix Header“;

5.) “Apple LLVM 7.0 -Language″ 栏目中你将会看到 Prefix Header 关键字;

6.) 输入: TestDemo/TestDemo-Prefix.pch (如 TestDemo/TestDemo-Prefix.pch );

7.),将Precompile Prefix Header为YES,预编译后的pch文件会被缓存起来,可以提高编译速度。效果如下

网易新闻

从架构上来看, 史一个根控制器上有两个侧边视图, 还要一个导航控制器来显示主页, 之后回有更多的控制器, 点击跳转过去
左边视图的设计: view上创建几个按钮, (可以将按钮的创建方法封装起来, 创建按钮操作很频繁) 设置好大小和距离, 内容边距. 关于按钮背景色史选中才出现, 关于状态只能用image, 所以要用CG来创建一个纯色的图片返回来用
同样的在自己用的控制器要封装起来    关于title可以将一个button封装起来, 在本项目的其他的控制器中可以重复使用, 只是标题不同嘛
点击显示左边菜单: 新闻控制器view应该右滑和菜单贴边高度一样, 要注意缩放时回将平移距离也缩放了, 要乘回来, 计算平移的距离比较麻烦
控制器的切换:
    在点击按钮的时候, 传出按钮的tag给代理, main控制器移除原来的控制器.view, add新的控制器.view
    可以尝试设置一个showView属性, 切换时就切换showView所指的控制器, 不过要想同时看到隐藏一个显示一个的效果的化, 就不能这样
    將設置子控制器抽出來放到main控制器里, 可以统一设置子控制器的主题, titleView, 左右按钮等, 并为其包装导航控制器, 添加到自己main控制器的子控制器中, 左右按钮的点击事件拿到main控制器中, 这样就从main控制器的层面来切换子控制器, 就想tabbarController一样
    将要移除的控制器的transform赋值给要显示的控制器, 在用动画把transform清空, 就能有动画效果回到原位置
    在需要清空transform的时候, 可以直接调用点击遮盖, 等同的效果, 不要重复做
    需要对选择view中的按钮做适配, 让它点下去就显示背景, 取消按下高亮状态时系统对按钮做的操作,--自定义按钮中重写hight方法,什么都不做
    bug: 当一个导航控制器的View首次显示到其父控件上时, 如果transform的缩放值被改来, 上面的20高度当时是不会出来的 ---- 调换下顺序就可以, 在修改值之前, 先添加到父控件上,
    将按钮贴边: 遍历导航栏的子控件, 看按钮的x值来判断左右按钮, 再将按钮的x值更改, 应该在控制器的viewDidLayoutSubViews方法中更改
        但是有点问题, 会看到按钮的移动
        最终的解决方案: 自定义导航栏替换掉导航控制器自带的导航栏, 用KVC替换, 在自定义导航栏里设置子控件的位置
        猜想: 导航栏中item有没有对应的设置来设置缩进位置的

显示右边的菜单:
    如果一个view里的业务逻辑比较多比较复杂, 我们可以用一个控制器来管理这些事件, 而不是将view的事件丢给底层的控制器
    用xib将右边菜单做出来, 因为相对固定
    菜单位置和左边菜单相似, 方向和宽度不一样, 最好用2个属性拥有这两个菜单, 当一个显示时, 另一个隐藏
    菜单中图标的 反转:
        CATransform矩阵
        转场动画transition  (之后要把之前项目中的笔记搬到这里来) 从uiview来做的动画比较有限, 更多的化可以用transition这个类来做
    按钮点击亮点, 自带的一个属性, showhighlightWhenTouched = yes 就行

bug: CUICatalog: Invalid asset name supplied: (null), or invalid scale factor: 2.000000 —– This one appears when someone is trying to put nil eventually in [UIImage imageNamed:] 给UIImage imageNamed:nil]传nil的话就会这样….要判断一下

bug: 当transform的时候, 有时候直接make(a,b,c,d)不一定好使, 在相同参数的情况下, 分步来做的结果不一样,

    showView.transform = CGAffineTransformMake(scale, 0, 0, scale, -kRightMenuW*scale, 0);
    上面这句就和下面的分步结果不一样
CGAffineTransform scaleForm = CGAffineTransformMakeScale(scale, scale);
// 平移
CGAffineTransform translateForm = CGAffineTransformTranslate(scaleForm, -transX / scale, 0);

showView.transform = translateForm;

bug: 在自定义view的时候, - (void)layoutSubviews中设置self.frame中的属性, 会死循环….YCRightMenuMidView的时候, 其他的倒是不会, 应该用了enumerateObjectsUsingBlock:^(YCRightMenuMidViewRow row, NSUInteger idx, BOOL stop) {的原因, 改成for循环问题解决

瀑布流

几条线下来, 没列中类似每行的尺寸都不一样, 应该自己些一个继承自scrollView的瀑布流控件
设置框架, 麻烦点不要仅, 要紧的是健壮.
模仿tableView, 做一个瀑布流控件:
    * 数据源和代理的协议要有, 和tableView差不多, 就是只用看说哪个就够了,
    * 自己实现的重要的说reload方法, 调用这个方法时, 会重新调用所有必须的数据源和代理方法, 根据返回的数据计算出每个cell的位置展示出来, 一次性计算好所有cell的frame, 返回多少个和多少列后, 就计算好他们的frame保存起来
    * 设置私有方法, 判断数据源和代理的方法是否有返回值, 没有的话就返回一个默认值
    * 类似算九宫格一样, 先算好几个相对固定的值,
    * 注意: 和九宫格不一样的是, 并非说从左到右排列cell, 而是看哪个的高度比较小就往那里排列, 不然如果高度大的都排到了一起, 就很不协调了
    * 用一个数组来保存每列的最大y值, 在计算应该在哪列放置cell的时候, 选取y值最小的那列: 用一个C数组来保存每列中的最大y值, 在选列的时候, 拿出数组的y值比较, 选出最小的列, 用最小的列和其最大y值来计算cell的x,y. 在放入cell后, 更新当组的最大y值, 并将算好的frame保存起来
    * 最后拿到这一个位置对应的cell即遍历计算到的i, 将frame赋值过去, 并将cell添加到self(瀑布流控件)

需要用时在创建cell

之前创建出来的说一口气就创建列所有的cell, 这样性能很不好, 应该想tableView一样要用到了才去创建:
    * 不要将自己的代理设置为自己, 这样占据列代理后别人就不能设置代理或者会覆盖掉自己的代理
    * 需要看当前显示的屏幕区域中有没有和算出来的frames有交叠的, 交叠的frame就说要显示出来的.
    * scrollView和别的控件有点不同就是在滚动的时候也会调用layoutSubviews方法, 我们可以用这个方法监听滚动
    * 在滚动的时候遍历所有的frame.maxY > offset.y && frame.y < offset.y + screenH, 就说要显示的frame,拿到这些位置的, 向数据源拿到对应位置的cell, 将frame赋值给cell
    * 这步完成列会有个问题, 在微滚动的时候, 会继续遍历, 继续判断到已经显示的frame符合条件, 拿cell并赋值, 数据源方法会一直调用即使是已经显示的cell, 应该判断是否已经显示出来, 显示了的就不要在向数据源拿了.
    * 创建一个字典来保存已经显示的cell : i(位置) : cell. 一个位置i对应一个cell
    * 拿出i对应的显示的cell, 遍历frames, 发现交叠显示的时候判断cell=nil?, 没有的正在显示的话向数据源拿, 即为新进入视野的cell, cell=!nil的话就说已经在显示的了, 不用管.也不去数据源拿.

循环利用

以上完成后, 微调不会重复创建显示在屏幕上的cell, 但是显示后就不做处理了, 导致滚完后, 有多少个cell都留在内存里, 应该把不显示的cell扔到缓存池中, 拿来循环利用.
* 创建一个NSMutableSet作为缓存池存放移出屏幕的cell,
* 遍历的时候判断出不再屏幕的时候在判断是否在显示中的cell, 不在屏幕+显示中=在上面那些添加到veiw后又不在屏幕显示中的cell, 说这种移出屏幕的cell的话, 就把它加到缓存池中, 并从view中移除, 也从显示cell字典中移除
* 提供方法给外界, 数据源决定从哪里加载cell(创建OR缓存池), 方法返回根据重用标识的cell(遍历缓存池, 找到这个标识的cell), 在遍历判断显示的时候, 要显示的依然是从数据源中拿, (数据源有可能返回的是缓存池或者新创建的)
* 至此可以在数据源返回cell的时候打印地址, 就能知道是不是循环利用

事件处理, 处理cell的点击

当点击时, 如果点到cell, 应该通知代理
    * 在touched方法里监听屏幕的点击,
    * 点击中, 拿到当前的点击点, 遍历当前屏幕上存在的cell(即保存显示cell的字典), 当cell.frame contain这个点时, 就返回这个cell的key即索引, 并*stop=yes;
    * 在拿到触摸点前判断有没有实现这个代理方法, 没有就不用做这么多事
    * 拿到索引后, 看有没有值(点中空白说没有值的), 有值才通知代理

自定义瀑布流后之简单应用

* 应该将自己写好的当作一个框架, 不满意的话就自定义一个, 不要改原来的结构, 增加自己想要的东西
* 向之前使用tableView一样使用这个框架
* 注意瀑布流需要服务器返回图片的宽高, veiw里宽说固定的, 为了比例一样, 需要算出显示出来的高度, 按照原来的宽高比
* 上蜡刷新加载更多---可以用框架, 或者把以前自己做的简单封装起来
*  if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation))判断当前控制器的方向

发现: 当操作到子控件时, 会调用到自身的laypoutSubViews方法, 如果在reloadData里没有操作子控件, 要手动调用, 不然reloaddata算出各frame后, 没有判断子控件的 位置来加载, 也是没有反应的.

bug: -[NSArrayI removeFromSuperview]: unrecognized selector sent to instance 0x10a50f150’

    //  makeObjectsPerformSelector:@selector(removeFromSuperview) 让数组里的对象执行方法
//  performSelector: 自己执行方法

循环利用&无限滚动

* 与之前用scrollView的方法不一样, 之前是用imageView放到scrollView里作为子控件, 在滚动的时候将不显示的放到缓存池, 监听滚动, 当位置到达要显示的时候将imageView放上去, 并立即无动画的切换回中间的imageView
* 这次用collectionView来实现, 因为他可以分隔, 可以横向滚动, 而且自带有循环利用机制
* 设置好view的大小和图片一样, 需要展示的个数, 滚动方向, cell中内容, 完了后直接像tableView一样使用就可以了,
* 注意: 这里使用cell是直接从缓存池中取, 没有时回自动从注册的cell里创建一个对应id的cell, 所以在viewDidLoad里要注册需要的cell

bug: cell 不能按照 xib 的尺寸显示: 在 collectionView 的 cell 的大小设置处要设置, 不然会按照这里的在拿到 cell 的时候系统自动会设置一次

bug: - (void)collectionView:(UICollectionView )collectionViewdidEndDisplayingCell:(UICollectionViewCell )cell forItemAtIndexPath:(NSIndexPath *)indexPath 不要在这里监听, 当滚动时有可能会左右摆动, 造成数据不准, 重复, 形成黑屏

无限滚动

* 和之前的方法的无限滚动的思路差不多
* 都是在滚到哪张时, 将中间那张无动画的显示, 让用户发现不了
* 一样是监听很多东西, 可以拿之前的来看看, 对比来结合优化

bug: 在跳转到下一组时, 会瞬间五动画到第一张: 因为在跳转后动画还没执行完成, 就又执行了五动画到第一张.应该是在自动轮播那里还未跳转到下一页时, 就先五动画的跳转到当前页, 以保不会越界, 总是在中间组

截图实现一些效果

用qurze2d来将view.layer render到bitmap上下文, 获得uiimage后再截取一部分成cgimage, 截取范围可以拿到cell在view中的位置, 分割后在y值处添加一个collectionVIew, 需要判断collectionView在不在可视范围, 需要保证在可视范围, 再把图片盖上去, 然后再用动画效果将下截图片往下移, 上截图片往上移至collectionView的上下两端

升级 xcode7.2后问题: 程序运行上下有黑边:选中target——>general——>app icons and launch images——>Launch Screen File——>选择一个 storyboard file文件,编译运行即可,打包后,黑边问题不再出现!或者选一个 launchimage 到对应的地方

做项目遇到的问题:

*  在点击评论或者什么, 有时候需要登录才能评论的, 先 modal 登录控制器, 成功后应该在后面想之前的控制器发送消息, 让之前的控制器偷偷的五动画 push 一个评论控制器, 再把登录控制器 dismiss, 这样就能 dismiss 后就可以输入评论, 如果登录了就直接 push 评论控制器
*  在导航栏下面有选择条可以点击切换不同控制器时, 其实是想 tabbar 一样点击时把控制器.view 添加到主页显示出来, 可以滚动, 说明几个控制器.view是连在一起放到 scrollView 或者collectionView 上的, 点击不同的滚动条标签就切换到对应的 view

源代码管理工具

* 起源: 解决软件开发过程中由源代码引起的各种问题: _版本备份(备份的多了,费空间时间不好管理)_版本混乱(难以找回想要的备份版本,对应修改的地方不明确)_代码冲突(多人操作同一文件, 就会有几个版本的修改, 不懂要谁的, 代码冲突了)_权限控制(哪个人可以操作哪些代码, 无法对源代码进行精确的权限控制)_追究责任(不懂 bug 代码出自谁手,容易耍赖扯皮)...
* 作用: 能追踪一个项目从诞生一直到定案的过程 / 记录所有内容变化,什么时候改了什么东西谁改的都会记录下来 / 方便查阅特定版本的修订情况
* *
* *现在就开始使用源代码管理工具, 它不会增加工作量, 还能有效的管理代码
* 工具: SVN 集中式 / Git 分布式

SVN

* 集中管理所有的代码版本, 向服务器 checkout 将代码保存到本地, 以后就是 Commit 和 upflow 了, 提交和更新自己的代码, 上班时 update 一下, 下班时 Commit 一下. Commit 的代码是能成功编译的代码
* Mac 自带有 SVN, 可以用命令行配置, 但是比较麻烦,可以在 window 上装Visual SVN Server方便搭建服务器, 大部分情况开发人员没机会接触服务器, 不用自己搭建
* 客户端, update 和 Commit 代码, 可以在 Mac 上用命令行执行, 也可以装软件来执行:Versions, Cornerstone
* *
* 配置服务器: 在windows 上装一个 VersionsSVN -> 添加仓库 -> 添加用户(设定权限) -> 访问其仓库地址, 能访问说明搭建成功

svn import

* import
* 当第一次给 repository 仓库上传代码时, 是 import, 因为本来刚建仓库的时候是空的
* 可以用 svn help import 来查询帮助
* 要带上用户名密码, 还要有说明 -m "", 在第一次连接时还要允许授权
* svn import https://192.168.1.109/svn/weibo --username mgr --password 123 -m " 第一次上传微博(离线缓存)项目 "
* Error validating server certificate for 'https://192.168.1.109:443': 这里要选p
* 会将当前(或这顶目录下的文件爱你导入服务器,不包括文件当前目录文件夹自己)
* *
* checkout (co)
* 下载服务器代码到本地: co 服务器地址 [下载到的路径, 没有就是当前目录]--用户名 --密码
* 有时候下载co 下来的 代码运行不起来, 因为有人在 Commit 代码时项目里有些代码是引用, 所以项目中的文件夹里没有这些文件, 上传上去的自然也没有, 会报错
* svn checkout https://192.168.1.109/svn/weibo --username mgr --password 123
* 会将weibo 这个目录也 check 下来
* *
* Commit(ci)
* 提交代码, 提交自己修改的文件到服务器, 可以用 state(st)查看路径下(没有就默认当前路径)有哪个文件是修改了的, ci 文件(没有的话就是路径下全部文件 ci) 用户名密码 注释       import/checkout 需要知道仓库地址之外, 其他的操作都是对已知的仓库的 workPlace 来操作的, 所以也就不用再写仓库地址
* update(up)
* 更新当前目录的所有文件 , 更新的话还是要进到有所有目录的文件路径里, 不能是只看到总路径
* *
* svn diff -r 1:2  要输入版本号对比
* *
* 重新定位仓库, 有时候地址变动时候将当前本地库连接到新的地址:svn relocate [URL] --username zs --password 123
* *
* 添加: svn add YCTool.h YCTool.m, 会在下次 Commit 的时候添加到仓库svn ci
* *
* 删除: 当前目录的话, svn delete YCTool.h YCTool.m, 不然就给定 URL, 添加和删除, 要在up一下才能在 log 看到这次版本的修改
*
* *
* 状态: svn state, 查看当前修改了而未被提交的操作
* 信息: 查看当前版本信息
* move: 移动或改名
* mkdir 创建文件夹, 而且会自动纳入版本控制

冲突解决

* 有时候有不同人操作了同一个文件, 而这个操作会造成冲突. 在操作文件时, 先updata 下文件, 看别人有改动什么东西没, 再去做其他操作
* 当版本号比服务器低时是不能提交的, 要先 up, up 时会提示冲突, 有几种选择, 猪一样的队友就是会不管你什么代码, 直接将自己的覆盖上去, 这时自己的版本号就是一致的, 但是代码却覆盖了别人的, 在 commit 上去, 在服务器上别人的代码都没了. 到时自己不懂其他人什么情况, 以为都是正确操作的, up 了仓库, 自己的代码就被猪一样的队友给覆盖了.
* 有几个选择项, 不明所以的覆盖都是不对的, 应该先查看双方的代码, 在决定用哪个, 要保留2个或者修改什么合并什么的话, 用手动修改, 这也是最安全可靠的方式, postpone推迟操作, 会生成3个文件, 最新版本, 上一版本, 和有提示区别的自己原来的冲突文件. 这三个不要改, 是 SVN 的东西, 提示参考用的, 修改自己的冲突文件, 完了后选择 "resolved 冲突文件 "来 up 版本, 再 Commit, 服务器就是自己修改好的版本了, 这样才对, 在用正确做法的时候, 别人就up 时就不会有冲突

回退

  • revert, 当修改了东西而且关掉了 xcode 的话, xcode 回退不了, 但是通过这个指令可以撤销修改, 前提是没有提交到服务器, 不然状态全是最新的, 当做没改.只能撤销那些 state 中显示的状态
  • 如果提交了, 想要回之前的东西可以将这个文件更新会之前的版本, up -r 版本号, 可以先查看下这个文件的那个版本是不是存在之前的方法: diff -r 版本号

常见问题总结

  • 以上… SVN 和 Git很像, 容易转换, Git好像更好更强大,
  • 注意: 不要改.svn 里的内容, 不要删, 里面记录着版本信息, 改动的话可能就要重新 checkout 仓库了.

cornerstone 使用

* 其实这就是个 SVN 的图形化界面, 链接了很多草租, 且可视化, 操作方便
* 其中的很多操作操作几次就懂了, 万变不离命令行

xcode 使用 SVN

* 就用它的提交功能就好了, 求他的冲突解决做的很烂, 不要用了,用 cornerstone 好了, 混合使用
* 注意:! user interface data 文件是用户操作的一些配置, 只要对文件做了操作, 这个文件都会改动, 而每个人的这个文件是不同的, 提交上去的话别人 up 的时候就会冲突, 所以这个文件就不要提交了

SVN 的目录规范:

* trunk: 主干目录, 将主要的主线功能放这里
* branch: 分支, 主线之外另外的功能使用, 开发完成后, 测试稳定之后, 可以合并到主干项目中
* tags: 重大版本的备份,要开发另一个重大版本时, 要对之前一个稳定的版本备份, 当发现新版本不行, 要重头开始做的时候, 再把上次备份拿回 trunk 中
* *
* 按照项目组的不同, 在 trunk 目录下面在分几级文件, 平台划分下, 再细分, 如可以分移动组->iOS 组
* 当 trunk 完成了这个版本的制作发布后, 经理需要将这个版本备份下: 在经历连接的仓库那里点击 tags->起名->保存位置(经理需要连接的是整个微博项目的仓库, 这样才能看到 trunk 和 tags 的目录)
* 当发布稳定版本后根据市场反馈发现重大 bug, 同时2.0版本在着手进行开发, 需要开一个分支来修复 bug: 从 tags 中的稳定版本开一个分支到 branch, 然后给予指定人员权限, 那个人连接到这个分支的仓库地址.
* 当 bug 在分支修复后, 提交到仓库, 经理需要将这个修复 bug 版本的 merge 到主线中的2.0来: 先将修复好的分支 checkout 下来, 查看效果, 再从work_copy 从 trunk 处merge 分支修改好的项目, 再 commit
* 这时候别人在从 trunk上仓库 up 的东西就是合并了修复 bug 的版本, 这时连接分支的其他的可以断连了
* 之后经理还要做将修复好的版本 tags 备份, 生成一个新的版本, 之后将分支删除
* 分支还可以在增加一个新功能而不影响主线开发的时候用, 开发完成后合并到主线, 发布后备份到 tags

Git

* 分布式版本控制工具, 和 SVN 不是一个量级的.比 SVN 快很多, 优点很多,
* 每个终端都是一个服务器, 都有自己的一个版本仓库, 平时的 commit 操作都是 Commit 到自己的本地仓库, 等需要时再 push 到公共仓库上, 或者说是其他的终端去, 刚开始可以从其他仓库 clone 下来一个仓库, 平时可以向其他仓库 pull 一些东西下来
* 使用工具: SourceTree, Github(针对自家 Github 平台), Xcode,

Git 的配置

* git init: 首先要在建立版本管理的地方初始化一个仓库, 然后就有了.git 文件夹, 里面装有这个仓库版本管理相关的所有东西
* git config "user.name" 用户名  配置仓库用户, 以后追踪是谁改的, 谁的操作. 或者尅 cat config 这个文件来修改里面的内容来配置
* 建议配置邮箱, git 可以 办到在 Commit / push 之前发一个邮件
* --globle 参数是配置全局的意思, 会让这条配置配置在整个系统, 到时整个系统中国年 git 的 user.name 都是这条配置的
* 进入 vim 状态后退出:wq
* 要查看某个文件里的内容用 cat 文件
* add->Commit 在 git 的版本控制下不能直接扔一个文件进去就行, 要添加的还需要 add, 再 Commit 才能在版本控制中看到这个文件
* commit: git 里必须要写注释才能提交, 而且不能为空, 后面跟提交的文件, 不写代表所有的都提交, 而且不用像 add 那样写.
* add: 添加文件到当前仓库, 如果添加多个文件到仓库, 直接写".", 代表将当前目录的所有文件添加一遍到仓库, 单个也写.更方便, 而且.是表示所有文件这个只有 add 有, 其他的这个特性是什么都不写
* *
* git config 常用配置指令:
* "user.name"   /   "user.email"
* -l : 查看配置信息
* -e : 编辑配置信息
* alias : 设置指令的别名 : git config alias.别名 原指令名/"原指令语句", 给 config 定义别名: git config alias.cfg config
* 对于改动的文件, 直接提交也不行, 在 git 中, 所有修改和添加操作都要 add, add 可以可以看出成是添加一个改动操作的意思, 它会把所有的改动操作添加到一个 state 暂缓区中, 在 commit 的时候一次提交到版本库里, 这样更快速, 像计算机缓存一样
* log: 可以查看文件的修改日志,日志中 commit 后跟的是版本号, 是那次提交的 sha1值, 因为是分布式, 这样可以保证版本号唯一
* log --pretty=oneline 可以打印漂亮点, 一次 commit 一行,
        21d58107677a609451d960e49d2af2d733e29b43  给 car.m 添加了 car 类
        403ec06a81000aa69da57495904eb24eb4625309  添加了 car.m文件
        ba14c4953e895b6f6bca85d02196e7ed16cf9162  添加了多个文件
        60f8f8f21f450390ce10b739bf284c8589714348  添加一个 person.m 文件
* 也可以起别名, 将一些配置设置指令配成一个指令, 来打印更好的日志格式: git cfg --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 的 log 都是这样的, 不用去重复配置
* *
* *

git 的版本回退

* git 支持无限次后悔: RESET
* reset --hard HEAD^  : 回到上一个版本, ^^:上上个版本, 还可以~1, 上一个, ~3, 上上上个版本, hard 标识强转回退, 可以起别名
* 可以在不同的版本号间跳转: reset --hard 版本号,
* 如果不知道版本号, 可以用git reflog 查看之前的所有操作, 找到最后一次提交的操作回退
* 写了一堆东西不想要了, 可以回退到当前最新版本号就可以了 HEAD

移除文件:

* remove: rm 文件名, 这个 rm 操作一样是放操作进缓存区, 可以回退到 HEAD 来撤销 rm 操作, 如果 Commit 了, 就回退到上一版本, 再要回来就回退到最新版本号

缓冲区

* 在对文件做了改变时, 要在 Commit 前将这些改点放到缓冲区, 提交时会把缓冲区的提交到版本库, 没有在缓冲区的提交不上去, 版本库对这些改变就没有记录了
* state : 查看某个文件(或全部 )的状态, 没有进缓冲区的 change 会提示出来, 进缓冲区待提交的也会标出来, 颜色不一样

* diff: 查看某个文件的修改状态, git diff, 放进缓存区后就看不到修改的状态了
* config -e: 进入 vim 编辑配置文件

工作原理:

* 工作原理:
    * 工作区(Working Directory): 即平时我们开发所要动到的文件, 操作到的目录, 即非.git 的那些我们自己建的东西
    * 版本库(Repository): 即 git 给我们做版本控制的地方, 即.git 目录下的东西
        > 暂缓区(stage): 当文件做了修改时 add 到的缓冲区,
        > 分支(master): git 自动创建的第一个分支, 默认一开始就在这个分支下进行版本控制
        > HEAD 指针: 用于指向当前分支, git branch. 查看当前分支

当有改变时, 暂缓区是空的, 工作区的状态改变了, 要先 add. add 到版本库的stage 里, stage 里就有了当前工作区新做的改变, 在 commit 提交到HEAD 所指的分支版本库, commit 后暂缓区会清空. 而且工作区和分支东西一致后会显示 clean, 干净的.
比如有新添加文件时, status 指令会提示Untracked files:, 证明这个文件没有在版本控制的追踪下,分支中没有这个文件,是新建的, -> add 添加到暂缓区,Untracked files:修改已经进入暂缓区, 有待 commit 到分支中 ->  1 file changed,成功在分支中添加了一个文件, 再差 status 会是working directory clean, 和分支追踪的文件没有什么异同
 文件改动时,Changes not staged for commit:, 文件的改动没有放暂缓区中待 commit,  -> add. st 变为待提交, ->commit -> 成功

远程仓库

* 自己搭建的话比较费时费力, 因为一般是搭建在 Linux 上的,
* 我们一般用公开的别人搭好的公共托管仓库, github, oschina,
* Github: -> 右上角有个创建仓库,
              -> readme 一定要选, 可以介绍你这个项目是做什么用的, 还有项目的一些描述, 使用方法等
              -> .gitignore 可以忽略掉不用提交的文件, 可以在这个文件里面编写匹配哪些文件是不需要提交的
              -> 右边有个 HTTPS clone URL , 就是项目仓库的路径, 这样就可以给别人下载到自己的仓库
              -> 其实可以给远程仓库添加当前文件路径的东西, 但是 mj 没弄出来, 可以先把网上的仓库 clone 到当前目录->git clone URL 再把项目文件夹放进这个目录, 这时工作区就可以用之前的操作, add. commit 到本地仓库,
              -> 这时只是在本地分支有这些操作, 还需要 push 到远程仓库去,
              -> git push  ----然后会要求输入用户名密码, 就是 github 的用户名密码
* 想要在自己电脑上 push 项目还需要一些设置, 这个在一开始就要配置了!不然连 clone 都不行!!!!!!!!!!!!!!!!!!!!!
    -> git 远程是用 ssh 连接的, 需要把自己电脑上的 ssh keyes 添加到 github 的设置上, 表示只有这个 ssh keyes 所有者才能 push 代码, 只有配置了 key 在 github 上的电脑才能传东西, 以后参与开发的组员也是需要配置 key 到这个项目的 github 上
    -> 获取 sshkey: 在电脑上用终端在~/.ssh 里创建公钥私钥对,
    ->命令: ssh-keygen -t rsa -C "你的邮箱地址" -> 然后一直敲回车 ->然后会在~/.ssh 目录下生成 ssh key 密钥对
    -> id_rsa : 私钥, 不可泄露
    -> id_rsa.pub : 公钥, 可以公开(将这个文件的内容粘贴到 Github 上)
    -> 利用 cat 可以查看文件内容
    -> 将pub 公钥粘贴到 gihub 上的 key 框中, add key.
每次增加新东西, commit 在本地仓库了想一次性的传到服务器的话, 用 push,
其他组员第一次从服务器拿东西下来是用 clone拿到整个项目,
之后每次从服务器更新东西都是 pull, 拉下新的变动的东西,
用 github 客户端也可以办到以上和远程仓库同步
现在 github 也可以用 https


使用开源中国的好处是本地化更好一点, 私有项目免费1000个, 速度更快,
创建仓库的过程都差不多

GitHub 有个 pullRequest 可以 fork 别人的项目, 燃火做一些改进后 pull Request 给作者, 看它会不会合并你的请求

==========下面这个是从上面剪切过来的

swift

* 同时间距脚本语言的的灵活性和编译语言的高性能
* 和 OC 的很多方法名, 函数名一样, 就是语法的格式不一样, 应该容易上手
* swift 和 OC 开发异同: developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/buildingcocoaApps/buildingCocoaApps.pdf    或者  swiftchina.org

* 不用写main 函数, 从上往下执行, 最前面的代码会自动作为程序的入口
* 不用写分号, 可以写, 在一行里有多条语句时必须写
* 注释: 差不多, 但是多行注释可以嵌套多行注释

playGround

* 能让代码在不运行程序的情况下就能看到执行的效果, 可以看基本变量的最终值, 可以看控件的执行效果

基础语法

* let 来赋值常量(之后不可改变)  var 来赋值变量(之后可改变, 但是类型改变需要强转)
* 字符串拼接: 在字符串间 + 即可, 用加号连接拼接的字符串
* 字符串插值: OC 中字符串插值的话要拼接, swift 在字符串中"\(变量/常量)"即可, 相当占位符里放东西, 类似文字修改中的V填东西
*  不能"字符串" + int + "字符串" :swift 是类型安全的 语言, 不同类型是不能相加的, 需要转换: "\(xxx)" OR String(xxx)
*  基本上可以使用任何字符类作为常量/变量名(包括中文和 emoji 表情)(但是不能包含.数学符号.箭头.非法 unicode 字符.不能是关键字.以数字开头.单独下划线...)
*  常用数据类型(开头都大写): Int,Float,Double,Bool,Character,String,Array,Dictionary,元组类型Tuple,可选类型Optional
*  声明变量/常量数据类型: let/var xxx : 数据类型 = ???  一般不需要特别声明, 会根据第一次赋值来自动识别.定义时不赋值可以声明类型待用
*  swift 是类型安全的强类型的语言, 在变量使用之前必须进行初始化, 仅仅是定义的变量在使用的时候会报错
*  整数中有有符号sign和无符号unsign, 然后又有Int8,Int16,Int32,Int64位的整型, 组合起来就有8种, 用 min 和 max 函数可以得到某类型的最大最小值
*  特殊的 Int 和 UInt: 其长度位数和当前系统平台一样 (**推荐使用这种类型, 会根据平台适应,免得在不同平台上 Int64会有不对的状况**)
*  开发中不要计较 int 类型哪个更合适, swift 是强类型的语言,而且不会自动进行隐式的数据类型转换, 类型不一致不能进行运算操作, 需要强转低位到高位, 这就在后期会很麻烦, 所以统一使用一个类型就行 Int(*那就是不用写, 在赋值时会自动判断这个类型**)
*  存储范围: 强类型的语言, 如果常量或变量的赋值溢出会直接报错不予通过, 不像 C 只是警告和建议, Swift 直接不允许, 类型安全
*  进制: 和其他语言一样, 二进制:0b, 8进制:0o, 十六进制:0x
*  浮点数: 默认是 Double 64位, Float 32位, 精度不同, 声明类型需要:类型, 不能在赋值后面加 f, 不同浮点型一样不能计算
*  浮点数可以用十进制和十六进制来表示, 十进制可以用指数形式 xxxex, 十六进制的话用0x 前缀且一定要指数形式, xxxpx, p 是2的次方, e 是10的次方
*  数字格式, 数字的表示可以增加额外的格式来增加可读性,可以增加额外的0或者下划线
*  类型转换: 类型(变量), 数值相加在赋值给变量是先相加再赋值的. 系统会根据计算后的类型来判断
*  类型别名: typealias,和 C 的 typedef 作用类似, 给类型其别名 typealias 新类型 = 原类型 --->  typedef 原类型 新类型,,原类型能用在什么地方, 新类型就能用在什么地方
*  运算符: 除了一般的运算符外, 还有范围运算符: ..< | ... , 溢出运算符:&+,&-,&*,&/,&%
*  = 可以 n 对 n 赋值:let (x,y) = (1,2), 完成了对应位置的赋值, 其实是(括号里的是元组)
*  赋值运算符没有返回值, 即 a = 2;这个语句是没有返回值的, a 有值, 但是这条语句没有值, 在 C/OC 中, 这条语句返回 a 的值即赋值后的值
*  取余运算符: %, 求余结果的正负和%左边的正负一样, swift 的还可以对浮点数取余:8 % 2.5 = 0.5
*  BOOL: 取值是 false/ture, 不认整形类型, C 是非0即真,认整形类型.   且 if 的判断语句必须是 BOOL
*  比较/逻辑运算符返回 BOOL 类型,  三目运算符的条件必须是 bool 类型
*  *
*  *范围运算符*:两种:闭合/半闭合, a...b : [a,b], a..<b : [a,b), 用在 for 循环中的条件
*  *
*  *溢出运算符*:默认情况下, 赋予超出取值范围的值会报错, swift 为整型提供了5个&开头的溢出运算符, 能对超出取值范围的数值进行灵活处理, var x = UInt.max; let y = x + 1 == 0, 会把溢出的都不算, 直接算回起点, 物极必反, 下溢出&-也是会到最大值; 除零移除, 除以模以0都会得0, 不用溢出运算符的话会报错
*  *
*  *元组类型*: 由N 个任意类型的数据组成, (N>0), 这些数据可称为元素, 元组可以为空
*  元组中的元素访问: 简直就是结构体和数组的杂交, 可以.元素, 也可以.下标索引
*  注意: 如果是用 let 来定义元素, 就要遵循 let 的限制, 那就是常量, 无法修改其中的元素
*  元组可以直接打印
*  元组在定义时可以指定元素类型, 但是这样就不能在设置元素的时候设置元素名
*  可以使用多个变量接收元组数据, 相当于用元组来给多个变量赋值
*  在取值/赋值的时候可以用下划线_来忽略某个元素的值, 取出其他元素的值: var (_, name) = (20, "jack"), 20赋值给_, 就被忽略了
*  *
*  流程控制
*  循环/选择/switch 后面一定要有{}, 不然会报错
*  和 C 不一样的关注点: for-in, switch
*  for i in 1...4 {xxxxxx} 结合范围..., 分别将范围中的值赋值给 i, 赋值一次调用一次循环, 如果不需要使用变量 i, 可以用_代替
*  在 for 循环数组里去索引, 要这样: for i in aaa.enumerate(){  i.index;  i.element  }
*  switch 中没有 break, 条件可以是任何类型 执行完对应的 case 之后会默认自动退出 switch, C 则是没有 break 就接着执行下一个 case. 而且 Swift 中每个 case 都要有一个可执行的语句, 空语句是不行的
*  多条件匹配: 那要进行几个条件都是执行一样的事情的时候, 不能像 C 那样用空语句, 就用这种方法,1个 case 后面跟多个匹配条件, 用都好隔开
*  case 的范围匹配: case 后面可以拿个范围作为匹配条件, 如果判断条件在这个范围里, 就符合条件
*  注意: switch 要保证处理所有可能的情况, 如果没有条件符合判断条件的话, 会报错, 所以要有 default:, 来作为其他判断之外的条件
*  case 匹配元组: 判断条件可以是元组, case (元组) 元组对上了就是 case 对上了, 元组中依然使用于元组的规则, case(_, x) 忽略第一个元素, _几乎是空占位符的概念, 匹配任何, 忽略任何. 元组中可以有范围(-2...2, 1...5), 就限定了一个矩形
*  case 的数值绑定: 在 case 匹配的同时, 没有限制条件的那个元素位置是定义一个变量/常量的话, 就会把那个对应的值赋值过去, e.g. case(let x, 0) 0就是限定的那个元素条件, 另外一个没有限定就是什么都不要紧, 而且会赋值给 x, 如果 case let (x, y) 就是匹配任何二元组了, 没有限定条件, 其他 case 没有符合条件到这里, 就会直接匹配并赋值到对应的 x,y. 也就满足了处理所有情况, 就不必有 default 了
*  case可以使用 where 来增加判断条件: case 就像一个事件, 所以不管是什么条件都可以, 匹配条件也可以是一个赋值判断:case let(x, y) where x == y: 无限制条件赋值后, 再用 where 来判断 xy 符合什么条件, 这个 case 才算是匹配了,where 可以看成"当"
*  fallthrough: 作用, 当执行完当前 case 后,会接着执行fallthrough 后面的case/default, 但是这后面的 case/default 不能定义变量/常量(就是 case let xxx 之类的)
*  标签: 其中一个作用: 可以明确指定要退出哪个循环  waiXunHuan: for _ in 1...6 { neiXunHuan: for _ in 1...3 { break waiXunHuan}} 指定 break 哪个循环
*  *
*  函数
*  函数格式 : func 函数名(形参 list) -> 返回类型 { xxxx   ;   return xxx   }
*  没有返回值类型的话可以 -> Void, -> (), 或者不写-> xx 直接是 func haha(){xxx}
*  可以返回元组类型: -> (Int, Int) { return (3, 5)}
*  返回元组类型的返回类型中可以给类型设置名字 func find(id: Int) -> (name: String, age: Int){ return ("hahha", 10)}
*  外部参数名: 形参名在对函数内部可见, 可知道其参数的意义, 但是外部使用后不可见(使用时可见), 想要外部知道参数意义, 可以设置外部参数名 func haha(外参名 形参名: 类型), 在赋值时就会有外参名显示在要传的参数前. 类似 OC 定义方法中方法名每个参数前都会说下这参数是什么, 一旦定义了外参名, 在调用函数时*必须*加上外参名
*  外参名简便写法: 一个形参写两个参数名太啰嗦, 可以用(#参数名: 类型), 这样参数名既是形参名, 又是外参名不用写两个意思一样的参数名
*  默认参数值: 在设定函数形参的时候可以设定默认参数值, 调用时不传这个参数就会使用默认值. (age: Int = 20), 设有默认值的形参 swift 会自动给它添加一个和形参名一样的外参名, 等于是形参名前加了"#"
*  在其自动添加的外参名的位置用下划线_替代占位, 就会忽略掉那个位置的外参名, 这样就没有外参名了, 调用函数时就不用写外参名
*  常量参数和变量参数: 默认情况下, 形参都是常量参数, 内部不可修改, 要修改的话, 在设置形参时要声明它是变量参数 var. 如 func hah(var age: Int){age = 29}, 不写就是默认 let, 函数中修改会报错
*  输入输出参数: inout. 有时候想象 C 一样办到可以传入一个地址, 函数内部通过指针修改传入的地址指向的外部变量的值, swift 没有指针的概念, 修改外面的变量需要设置 inout 形参, 传值时要&, 可以认为是函数这个体与外部的交流, 输入输出值.
*  不用第三方变量来交换两个变量的值: 1,2,1 = +, -, -; OR 1,2,1 = ^,^,^
*  注意: 传入实参的时候必须要&, 不能传入常量(因为不可更改), inout 参数不能有默认值, 因为是改外面的变量形参设默认干嘛/ inout 参数不能是可变参数/ inout 参数不能在用 let var 修饰
*  价值: 实现多返回值: 传入一个 inout, 函数中对这个 inout 赋值, 相当于外界拿到了函数中传出的值, 用元组也可以实现多返回值
*  *
*  函数:
*  函数类型和基本类型一样也作为一种类型, 由形参类型列表->返回值类型 组成: (Int, Int) -> Int/()->()
*  利用函数类型定义变量: var haha: (Int,Int)->Int = sum;   func sum(num1: Int, num2: Int) -> Int { return num1+num2 } ;       haha(1,2) ---返回3, 用 haha 来调用了 sum 函数, 类似 C 中指向函数的指针. 由于 Swift 有类型推断机制, 直接:var haha = sum; 会自动推断出 haha 的类型
*  和其他数据类型一样, 函数类型也能做其他参数类型能做的, e.g.函数作为参数, 传递给其他函数来使用, 让其他函数在内部调用这种类型的参数, 只要类型是一样的, 传不同的函数都可以
*  函数作为返回值: 函数中设置返回值类型是函数类型, 函数中根据条件判断来返回一个这个类型的函数(这也说明在函数内部可以使用外面的函数,即使没有传进来)
*  函数重载: 函数名相同, 函数类型不同, 就构成了函数重载, 类型安全, 对类型很严格, 所以会根据类型去找对应的函数, 只有返回值类型不同的话在调用时要给接收变量指定类型才可以, 不然有歧义就会报错
*  嵌套函数: 在某个函数中定义的函数; (全局函数:在全局作用域中定义的函数), 这样外界就不能调用这个函数, 相当私有化了, 但是内部定义的函数可以作为返回值返回给外面, 外面就能调用了, 这全在看函数中怎么决定
*  *
*  断言
*  断言是一种实时检测条件是否为真的方法assert(判断条件BOOL表达式: BOOL, 错误信息: String), true 的话就继续执行后面的,false 的话就直接终止程序,并抛出错误信息. 有些时候需要某些条件一定成立才允许程序执行下去的可以调用断言方法
*  使用场景e.g.:检测数组索引(不能越界, 可以认为设定越界就报错, 自己设定错误信息) / 检测传递给函数的参数, 如果参数无效, 就结束并抛出错误信息
*  Swift 是兼具脚本语言的特点, 所以断言语句可以用在如果不符合条件的时候, 就停在当前, 后面的语句都不执行了, 但是前面的语句的效果还是要有的, 还是会执行
*  注意: 断言会导致程序的终止, 如果判断的条件是十分必要, 用断言可以保证其合理, 不然不要用断言, 在开发中使用可以让自己清楚的知道疏漏在哪, 为疏漏做好准备  /  断言可以保证错误在开发过程中及时发现, 但在发布时最好不要用, 所以断言的作用是面向开发的
*  可能之后要弄个自动机制, 开发时候用断言, 发布阶段就置空或其他, 像 YCLog 那样, 但是 Swift 没有宏, 可以将断言函数传递给自定义函数变量, 在不同的环境里有不同的调用方法, 或者这么说, 自定义函数, 在不同的环境里调用不同的方法或者是什么都不做
*  func ycassert(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String) {
assert(condition, message)

}

  • 想要参数可以是表达式, 需要像系统写的那样来配置参数的类型
    • *

      ==========上面这个是从上面剪切过来的, 下面继续有讲

Swift 的面向对象

* 对象的创建: 类(), 这个 "()"其实是个构造方法,
* 属性: 在类里面定义个属性, 方法和 java 还有 Python 很像, 有些和 OC 像
* 分三种属性类型: 存储属性(存值的类型) / 计算属性 / 类型属性
* *
* 直接通过点运算符., 就可以读取某个对象的存储属性
* *
* 延迟存储属性: 就是懒加载, 当需要的时候才调用来存储. 用 lazy 来标识, 一般用在类属性, 而且必须是变量,不能是常量
* *
* 计算属性: 不是用来直接存值, 而是间接的通过计算去赋值或者返回一个值. 有时候有些属性是和其他属性相关联的, 这时候就用到计算属性, 里面存的是计算过程, 属性里存储 set 方法和 get 方法, 间接对其他属性赋值, 返回的时候也是从其他属性拿值来计算后返回
* 计算属性只能声明为变量属性, 因为它的值是依赖其他来计算的
* 一个属性不能既是存储属性又是计算属性, 从这方面又说明了其类型严格
* get 要写在前面
* *
* 类型属性: 用 calss关键字修饰的属性就是类型属性, 也叫做类属性
* 类型属性只能是计算属性, 有些类中都是固定的值的时候就用类型属性, 一个类只会有一份, 类的多个实例都共享这唯一的一份
* 它不依赖于对象存在, 用类名来访问,
* *
* *
* 属性监视器: 在存储属性上可以设置属性监视器, 其实就是在属性设置默认值后加个{ willset{}   didset{}  } 属性在其内部赋值不会调用这监视器, 在外部调用才会, 不像计算属性, 内部调用 set 方法也会调用自身, 因为这赋值取值就是通过这两个方法的, 而存储属性在设置时去调用这两个方法.

##方法

* Swift 中类的方法和函数差不多, 而且和其他语言的差不多分为实例方法, 类方法,
* 函数的话是谁都能调, 方法是只能那个类或其实例调用, 在 OC 中 C 的函数和 OC 的方法很不一样, 但是 Swift 是类中方法和函数是一样的
* 调用是.函数(参数)
* 参数和外面的函数一样, #已经被抛弃了....
* 类方法和实例方法可以一样, 类方法只能是类调用, 实例方法是给实例调用
* 方法里如果没有使用到类中的属性就应该用类方法, 节约内存
* 类可以调用实例方法, 其实系统将实例方法也当做了一个属性, .实例方法(实例), 返回这个函数类型的函数, 外界就可以接收这个函数后调用这个函数
* *
* self: 每个方法内部, 都有一个隐藏的属性 self, 其作用和 OC 中的 self 基本一致,
* 类方法中调用的方法, 默认都是去调类方法, 所以 self.可以不写

##继承

* 基类: 不继承某个类的类, 没有父类的话, 它就是一个基类
* 单继承; class Dog : Animal     Dog 继承自 Animal 用冒号:
* 重写: 用自己的实现覆盖掉父类的同方法,, 需要写关键字 override, 在子类写方法, 如果父类有的话会有提示 override,
* 子类方法不能和父类一样, 只能重写, 加上 override, 可以在重写时调用父类的方法, 保留父类做的事情
* 子类可以重写父类的: 方法 /  属性  /  下标脚本
* 无论是存储属性还是计算属性都可以重写, 而且必须要用 override 标识重写
* 重写属性都需要提供 get/set; willSet/didSet 来重写, 就是连重写存储属性时, 也不能赋初始值了, 要用 get/set. 差不多就是将存储属性重写成了计算属性, 里面可以拿到父类的存储属性或者是做什么改变, 其实其重写存储属性是间接调用父类属性来操作, 重写属性其实就是提供方法来操作父类的属性
* *
* final:
* 被 final修饰的  属性  /  方法  /  下标脚本, 都不能被子类重写
* 被 final 修饰的类, 不能被继承, 修饰了 final 意味着最终,

#构造和析构: (构造, 解析构造)

* 构造方法: 也是 init, Swift 的类中的属性都要有个初始值, 可以在定义属性的时候设置, 也可以在构造方法中设置,
* 在类的实例创建的时候就会调用构造方法, 这和 OC 一样, 但是和其他函数不同不用写返回值类型和 func 关键字
* 构造方法可以设置参数, 用穿进来的这些参数初始化属性, 而且每个参数既是内参名又是外参数名
* 构造方法一样可以重写, 可以是默认的空参数,
* 但是如果写了一个有参数的构造方法, 系统不会自动创建无参数构造方法

##构造方法类型:

* 类型有2, 指定构造方法 / 便利构造方法
* 默认情况下, 构造方法都是指定构造方法,
* 被 convenient 修饰的构造方法是便利构造方法
* 默认情况下每个类有一个隐式的无参构造方法, 自己定义了有参数构造方法后, 系统就不会自动创建无参构造方法了
* 便利构造方法中必须调用同一个类中定义的其他构造方法
* 遍历构造方法必须最终以调用一个指定构造方法结束
* 上面2句就是说, 便利构造只是假象, 指定构造才是真正的构造方法, 遍历构造只是为了遍历, 最终怎么掉, 都需要指定构造方法参加才行
* 这里指定构造方法不能掉本类定义的其他指定构造方法, 只能掉父类的, 所以这里只能是便利构造方法调用指定构造方法
* 如果父类中只有一个指定构造方法而且是无参的, 子类的指定构造方法默认会自动调用父类中的无参指定构造方法
* 变量在父类初始化后还可以在子类初始化, 也就是子类可以初始化父类的变量, 但是不能初始化父类的常量,因常量不能变了

##析构方法

* 在对象被释放的时候调用, 就像 dealloc, 对象在被释放之前, 会自动调用析构方法
* 析构方法不带任何参数, 不带小括号,  直接是 deinit { xxxxxxx }
* 不允许主动调用析构方法
* 每个类最多只能有一个析构方法
* 一个对象在被释放之前, 先调用自己的析构方法, 在一层层往上调用父类的析构方法.

#面向对象补充:

* 属性监听器只能在存储属性里设置, 计算属性要监听直接在 set 里, 而且本来两个就不能共存
* 只能在父类上扩充功能, 不能减少父类的功能 --- 父类的只读属性可以改为读写属性(添加写 set 方法), 不能将读写属性改为只读属性(去掉了 set 方法)
* 子类重写父类属性: 为属性扩充些功能, 可以添加 set/get, 用计算属性访问父类属性, 可以添加 willset/didset, 值最终会赋值给父类属性

#可选类型: ? !

* 当一个值可能存在, 可能不存在的时候应该用可选类型, 如手机号码,  因为 Swift 类型安全, 属性在必须在使用钱进行初始化, 但有些时候某些属性就是不一定有值的, 一开始就给值这不符合现实
* 可选类型格式: 类型名?  : 问号表明号码是可选的, 可能是 String, 可能不存在nil, 默认设置 nil 的话, 应该这么定义变量: var phone: String? = nil, 没有就默认 nil, 可以设置其他,.....
* 意味着在 Swift 里只有可选类型可以赋值 nil, 而且可选类型默认值就是 nil, =nil 可以不写
* *

##可可选类型的应用:

* String 有个toInt 方法, 可以将字符串转成对应的 int 类型, 但有时候字符串是转不了数字的(如 askjfhafhs), 这时候就直接返回 nil, 而返回的类型就是 Int?, 整形的可选类型, 应用在返回值不确定有没有, 或某些变量是否会有值的情况
* Int? 是一种 Int 可选类型, 不是 Int 类型, 两个是不一样的类型
* 试试看可选类型能否存 nil 意外的其他值

##可选类型的本质:

* Int?其实是对 Int 的一层封装, 它们是两种不同的类型,
* int?赋值是将 Int 类型的数据包装成了 Int 赋值过去, Int?内部有 Some 区域和 None 区域, 有值就存 Some 区域, 没有就 None区域,None 区域默认存了 nil
* 而且在 Swift 这个强类型语言中, nil 只能用于可选类型
* 不能将可选类型赋值给非可选类型, 因为可选类型是对非可选类型进行更大的封装, 赋值时要拿 Some 还是 None 赋值不清楚, 不能直接.some 拿值, 用其他方法拿(强制解包, 取出 Some 值)
* 相反, 非可选类型赋值到可选类型时会自动进行包装成可选类型再赋值

###可选类型的其他使用:

###强制解包: 在可选类型的值后面加感叹号!, 就能将可选类型(包装)的值取出来, 赋值给具体类型

* *
* 基本概念:
* 解包: 将可选类型(包装)的值取出来
* 强制解包: 用感叹号!将可选类型(包装)的值取出来
* 强制解包时如果包里没有值, 是 nil, 会在运行时报错, 所以在强制解包时应该判断这个包有没有值
* 可以用 if 语句判断可选类型的值存在否, if num, 返回值类型是 BOOL, 所以在强制解包之前先 if, 这比较严谨
* var num = "123".toInt(), if num {xxx} else {xxxx}, 这里得出的 num 也是可选类型, 之后使用里面的值要解包
*

###选择绑定

* 作用: 用来确定一个可选类型(包装)的值是否存在 ? 存在: 把该值赋给一个临时变量/常量, 不存在: 不创建任何临时变量/常量
* 概念: 将可选类型的值有选择的赋给临时常量/变量, 也叫选择绑定解包
* 使用场合: if/while 语句, if let/var num = "123".toInt() {xxxxx} else {xxxxxx}, 这里的临时 num 是实际类型, 使用不用强制解包! 有值才赋值给 num, 没有就不赋值, 也就没有 num 临时常量/变量的存在

###隐式解包

* 默认情况下是非隐式解包, 需要用!强制解包
* 如果将可选类型声明为隐式解包, 则不用在进行强制解包, 能够自动把包装里的值取出来赋值给具体类型
* 声明格式: 将?改为!即可
* 解包原理: 相当于告诉编译器,这个可选类型的值一直都存在, 绝对能取出里面的值,所以取值的时候不用加!, 编译器自己会加一个
* 这样在一开始设置可选类型时设置隐式解包, 之后就不用每个地方都!, 比较方便, 但是还是要注意判断是否有值, 不然编译器自己加!取出来是 nil 一样会报错
* 应用场合: 某些值在有些情况下一定存在, 就用这个, 不要乱用....
* 比如某些一开始一定有值, 在以后可能没有值了在赋值 nil, 比如生命值, 呼吸量, 头发颜色(一开始有头发, 后来掉光了就没有值了)

###可选类型的总结:

* 强制解包, 定义时?, 解包时判断, 用!取值
* 隐式解包, 定义时!,保证一开始有值, 取值时直接可取不用!

#Swift 练习题

弹出图片
字体选择视图
选择条

  • 技巧: 在 storyBoard拖图片时, 按 shift 键是等比例伸缩
  • Swift 中的很多函数和 OC 中的方法名差不多, 拖线连接也是, 而且拖线出来是可选类型,
  • Swift 中的闭包 -> OC中的block , 少了^, 也可以看成是函数, 即传递一个待调用的函数, 当函数执行完后会调用这个函数
  • Swift 中有很好的地方就是可以直接改结构体中的值, 之前 OC 还需要取出再赋值回去
  • 当配置了 OC 桥接文件后不需要了之后报错, 应该在 target -> targets -> BuildSettings搜对应名字的, 删掉
  • 闭包的特点: 所有东西都写在里面, 有参数的话, 在声明参数和类型后要写 in 在最后, 类型声明和函数差不多
  • {(声明参数类型) -> 返回值类型 in}(). 类似 block, 定义好后就可以直接调
  • UIView 动画中有个方法可以给动画加弹簧效果

  • 可选链, 枚举中的值本来不是 int, 但是可以继承自 int, int 是结构体, 就可以让枚举和结构体产生关系, 让枚举是 Int, 调用枚举用”.”,

  • 当传值时会类型推断, 如果知道是某个类型的话可以省略那个类型, 直接.xxx
  • bug: 注意变量名不要写的和类名一样!!!!!!!!!注意!!!, 要先设大小! 再设位置!, 不然会有意想不到的情况, 先设位置再设大小会根据 frame.y 又缩放回去….
  • bug: var btns: Array? = UIButton, 数组的可选类型
  • bug: fatalError(“init(coder:) has not been implemented”)因为这个方法是必须要求实现, 当不需要这个方法时, 可以将这个方法声明为便利构造方法, 调用 self.init 或者其他指定构造方法,

#技巧: 需要只设置一次但是又只能在重复调用的方法中设置时, 用
struct TokenContainer {
static var token : dispatch_once_t = 0
}

dispatch_once(&TokenContainer.token)

在 OC 中会自己设置一个值, Swift 中没有, 所以要自己加锁

在 Swift 中用//MARK: - 代替 OC 的#pragma mark- Xcode now supports //MARK:, //TODO: and //FIXME landmarks to annotate your code and lists them in the jump bar

###枚举:

* 和 OC 的枚举不同, 它也是种类型, 要想和 Int 有关需要继承自 Int, 里面就会由 Int 包装成枚举, 要拿出里面包装的 Int 值, 就要用.rawValue, 想从 Int 封装成这个枚举, 要用.fromRaw

#静态库

* 库是分享源代码的一种形式
* 开源库: 像 GitHub 上那些,     闭源库: 静态库.a,.framework, 动态库.dynalib, .framework, 直接给编译好的二进制文件来用, 看不到源代码,
* 闭源库使用区别:
    * 静态库: 链接时, 静态库会被完整的复制到可执行文件中, 被多次使用就会有多份冗余拷贝, 就是同一个系统里, 谁拷贝谁就有一份拷贝
    * 动态库: 链接时不复制, 程序运行时由系统动态加载到内存, 供程序调用, 系统只加载一次, 多个程序共用.节省内存
    * 注意:! 项目中如果使用了自制的动态库, 不能被上传到 AppStore, 像加载库里那些 dylib

* 制作静态库:
    * 选择创建库 -> 将要打包的文件放进去(资源文件:音像图片等不能打包到静态库里) -> 选择暴露的文件:targets -> BuildPhase -> + -> New copy headers...(拷贝头文件暴露出来的意思) -> 在出来的栏目里点+ -> 选择要暴露的文件 -> 导航视图中 Product 文件夹下即是将生成的库文件 -> 选择真机, 编译, 报错可能是没有 importUIKit, 因 CG 是里面的东西, -> 生成.a 文件
    * 注意: 选择真机生成的静态库只能真机上用, 选中模拟器的只能模拟器用, 我们可以手动用命令将它们合并 : 进到文件所在目录, (这个的所在目录也是在缓存里....)
        lipo -create 模拟器.a路径 真机.a路径 -output libxxxx.a(这是自己定名字)
    * 在使用别人的静态库时, 运行有时会失败, 因为库里的方法是给其他设定的分类, 静态库里包含分类,它就不链接, 要加一个链接参数, 不管静态库里有没有分类都导进来: BuildSetting -> 搜索 OtherLinker -> otherlinkerFlags -> +: -ObjC -> 代表不管是不是分类, 所有 OC 代码都链接进来    //// xcode7.2已经做好
    * 这样别人看不到源代码也你能直接用, .a 只能放源码二进制文件, 不能放图片等其他资源, 所以要另外提供才行

    * 这里再补充一下查看静态库.a对处理器架构的支持,先cd到.a文件的路径下,命令行输入:lipo -info xxxxx.a
  • bug: 有时候旧文件改动了, 连接出静态库可能有些头文件也要暴露出来, 使用方法也变了!!看看作者文档或主页!!卡了半天!!!, 生成静态库没问题!
    #在使用静态库时应该也设定 Building 参数 OtherLinker -ObjC

  • bug: 有时候等半天没自动补全, 以为是方法错了, 其实是 xcode 的问题
    -[MJRefreshHeader setMj_h:]: unrecognized selector sent to instance 0x79f1aee0
    Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[MJRefreshHeader setMj_h:]: unrecognized selector sent to instance 0x79f1aee0’

动态库

参考文章

##制作.framework

* 创建项目选择 bundle -> 进到 targets.BuildSettings -> 搜索 pack -> packaging.wrapperExtension -> 修改扩展名 bundle 成 framework -> architectures.baseSDK选 iOS -> 回到导航栏.info.plist.bundleOsTypeCode改 BNDL 成FMWk -> 拉入文件(包括媒体资源也可以拉进去, framework 可以放进媒体资源) -> 进到 BuildSetting 设置暴露的头文件 -> 选择真机编译 -> 有时 product 处还是红色, 但可能已经生成了, 在 deriveData 缓存文件里, /Users/mac/Library/Developer/Xcode, 找到对应项目.build.pruduct,就在里面
* 还是真机和模拟器分别编译, 再手动合并(只合并二进制可执行文件, 其他的都一样): 命令一样, 最后把合并的给别人来用
* 可能会出现编译时i386报错, 不好解决, 这时用别人的制作 framework的框架,
* universal-framework, 运行脚本安装后, 可以在新建文件的地方看到 framework 选项, 创建好后设置暴露文件, 编译就可以了, 但是可能还是会出现点问题, runscript 编译报错, 删掉后可通过, 但是加载不出资源文件:单独添加资源文件到项目目录里.

##更新: xcode7.2已经自带这个 framework 模板

  • bug: Undefined symbols for architecture x86_64:
    “_OBJC_CLASS_$_MJRefreshBackNormalFooter”, referenced from:
    objc-class-ref in ViewController.o
    
    “_OBJC_CLASS_$_MJRefreshNormalHeader”, referenced from:
    objc-class-ref in ViewController.o
    
    ld: symbol(s) not found for architecture x86_64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    ——–搞错了, 将真机的文件拉进去了….
    ——–其实, 步奏都没错, 只是在 xcode7中要把静态库链接到 embed link 没有做….

#——–其实, 在 Xcode7.2后做的挺完善, 步奏没那么麻烦了, 也自动做了很多设置, 记得最后要在 embed 链接 framework

##最常用的还是.a 的静态库, 原生,稳定. 制作时注意: 如果静态库中需要暴露的头文件比较多, 应该设置一个主头文件, 一般和库同名,包含所有需要暴露的头文件名

##在程序发布阶段, 应该用发布阶段的静态库, 之前生成的都是调试 Debug 阶段的, 发布应该生成 Release

* 在 edit Schem 里 RUn 里设置 Release
* Debug 模式下性能比较消耗, 因为会生成很多调试信息, 在发布打包的时候 Archive 时默认是 release 状态
* 默认情况下, 用户从 store 下载默认是 Release 运行

#整理接口文档: 未做

* 在抓数据过程中, 最好整一份接口文档, 因为抓下来的数据比较乱, 整理好了方便以后查看
* 收先格式化数据(json 格式化)
* 打开格式好的html, firebug 查看对应的元素来修改,改好后用全赋值粘贴到文件中, 之后返回的数据的用 ultroEdit 将格式化好的东西, 替换到 pre 标签中, pre 标签会保持其中的格式

#从 IOS6开始, 对于应用会涉及到用户隐私的操作, 苹果都会弹框提示用户是否允许

#CoreLocation, 定位

* 要做关于位置的开发, 要知道2个东西: map kit.  coreLocation.   LBS, SOLOMO 都是需要这类应用
* 导入框架(xcode5后默认自动导入) import 主头文件
* 可以查看里面有什么方法是可以用的

##CLLocationManager

* 用户定位: 创建位置管理器 -> 开始更新位置, 通过代理来获取管理器的回调
* CLLocation 表示某个位置的地理信息, 经纬度/海拔/航向/速度/...大概这可以做个虚拟现实服务.....
* 定位开始后会一直不断的调用, 比较耗电, 当定位完成后应该停止更新位置
* distanceFilter: 距离过滤, 每隔多少米定位一次
* CACLLocationAcuracy: 精准度, 越高越耗电
* loc1 distanceFromLocation:loc2] 可以计算2个位置之间的距离
* 在info 里添加一个属性 NSLocationUsage可以设置一个提示, 当系统弹框提醒时可以显示在弹框中, xcode 解析出来就是Privacy-Location Usage
* 必须要在 info 设置位置请求字段,iOS8之后, 不会主动弹出框来让用户授权, 我们要配置 info.plist 文件, 添加授权 key 和要显示的话, 再调用这些方法, 才会弹框授权, 才能使用定位
__字段: NSLocationWhenInUseUsageDescription 或者 NSLocationAlwaysUsageDescription__,
iOS8之后,如果想要定位,必须调用 __requestWhenInUseAuthorization__ 或 __requestAlwaysAuthorization__方法。可参考http://www.jianshu.com/p/ef6994767cbb
* 可以设置某个区域的监控, 当进入这个区域或离开这个区域就通知代理
  • bug: reason: ‘Delegate must respond to locationManager:didFailWithError:’–必须实现设个方法
    ##CLGeocoder
    • 地理编码: 将地名编码成坐标, 经纬度等信息, 根据地名可以获得具体位置信息
    • 反地理编码: 根据坐标经纬度等信息, 得出具体位置信息
    • 只用创建一个对象就可以用, 调用 geocoder & reverseGeocoder 就可以进行地理编解码
    • 完了后会调用 block, 传2个参数, 查到信息的话会返回一个 CLPlaceMark:地标, 里面包装了很多信息, 可以打印看看
    • *
    • 反地理编码: 用法和地理编码差不多, 根据经纬度拿到地标
      *

#Map Kit

* 基于 CoreLocation 的地图工具包
* 一样是导入框架和猪头文件
* 所有类型以 MK 开头

##MKMapView

* 专门用于地图显示
* 直接加载出 MapView 就能显示出地图, 默认显示中国(大概是因为判断到网络是中国的), 而且是大范围标尺显示
* 地图类型: 普通地图MapViewMKMapTypeStandard  /   卫星云图MapViewMKMapTypeSatellite  /  混合,普通视图覆盖卫星云图之上MapViewMKMapTypeHybrid,
* 现在是这些了:
* MKMapTypeStandard = 0, 普通
    MKMapTypeSatellite, 卫星
    MKMapTypeHybrid, 混合
    MKMapTypeSatelliteFlyover
    MKMapTypeHybridFlyover
* 设置用户跟踪模式会马上拿到当前设备位置, 可以跟踪显示当前设备的位置, 自动将这个位置显示在屏幕上
* 可以设置代理, 监听地图相关的行为
* 有个更新位置会调用的方法, 传回MKUserLocation
* MKUserLocation: 可以当成一个用户位置的封装, 其实是对大头针的一个模型,位置信息封装, 也就是当前设备所在位置, 这个蓝色大头针有信息: 位置/标题/子标题/,  里面封装了 CLLocation 地理信息, 还有其他设置, 让大头针的信息多元化, 可以在这里拿到当前位置信息后设置这个的区域, 就壶显示在频幕上
* 区域中包装了中心, 区域跨度等信息,

##显示特定位置和区域

* setRegion: Animated:   设定最终用户位置后, 在设定显示区域(包含中心和跨度, 中心可以设置成用户位置, 就会将用户位置显示在中心, ), 就能显示出特定大小的地图,

##添加大头针:

* 这里地图上的大头针是指在地图上标识目标食物的那个视图控件
* 往地图上添加一个大头针, 用其方法:   addAnnotation:    annotation 意注释, 可以看成大头针的意思, 它也是为注释而生的,
* annotation 在里面是大头针的模型, 要做设个模型就要遵守 annotation 协议
* 设定大头针的位置, 标题/子标题, 添加到题图上, 就能显示出来,

##自定义大头针

* 默认是使用MKPinAnnotationView来显示, 其是MKAnnotationView的子类, 而MKAnnotationView本身没有图片显示的, 直接用的还会没哟东西显示出来, 用MKPinAnnotationView可以少量的进行些修改, 要想自定义更多, 还是需要自定义一个 view
* 在自定义 view 时, 可以给模型加上一个 image 属性来设定图钉要显示的 view, 但是有个注意点, 在代理方法中指定返回的是自定义 view 的时候, 当用户定位追踪时也会调用这个方法, 传入的是它的模型, 而这个模型是没有 image 赋值的属性, 所以会报错:  解决: 在代理拿到 view 的方法里判断是否是自定义的 view, 不是即返回 nil, 系统会按照默认的去添加 view
*  有时候在弹出的视图想丰富化, 貌似自带的没有响应的属性设置自定义的弹出视图, 想自己弹出视图, 可以监听大头针的点击, 自己创建一个 view 弹出来, view 来显示东西, 并设置好位置在图钉之上📌
*  但是这样的话, view 的位置不在父控件的范围, 点击事件不能被响应, 可以用这个方法监听全局点击, 判断点击的点是否在哪个 view 上, 就用那个 view 来处理点击事件
*  方法: hitTest:withEvent: 可以拦截整个操作系统的触摸事件, 因为 iOS 底层的事件传递都是用这个方法来传, 拦截这个方法就几乎拦截了所有的触摸事件. 可以拿到点击的点, 判断. 返回一个 view, 那个 view 就会用来做响应点击事件的 view
*  可以添加 tableView 等等控件, 但是没词点击都会创建一个, 而且每个大头针都会显示出来, 我们可以给这个类做一个单例, 每次出来就改变位置和父控件, 或者通知其他图钉将这个 view 收起

##注意: iOS9已经可以设置 detailView, 可以显示更多东西

  • bug: detailCalloutAccessoryView添加本来没有宽高的东西的时候什么都不显示, — 在使用这个特性时需要添加宽高的限制, 不然显示不出来
    [v addConstraint:[NSLayoutConstraint constraintWithItem:v attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:100]];
    [v addConstraint:[NSLayoutConstraint constraintWithItem:v attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:200]];
    ###对于Swift:

    • let widthConstraint = NSLayoutConstraint(item: myView, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 40)
      myView.addConstraint(widthConstraint)

      let heightConstraint = NSLayoutConstraint(item: myView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 20)
      myView.addConstraint(heightConstraint)

##画线和导航

* mapkit 中有个类, 可以获取路线, 即方向请求: MKDirectionsRequest, 设置起点和终点, 用 MKPlaceMark,  这里要用到地理编码了,
* 注意: 地理编码器同一时间只能进行一项编码任务, 而且是异步的, 所以应该一个完成再编码一个, 将编码嵌套起来,.
* 拿到路线请求后, 根据请求创建一个 MKDirection 对象, 即方向, 用来计算路径. 从返回的路径(或许有几个)拿出遮盖模型, 添加到 mapVIew 上, addOverlay:
* 还需要在代理的添加遮盖的方法中返回一个 render,
  • bug: 画线出不来, —— 不能用 fillColor!!!! 因为这个线不是闭合的图形!!!!!

    • 要想添加导航功能我们自己不好做, 需要有强大的地图服务支持, 一般我们是打开苹果的官方地图应用,

#集成百度地图: 未做

* 继承百度地图SDK, 添加.mm 文件(里面有些使用到 c++的东西的, 项目里必须有一个.mm 文件, 没有的还不会链接 C++的东西, ),或者是早 otherLinkerFlags 添加-all_load(表示不管什么类型的文件都链接进来, 更推荐改.mm) , 使用更多的方法可以查 SDK 文档,
* 使用百度的 map, 用里面的控件, 传入地点, 或者是查找路径就可以, 设置代理可以监听它的执行结果

导入百度地图: Swift3

  1. 下载好最新的包, 要选择全下载!!! 地址
  2. 注册开发者, 拿到了 AK, (就是输入应用的 bundleIdentifile, 得到一个码, 之后需要在 appdelegaete 里面填的)
  3. 将需要的 framework 拖到项目中, 在 xcode8里会自动在 Target -> link 什么的里面自动添加好这些 framework, 恩, 然后…就按照帖子做吧
  4. 注意: 在 Swift 中因为用到的都是 framework, 所以好像不用有个文件是.mm 的, 这个可以忽略
  5. 注意: 在 xcode 后面的 版本中不会自己在info.plist 中添加bundle display name, 要添加才行, 不然不能
  6. 完了后按要求做, 可以显示出一个

百度地图的主要操作

  1. 在AppDelegate代理中声明并初始化 mapManager, 可以查看和判断网络的连接和授权情况
  2. 除了按照教程里夹在出了 mapVIew 外, 主要的属性和用途如下
    1. .enableCustomMapStyle(enableCustomMap) 设置是否开启自定义样式地图
    2. .mapType可以设置地图类型, 标准/卫星/
    3. 点击/双击地图的某个点或者标注点会回掉响应的方法
    4. 可以选择显示交通/建筑/POI
    5. 设置缩放级别等. 可以查看BMKMapView里面的属性, 代理方法可以查看里面的代理方法, 都是中文注释
    6. 用BMKPointAnnotation来添加大头针

###- bug: 在设置 mapview 后 app 的整个界面都打不开…. 打断点, 发现在设置 frame 的时候死循环好像, 过不去, 可能是在 viewdidload 方法里这个 View 还没加载完? 因为是从 AppDelegate 调用的, 取消这个调用, 让他生命周期自己走就可以了.

#音效播放

* 短音频: 即音效, 只有1,2秒....
* 长音频: 如背景音乐,
* 每个音效文件都对应一个 id, 根据文件路径来创建一个id, 之后不想要这个音效了就通过这个 id 把音效销毁 dispose
* 短音频: 用 AudioeServicesPlaySystemSound

* 长音频: AVAudioPlayer, 这个最好是在停止之后就移除, 重新播放再重新创建一个 player,
* 最好封装一个工具类去管理播放音乐

音乐播放器:

* 用播放长音频的控件, 在点击 cell 时跳到播放控制器, 将模型传入,
* 播放 view 拿到模型后设置界面, 并根据模型来播放音乐
* *
* 细节处理:
* 进度条: 设置 timer 来调用 player.currentTime 拿到当前播放时间, 设置进度条的位置
* 细节很多啊...自己看代码吧,....
*

##后台播放:

* UIApplicationDelegate 中注册后台播放, 并在 info.plist 中表明是音乐应用,
* 后台时如果是打开本播放器同时还播放着其他歌曲, 需要播放哪一个? 在 VAudioPlayer 里有标明需要结束哪个的模式
* 请求够哦太模式选音乐会获得更多的后台时间
* // 音频会话
AVAudioSession *session = [AVAudioSession sharedInstance];

// 设置会话类型(播放类型、播放模式,会自动停止其他音乐的播放)
[session setCategory:AVAudioSessionCategorySoloAmbient error:nil];

// 激活会话
[session setActive:YES error:nil];

##更新锁屏播放信息

* 使用媒体播放信息中心 MPNowPlayingInfoCenter, 设置其中的 nowPlayingInfo字典, 往字典里加条目 MPMediaItem... 给字典增加对应的内容
* 设置好字典后, 还不行, 在设置锁屏媒体中心时会自动生成三个音乐控制按钮, 要对这三个按钮进行远程监听, 完成这些按钮的远程控制, 才会将信息放在锁屏上展示
    * 开始监听远程事件,
    1. 首先要成为第一响应者, (FirstResponder) 这是必备条件
    2. 在应用层面, 开始接收远程控制事件, 默认这样只调用这句没用, 但是它默认会调用本应用内的第一响应者, 所以上一步的作用在这里,
    3. 在第一步中, 除了声明第一响应者外 , 还要在控制器中实现 canBecomeFirstResponder 方法, 才嫩真正成为应用的第一响应者.
    4. 再实现远程控制接收事件的方法, 传入的事件可以拿到它的类型, iOS 就三种类型, 触摸/加速度/远程, 往下有子事件, 可以 switch 判断其方法
    5. 在 info 字典里还可以添加几个属性, 来让锁屏显示播放的进度: playbackDuration,
* 设置好后可以显示且监听远程事件了, 而且设置播放时间后会显示 总时长, 且会显示计数当前时长, 这个其实是它有个定时器在显示信息的一刻开始计时, 但是点击暂停并没有停止计时器, 在我们点击播放重新开始后重新设置锁屏信息, 且传入当前播放到的时间(即已经播放的时间: elapsedPlayBacktime), 才能正确显示当前播放进度
  • bug(音乐后台播放, 锁屏显示界面): 真机也显示不了, 而且后台也不能播放 —- iOS9更新后, 或者是 xcode 的更新, 需要在 tagets -> capabilities 下设置interApp Audio 打开, 还有backgroundModes勾选相应的, 远程控制事件也需要配置 info 了现在!!!http://i.stack.imgur.com/GRx6h.png
    ##显示锁屏歌词
    • 在更新了当前显示歌词的时候, quarz2d 来绘制出当前歌词的前后几条歌词, 再将这个图片放到锁频信息上更新锁屏信息即可

##解码

* 音频文件的播放需要解码, 分为硬解和软解, 硬解比较省电, 但是一次只能解码一个资源
* 有时候需要自己对音频进行转换, , 我们优先使用能支持软硬解的格式, 可以使用一些终端工具: afconvert...

#流媒体播放

* 接收远程传输流来播放,
* 可以下完再播, 相当于发请求下载到本地后再播放就完了
* 边下边播, 不简单, 需要知道很多音频的东西, 因为播放一个不完整的文件需要很多信息, 但是这个已经有很牛的工具, , 我们可以直接用
* audio_queue.cpp, 网上大多数流媒体播放都是用这个工具的
* 苹果提供了一些播放流媒体的控件, AVPlayer, MPMoviePlayerController, MPMoviePlayerViewController,  但是这些对于缓冲都拿不到, 不是太好
* AVPlayer: 直接设置播放的 URL, 然后play 就好
* 如果仅仅是想播放一下的话, 用自带的这几个就好, 想要更复杂的, 个性化的话, 就要用第三方的框架轻松实现,(废话, 谁会做这么简单)
* DOUAudioStreamer:豆瓣出品 / StreamingKit / FreeStreamer ----这些在 github 上都开源的, 豆瓣国人出品, 而且更强大, 件事使用

bug(访问 http 出错): 用xcode7之后, 会默认拒绝自认证的链接, 导致微博项目不能访问授权页面, 在info.plist中加入:

NSAppTransportSecurity


NSAllowsArbitraryLoads


或者想添加例外或者更多连接网络方面: http://stackoverflow.com/questions/31216758/how-can-i-add-nsapptransportsecurity-to-my-info-plist-file/31629980#31629980

##使用豆瓣流媒体框架播放留媒体

* 导入项目文件
* import 头文件(它本身依赖的框架 xcode 自动已经包含进去了)
* 其 DOUBANAudioStreamer 就是它的流媒体播放器
*

#KVO : key value observing

  • 谁来监听某个 key 的 value 的变化, 只要这个 key 的 value 发生变化, 就会通知 taget
  • 只要添加了监听器, 在改声明的类里 dealloc 就要移除监听器
  • 当监听到时, 会调用监听器的observeValueForKeyPath:ofObject:change:context: 我们可以拿到 变化的值, 这样就监听了某些一致在变化的属性, 来可视化
  • 监听时设置的 option 可以设置成获得最新值或者是 old 值, 会传到回调方法的 change 字典中
  • bug: 监听 KVO 监听不到回调, —- 有些属性不能使用 KVO, 要设置对了属性才会回调方法
  • bug: 控件设置不起作用, label 不显示字, 进度条不动 —- 设置 UI 控件的代码不是在主线程执行, 拿不到主线程的 UI 控件来设置
  • bug: 速度显示不出来, —- 速度本来设置是 NSUinteger, 需要转换为 double 再进行计算, 不然证书计算最后是0

#视频播放,

* 苹果提供的几个工具流媒体和本地都能放,
* 添加 URL, 创建好后添加视频 view 到我们的界面上, 就可以看到画面了
* *
* *
* VLC 和 ffmpeg 是解码库, 非常强大, 几乎可以播放任何格式, VlC 在 ff 的基础上封装了更多更强大的东西, 但是一般不直接用里面所有东西, 因为太大了, 什么平台什么都包括, 很多我们用不上, 可以使用简单的抽取
* 对 ff 抽取出需要的进行脚本编译, 拿出来的东西放到项目里用, 或者我们用第三方框架,他们是基于这些做好的简单的使用, kxMovie
* 在编译 VLC 的时候, 执行脚本要加 -s 才能编译出给模拟器用的文件 (需要翻墙才能下载完成的 VLC 库, 才能正确完整的编译出东西来)

VLC

* 将 MobileVLCKit 拉进项目, 导入猪头文件: MobileVLCKit
* 创建播放器 VLCMediaPlayer, 设置它要播放的 media 文件
* 设置可绘制的载体, 即其认为视频可以绘制到的地方 .drawable
* 直接运行会报错, 它还依赖于其他库, 需要将这些库导入: libiconv, libstdc++, audiotoolbox, libbz2,
* 完了还会报错 no symbol architech i386 ----- std:...  这些是缺少.mm 文件, 改个文件成.mm
* 完了还报错 --- 在 taget -> BuildSetting ->搜 c++ st将C++StandLib 改成 libc++(GNU C++...)的库
* 完了就可以运行出视频文件播放了,
* *
* 官方实例程序有很多功能, 想通过 wifi 将东西放到手机上, 可以通过这个实例程序学些东西
* 编译官方 workspace, sh 文件名.sh    加-s 是编译模拟器版本, 不然就是只能在真机上用
* @@@@@@@@@@@@@ 诶.....编译不通过...  还要自己找方法解决, 以下载官方客户端和源码, 正在研究编译....新进展, 编译通过: 丑姑娘心编译一次, 提示找不到 iosSDK7.2, 从 xcode5.1拷贝过去后再编译仍然不识别, 只能修改编译 sh 文件, 将7.2改为目前的9.2, 再次编译, 成功, 仍然失败!提示有条指令失败, 但是 sh 文件中找不到这个指令, 应该是次生指令, 但是看到之前缺失的文件编译成功, 设置BundleIdentifile 后从新 build 程序, 提示失败:bitcode 和什么不能同时使用, 就找 bitcode - no, 编译成功!! 运行在真机, 成功!!!!
* 里面默认的播放器没有控制按钮, 需要自己加, MJ 弄了一个

#录音:

* 关于语音识别, 自带的很难用, 一般用第三方, 很多都是用讯飞语音
* 录音用自带的就够了
* 使用 AVFoundation -> AVAudioRecorder, 设置存放路径 URL, (记得 URL 是有哦协议头的), setting 可以不放东西, 就会是默认, 本来是放一些对录音的设置,
* record 录音或恢复录音, stop 停止录音并保存到 url 中
* *   设置格式 caf
* 自动停止录音: 需要监听分贝, 当达到某值时开始录音(或者只能停止录音?开始还需要点击?)
* 开启分贝监听功能, --- meteringEnable, averagePowerForChannel:来拿到平均分贝,也许需要设置 setting 的字典, 不是!! 其实是调用updateMeters获取分贝, 即更新最新分贝才会根据这些更新的分贝来算出平均值,
* 可以用 timer.duration 来获取2次刷新之间的间隔, 秒为单位, 直接累加到记录持续时间的属性就行,
* 在拿到分贝的时候判断, 安静时就记录时间, 有说话时就清零时间, 当时间达到2秒时就停止录音并关掉定时器
* 或者用 nstimer 定时器, 在2秒的时候监听分贝, 静音的话就判断准备停止, =1就2秒后调用停止方法,=0就设准备停止=1, 非静音就延迟2秒, 并准备停止=0, 或者结合上面用就不用设准备停止的标记
* 有很多播放音频的第三方, 可以稍微了解下来使用

#传感器

* iPhone 上有很多的传感器, 开启会耗电量增大, 慎用
* 距离传感器: UIDevice proximityMonitorEnable:  系统通过通知的形式通知应用距离靠近或远离, 而且开启后会自动在靠近时黑屏, 远离时点亮, 通知值传了设备进来(我们自己本来也可以拿到)

#加速度感应器

* 原理: 有个硬件, 里面以手机为坐标原点, 搭建三维坐标, 测试每个方向上的力, 即加速度, 则知道哪个方向具体的方向和速度

##UIACcelerometer

* 类: UIAccelerometer: 加速计: iOS5之后不建议使用, 但是用法简单, 很多程序都有残留
*     * 获取单例对象 & 设置代理, 通过代理来返回其信息 & 采样间隔:updateInterval & 实现代理方法, 里面直接拿到返回的信息
*  *
*  小球实例:
*  从回调的方法中拿到加速度, 在每个方向上累加大到速度, 再累加速度到位移给小球, 做好边界检测即可

##CoreMotion

* iPhone4出来后, 增加了陀螺仪的硬件, 之后关于 motion 的编程成为重头戏, apple 增加了专门除以 motion 的这个框架
* 通过 CoreMotion 获取加速的的方式: 1. push:系统推送, 频率高 |  2. pull, 需要时再手动调用, 需要时再调用
* *
* push:
* 创建运动管理者 && 判断加速计是否可用 && 设置采样间隔 && 开始采样(采样到数据就会调用 handler,handler 会在 queue 中执行)
* 有 block 的是在 push, 没有的是用在 pull 的
* block 中拿到加速计的操作和之前的差不多,
* pull 的话在判断好加速计可用后就设置开始更新加速计. 在需要的时候直接从管理者拿到加速度就可以了

###摇一摇

* 1. 做摇一摇可以通过监听加速计的数据, 分析其数据来判断是否在进行摇一摇, 比较复杂
* 2. 还可以调用苹果提供的 API 来实现摇一摇, 十分简单:    shake, 只要是响应者都能监控摇一摇
*         -(void)motion.....   每个继承自 responder 的都有这个方法,  实现这些方法就能监听手机是否摇一摇
*      有开始 | 结束 | 中断   一般摇一摇结束后做事情
*      实现这些方法, 摇一摇就做完了.....
*
  • bug: 控件显示不出来: —- 设置的 frame = 父控件的 frame, xy 比较大, 就在控件中看不见了, 应该设置 bounds
    ##获取更多硬件信息
    • 设备型号 | 内存信息 | 硬盘信息 | 进程信息(appStore 下的说是能杀进程的都是假的, 调用的大量私有 API 才能杀进程, 这样的应用不会上架, webView也不能监听加载进度, 只能监听加载成功或失败) | 在项目include 中的底层 C 文件中有很多信息,
    • 可以直接用别人封装好 C 的东西, 来获取这些信息, 没什么技术含量, 存耗时间的活 : iOS-System-Services | uidevice-extension
      *

#关于私有API

* 存在, 但是敲没有提示的类, 敲不出来, 提示报错的, 就是私有, 不然敲的出来的都不是私有的
* 但是可以通过转换类来使用, 但是使用了私有的, 可能就上不了架, 不建议这么使用  :  Class c = NSClassFromString();
*

#蓝牙

* iOS 提供了4个框架用雨蓝牙连接传输
* 1. GameKit:     (用法简单), iOS7过期, 只能用与 iOS 设备间的传输多用于游戏
* 2. MutipeerConnectivity.framework: 只能 iOS 设备间, iOS7开始, 主要是文件共享(仅限沙盒内), 取代1.
* 3. ExternalAccessory.framework: 可用与第三方蓝牙设备交互, 但是那些设备要警告苹果 MFi 认证,(国内较少, 要交钱), 不推荐
* 4. CoreBluetooth.framework: 当下热门, 推荐,  可用于第三方, 且必须支持4.0, 很多都支持了, 蓝烨4.0又叫做 BLE(bluetooth Low Energy)目前应用较多: 手环, 嵌入式设备, 智能家居(iOS8后有个专门做智能家居的 HomeKit)

##GameKit: 这个只能在同一个程序之间传数据, 因为这样才能知道数据的格式和接收方法

* 先建立连接, 展示可以连接的设备, 再通过蓝牙将设备发过去,
* 展示设备有一个控制器可以展示, 直接创建好就可以: 设备列表控制器:, 显示很简单, show 就行, 即创建->show
* 如果和别人连接成功, 需要手动销毁弹窗, 通过代理, 在连接完成的方法中销毁弹窗, dismiss
* 代理方法回调传入连接到的点的蓝牙 id 和一个会话 session, 等下就是通过这个会话来传输数据,
* 可以选择一个图片, 点击发送就将图片发送到另一个设备上
* 在连接成功后返回的 session 保存起来, 并将自己传给 session , 处理接收的数据(接收到蓝牙设备传输的数据时,就会调用self的receiveData:fromPeer:inSession:context:)
[self.session setDataReceiveHandler:self withContext:nil];
* 在发送数据的方法中将数据转成二进制 data, 通过 session 会话发送数据 [self.session sendDataToAllPeers:datawithDataMode:GKSendDataReliable error:nil];
* 在接收数据的方法中(即之前设置 session 的 handler的方法)将二进制转成对应数据进行处理即可
*
  • bug: 一方点击连接后, 另一方没有反应, 还是在列表状态么没有弹出是否接收的提示
  • bug: 点击接受后, 没有任何反应了, connecting 一下后就连接失败

#Core Bluetooth

* 每个蓝牙设备都是通过 Service 和Characteristic来展示自己,  你做为外设, 总要给中心提供点什么才能交互吧
* 一个设备至少有一个服务每个服务下包含若干特征
* 特征: 与外界交互的最小单位. 如: 设备 A 是描述出厂信息, B 是收发数据
* 服务和特征都有唯一标识 UUID, 通过 UUID 就能区别不同的服务和特征
* 设备中的服务和特征, 都是由厂商提供的, 类似接口
*

#用小米手环实验一下http://www.jianshu.com/p/053f8756f57a |https://github.com/markdashi/MIBLE
https://github.com/stormluke/Mili-iOS

##开发步奏

1. 建立中心设备
2. 扫描外设, 穿nil 即扫描所有外设
3. 连接外设
4. 扫描外设中的服务和特征
5. 利用特征与外设交互数据
6. 断开连接

* 查找, 连接到外设都会通知中心代理, 扫描外设服务在连接到外设后,调用传进来的外设来去搜索服务, 当扫描服务完毕后调用外设代理方法, 其并未传会发现的服务, 但是这个方法调用内, 其外设的 services 已经有值, 可以在里面获取
* 找到想要的服务后, 接着扫描服务下的特征, 调用代理方法, 然后过滤不要的特征, 强引用想要的特征, 之后就可以给这个外设进行数据交互
* 可以在搜索附近设备的时候传入服务 UUID 数组, 就只会发现有这些服务的设备, 不好使就传 nil, 就会发现所有设备
*

##使用场景: BLE + iBeacon 有很大的想象空间, 但是国内目前应用还不广泛

  • bug(centralManager 创建后没有反应): 创建后会自动去发现周围设备, 但是不能调用代理方法—– 创建时没有设置代理

#社交分享

* 在很多应用中都有一个分享的按钮, 可以将当前内容和自己的用分享到其他平台进行推广
* 让更多人了解自己的App. 是当前互联网应用程序推广的重要手段之一
* 比较或的用户平台就微信和微博,
* *
* 方式:
* 要分享到其他平台, 就要有其他平台的权限, 像微博一样还需要成为开发者,这是肯定的, 更麻烦的是还要发出授权请求, 完了在发送分享请求, 保存密钥, 保存用户信息, 而且每个平台都要判断来做, 比较麻烦
* 可以利用 iOS 自带的分享框架 Social.framework, 但是里面的比较少, 有大部分是国外的, 不适合在中国
* 用第三方分享框架, : 友盟, |  ShareSDK: 体积较大
* 这些在主页上都写清楚了用法, 先学学自带的
*

###自带分享框架

* 导入Social 框架,
* 创建一个编辑界面, 设定好服务商, 然后将这个界面 present 出来, 当用户完成操作后会调用代理通知已完成,
* 可以给界面初始化文字和图片
*

#第三方登录:

* 使用其他平台进行登录, 用其他平台的帐号作为用户认证
* 其实就是将获得的 AccessToken 传给服务器, 让其分配几个用户记录
* 登录时, 可以不用 OAuth2的方式,而是才用 SSO, 当点击微博登录的时候, 跳转到官方应用进行授权,
* 使用官方提供的 SDK, 里面有实例程序

#ARC 和MRC 混合开发,

* 文件比较少, 又想看源代码的时候, 可以在 buildsetting 里一个个文件设置其为-fno-objc, 但是文件多的话就惨了, 用 ARC转换的话又有些会报错的可能, 如果只是用, 不想改动源代码, 或者已经调试好源代码的情况下, 可以将这个框架打包成一个静态库或框架, 我们拿到头文件和静态库就可以了, 不用每个文件的标志了
* 还有方法比较复杂, 像 vlc 的那样, 在一个项目里引用另外一个项目,但是这个方法要配置很多东西, 比较复杂
*

#通讯录访问

* 有两个框架可以使用,
* 1. AddressBookUI.framework: 提供图形界面给你选择联系人,
* 2. AddressBook.framework: 没有 UI 界面, 需要自己搭建联系人界面, 里面的数据大部分基于 CoreFoundation 框架, 使用蛋疼
* 要访问通讯录会要用户授权
* 拿到通讯录点击某个联系人后会通知代理, 可以在代理方法里拿到信息并决定要不要给系统继续操作
* 传入的 Person 是个 C 的变量. 要经过很多步奏才拿到对应的姓名和电话

##注意: 凡是看到copy / new / create / retain / 的东西, 又没有专程O OC 来进行 ARC 管理的, 都要在用完后 release, 用: CFRelease(xxx)

#自定义通讯录界面

* 首先需要用户进行授权: 先对授权状态进行判断: 未授权过的就发起请求:
* 用 AddressBook 框架, 没有 UI 界面, 需要自己搭建来展示, 这个框架可以拿到通讯录所有人, 返回一个数组, 之后的操作就和之前一样了
* 可以使用第三方封装好的面向对象的框架: RHAddressBook, 把之前的东西封装成 OC 对象了, 用法逻辑和官方自带的一样, 具体就按照对象来用就行了

#推送通知:

##注意: 在 iOS8之后,需要应用注册用户通知设置, 而在 iOS10过期, 使用UNNotificationRequest 代替
iOS8通知

* 本地通知   |    远程通知  :  都长一样的
* 可以让后台 APP 通知用户 APP 内部的事情
* 创建本地通知对象 -> 设置通知属性:启动时间 firedate,音效,内容等,还能设置重新发通知的间隔, 还能设置 APP 的图标数字,最好设置时区为默认的时区, 以防用户改时间后会出现错误, 有个 alertAction 可以设置来在锁屏界面小夏下方显示的提示 -> Application 像通知中心注册通知,即Scheduel(调度通知, 启动任务),scheduleLocalNotification: ,
* 在不需要时可以取消调度通知 cancelAllLocalNotification,
* 还能拿到安排了但是没有发出去的通知, 在调度中, 还没调度上, 即等待发出的通知
* 立即发出通知, 在后台时如果有什么想要用户立即知道的话, 就用 presentLocalNotificationNow: 会立即发送通知

##点击通知后的事件

1. APP 在后台, 并未结束: 打开后是从后台调到前台, 并且调用 APP 代理方法: didRecieveLocal...
2. App 被结束了, 点击后是重新打开 APP ,调用启动结束方法
*  其实每个情况都相当于点击图标
*  如果是后台进前台,  调用那个方法就可以判断出来, 而重新打开的话, 会在 didfinishlaunchApp 方法中传入字典, 直接打开是没有值的, 点击本地通知打开会传入通知信息, 从这里就能部署三种不同的打开状况: 直接打开 | 点击通知启动 | 点击通知进入前台

##点击通知进入应用后的跳转

* 在进入应用后判断情形, 是点击通知进来的就跳转到通知指向的详情界面, 通过设定一个 segue, 在判断时候调用这个 segue 所在控制器来跳转到这个 segue,并将通知传递过去,
* 通知还可以添加额外信息, 设置 userInfo, 用字典传递数据
* 有时候在后台到前台的时候需要看到启动图片的, 可以给通知设置属性: alertLaunchImage: 把启动图片的名字传进去, 只能是启动图片的那里
  • bug: 在前台时, 通知发出应用接收到时, 也会跳转到详情页 —- 通知不管怎么样都会发出, 而且没关闭应用的话会调用 APP 代理接收到通知的方法, 这里会直接条过去, 应该加个判断,application.applicationState, 从后台进前台的话调用这个方法时是非激活的, 所以当在激活状态时就直接 return, 不做任何事

#未做

#远程推送

* 在应用在线时, 可能与应用本身的服务器建立了长连接, 这样可以直接通过服务器互发消息, 但是应用一旦关闭, 就不能与服务器进行长连接, 消息就推送不过去,
* 而苹果的 APNs 是只要设备联网就进行了长连接, 这时可以通过苹果的 APNs 对已经关闭的应用的用户推送通知,
* 而要知道推送给哪个设备, 必须知道设备的 DeviceToken, 是非苹果私有的设备唯一标示, 通过 APNs 的推送需告诉其发送的 DeviceToken, 才能正确推送到对应的设备
* 所以在用户安装应用后登录的时候, 要将用户设备的 DeviceToken 上传到服务器与帐号绑定, 在退出时解绑,解绑的具体操作是服务器的事, 不然发送到这个用户的推送有可能推送到这个设备上, 但是这个设备已经不是这个用户在使用, 这样会造成信息泄露
* *
* 生成 DeviceToken: 当我们在应用内申请获取 DeviceToken 的时候, 苹果会自动把 UDID 设备标识结合应用的 BundleID还有其他一些东西传给 APNs, 加密生成 DeviceToken 返回, 到时候我们就保存这个 Token 就可以了

####现在大多数方案才去 JPush:极光推送, 因为每次打开应用都要检查这个用户的 DeviceToken, 如果这个用户的 DeviceToken 变了, 或者每次获取到都发服务器,要服务器判断, 要马上发给服务器更新数据, 所以终端和服务器压力都很大.

##生成和上传 DeviceToken

* 一般来说在应用以打开应用就获取DeviceToken,
* 要获取 DeviceToken 需要在 apple 网站 MemberCenter 生成推送证书,需要提供几个信息: 哪个应用做推送,哪台电脑上调试推送服务,哪个设备调试推送服务
* 下载好推送证书, 下载真机调试证书, 还有电脑真机调试证书, 安装, 才可以完成这些
* 在我们第一次注册的时候会弹出提醒框
* github 上有人开源了一个服务器:PushMeBaby可以给 APNs 推送消息服务, 用它在 Mac 上搭建这个服务器, 就能完成对 APNs 的连接
* 在 losock.h 文件里有个错误, <CoreServices/../Framework...>是作者不小心留下的, 注释掉就可以了
* 推送的格式有要求,参考文档
* 在固定格式之外, 可以自定义传送信息, 只要固定的格式给对了. 额外的信息没有规定, 可以自己定
* 在点击通知进应用的判断和本地推送一样, 只是获取的 key 是 remote 的 key

iOS8的推送

  • 需要[UIApplication shareApplication] registerUserNotificationSettings:]
  • 注册远程通知: [[UIApplication shareApplication] registerForRemoteNotifications]
  • 需要设置好通知类型Types, 行为:Action, 类别: Category, 将类别放到 NSSet 中, 通过这个 set 来创建一个UIUserNotificationSettings.
    -
    -

##极光推送

* 下载 SDK
* 按照它说明的使用
* 将推送的调试证书和生产证书导出成.p12文件上传到极光,
* 在极光网站上可以发送推送信息
*

#~~~~~~~~~~

#iPad 开发

* 大多数 iPhone 上用的类在 ipad 开发都可以使用, 但是有几个类是 iPad 专用的
* iPad 的开发理念不一样, 拥有更大的屏幕, 可以展示更多的东西, 可以显示更多的内容
* UIPopoverController
* 继承死 NSObject, 主管理, 要设置 containerView, 来显示东西, -> 设置大小, -> 设置从哪显示 ,箭头 any ->
* 注意 popoverController 在销毁的时候要先隐藏, 不然会报错
* 内容控制器可以自行设置自己显示在 popover 里的尺寸, 字啊内容控制器自己加载完的时候设置.contentSizeForViewInPopover = ?
* 上面是 iOS7之前的设置, 之后用 preferredContentSize = ?
* 可以设置代理监听是否可以 dismiss 和 dissmiss 了调用, 还有 reposition 重新布局后会调用的方法
* 可以设置从那个 rect 弹出, 传入 inView是参考坐标系
* 有时候想点击外面的区域不让 pop 消失, 要设置一个属性passthroughViews, 将还想要跟用户进行交互的控件放进去,
* iPhone 想实现这个要自己自定义 popView, 或用别人的第三方库
* 那别人的取色框架来用的时候, 如果觉得代码风格不好或者不够严谨, 可以自己改, 抽取出核心代码, 可以以后自己用来改造点东西, 转化一张 png 图片背景色变为白的的话, 直接 representjpeg 那张图片在 writtofile 就可以了
* *
* iPad 中的 modal
* 有点不一样, 展现样式是 多种多样的
* 可以在要 modal 出来的控制器设置 modal 的样式,
* 还可以设置 modal 的过渡样式, 即从哪里出来的动画, 其中的翻页效果只能用在全屏上
*
* UISplitViewController
* *
  • bug: 在 xib 或 storybord 里拖线的时候, 加载对象的时候选最前或最后的时候, 或错, 就是拿到的对象错了

#iOS版本判断: [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9,3,0}];

#QZone

* 先做个登录界面
* 图片资源多, 单都是有对应作用的
* 登录界面和以前的差不多, 它用的登录框是用图片直接做两个框的背景, 再在背景之上加上 label 和 textField
* 主页显示的分垂直和横屏时候左边的菜单的状态有区分, 要做到能充分利用空间, 先设置了高的话打横高不变,多出屏幕的地方还是存在. 可以哟给你 autoresize, 设置高为 flexble,
* 或者使用 autolayout, 代码实现比较蛋疼繁琐, 可以用别人封装好的分类来简单使用, 就少打很多代码
* 当设备要旋转时会调用 willRotateTo...方法, 这里我们可以判断要旋转到哪个方向, 传给 docker 来决定显示的宽, 记得在初始设置的时候调用这个方法, 不然不一定是什么方向的
* 在横竖屏切换的时候, 擦混入方法告诉控件, 控件中的子控件也获得旋转信息来自己适配横竖屏, 这样就方便管理各个控件的旋转适配
* 按钮的文字和图片的排布可以重新实现方法 drawImageRect....来实现他们的布局
* 当子控件布局时, 使用的父控件的尺寸是当前未旋转的, 但是计算的是旋转后的, 这样会出问题, 需要将子控件的位置也 autoresize 跟随父控件

#美团

* 一般要进行哪个应用的开发, 获取哪个平台的数据, 都要成为其开发者, 有些框架的使用也需要这样
*  在不改变源代码的情况下, 可以用字典来存放对应的值, 可以用 description 来获得对象的地址
*  用 block 来进行回调可以省去很多麻烦, 一个事件对应一个自己的回调, 对于可能会有多个回调的时候就可以互不影响
*  东西不好, 源码写的太烂了可以适当修改, 改到好用
*  搭建 UI 时, 最好打通数据, 我们要先加载元数据, 即类型字段, 描述数据的称谓数据,是抽象的没有具体值的, 它描绘的数据才有具体值.
*  对于城市组数据, 存字符串就好, 不然城市模型太大, 而且会冗余
*  当属性是@property(readonly)时, 只会在没有实现 get 方法的情况下生成 get 方法, 自己实现了 get 方法就不生成了且不会生成成员变量了, 这时候要自己定义这个成员属性
*  既然有些数据如城市, 分类等的是死的, 我们可以先将其保存起来当做本地数据, 而且应用启动时只加载一次, 应该统一用个工具类来管理
*  MJ 的框架可以给属性对应一个别名, 在字典转模型的时候就不会因为字典的 key 有关键字而属性的名字不对应的情况, 可以实现方法, 将 key 和属性对应上: replaceKeyFromPropertyName{ return @{@"属性名" : @"字典 key"}}

##单元测试:

*     项目中有个 test 是专门做单元测试的, 且这个 test 中的东西不会包含到应用中,即沙盒中是不存在其中的任何东西的
*  在 test 下新建一个 testCase,它是继承 XCtestCase 的,它的 tagets 是到 test 的,
*  断言: XCTAssert(xx, xxx)
*  方法左边有个运行的小图标, 点击这个就能运行这个方法而不用将整个程序跑起来
*  这个可以将测试代码放在这里, 不会影响源程序的开发和体积, 可以快速的确定某个或几个功能是否正确, 直观看到业务方法对错
*  注意: 但愿测试不能做一些异步的测试, 因为方法执行完就结束了, 数据回不来
*          不能测试 UI 的,
*       *


*    *
*     在自定义 UIBarButtonItem 的时候, 其宽度会随内部的 View 改变, 通过改变内部的 View, 可以给按钮间弄个间距
*     按钮中的图片也可以设置对齐方式, 再设置内切就可以设置一个间隔
*     设置好 view 的 xib 后, 加载出来可能会有拉伸现象, 避免这个, 可以在管理 xib 的类中在加载方法 initWIthCoder 里设置 self.autoresizingMask = ...None;
*      在弹出的 popView 里, 对于2个 tableView 的, 建议自己写个 view, splitView 用起来比较麻烦
*      如果在 view 中有按钮的, 设置其点击监听可以直接提供方法设置 taget 和 aciton. 内部再用这些设置按钮的事件
*      分类中的 property 只会自动添加声明, 没有实现, 所以要自己实现 不然运行时会报错找不到这个程序
*      因为按钮设置其属性大多数是设置一般的状态, 这些不需要重复写, 可以给按钮设置分来来方便设置这些属性
*      *
*      关于分类和地点的下拉, 其中的滚动视图都几乎一样, 可以由2个 tableView 组成, 应该抽出成一个控件, 控件再根据模型来决定要怎么显示
*          可以在自己的工具类中定义一些 typedef , 方便设置 block
*       searchBar 里有属性设置是否弹出取消按钮, canshowCancelButton
*      在 formSheet 模式的 modal, 点击 endEditing 不能退出键盘, 实现 - disableAutokeyboarddissmiss = NO;
*       在 autoLayout 下, 改变一个约束, 其他与这个约束有关的控件也会跟着调整位置
*       改变约束后, 想要生效的控件有动画效果, 需要调用约束控件的父控件 layoutIfNeeded 方法, 在 UIViewAnimate 方法中调用
*       弹出菜单的时候把后面的 popoverView给退出, 但是 contentViewC 不能直接拿到所在的 popOver, 这时, 可以用 block 在创建本控制器传给 popOver 时设置其调用 popOver 来 dismiss, 或者, 用 KVC 来取出自己所在的 popOver 来赋值

#warning: KVC 可以通过 key 访问任何属性和成员变量)

* 可以使用 indexOfGroup....来返回一个数组, 这个数组作为整个 table 的组的索引, 它是按照索引来对应组的, 所以索引的顺序应该和 table 数据源提供的组的数据一致
* 可以用 KVC 来获得一个数组里其元素的某个属性的值, 数组里有多个属性有这个值, 所以最后返回的是每个元素的这个属性值的数组
* 也可以调用其属性设计组 indexColor
* 查看重叠的所有 UI 控件: Shift+Control+单击
*

##可以使用判定: NSPredicate 来设定判定条件, 用数组来调用根据判定来过滤出符合的数组来返回: filteredArrayUsingPredicate:, 在判定中, 是用字符串来描述的, 其中 数组中元素属性名 contains 匹配字符串, 其中元素属性可以跟上 OC 的语法, 不过也是在字符串中的, 类似就是调用了 KVC 的方法, i.e. : name.lowercaseString, 一样可以将 name 的字符串转成小写, 也有点想数据库的写法

* 在当控制器 dismiss 的时, 有个属性 isBegingDismiss, 可以判断是否正在 dismiss, 可以根据判断来决定要不要在这个时间段执行什么效果
* 在 collectionView 的 Storyboard 中有默认的一个 cell., 也可以多拖几个 cell 进去, 设定其 reuseIdentifier, 就能在使用 collectionView 的时候从缓存池中找这个标识的 cell,没有就会从 StoryBoard 中找来加载, 在这个 collectionVIew 中定义的 cell 就被它识别了, 所以不用注册, 要是是代码或者 xib 创建的话, CollectionView 不知道其存在, 就不能使用它, 需要 CollectionView 注册这个 cell 才能使用

#问题: 大众被收, 美团没 API,可以尝试开发其他接口的程序
Github:GitHub API v3
Weibo:API - 微博API
Twitter:https://dev.twitter.com/
Dribbble:Dribbble Developer
Angelist:API - AngelList
Nytimes:http://developer.nytimes.com/docs
(这个是从西乔的文章听说的),使用这个API,开发者可以调用到几百万份结构化过的数据,从1981 年至今,纽约时报报道的事件,畅销书,甚至房地产等等。
Instagram:https://www.instagram.com/developer/

什么还嫌不够,来吧,想要什么API 搜索吧。。。
APIs.io - the API search engine
ProgrammableWeb

  • bug: CUICatalog: Invalid asset supplied: (null), or invalid scale factor:1.000..
    ———– 当设置图片传空壶有这个警告或者传入的图片找不到
  • bug: 当点击菜单按钮时, 崩溃, reason: ‘-[UITableView _contentOffsetForScrollingToRowAtIndexPath:atScrollPosition:]: row (2) beyond bounds (0) for section (0).’ —–
    ——-点击菜单后, 里面有对 tableView selectRow 的操作, 滚动到某一行, 但是这行不存在, 因为这个 tableView 的数据没有, 所以在初始化的时候必须要有数据, 才能这样操作

##iPad 的自动布局, 设置 collectionView 的 item 间距等

* 一般自动布局的话我们是调整约束, 在collectionViewController 中可以通过 self.collectionViewLayout 或者 self.collectionView.collectionViewLayout, 拿到的是父类,抽象类, 我们需要转成 UICollectionViewFlowLayout 才能设置更多的属性
* .minimumLineSpacing, 最小行间距
* .minimumInteritemSpacing, 最小列间距
* .itemSize , 设置每个 cell 的尺寸
* 在计算即将转的方向后的尺寸时, 要拿即将转的方向的数据来算, 像宽度, 即将转的话将是高度作为宽度, 我们当前算就是要拿高度来算

##改变 autoLayout 的约束

* 可以代码拿到, 也可以连线, 改变其 const 值,


* 可以在 drawRect 中画线来当做文字的删除线
* 标记最新: 拿到日期和今天的日期比较后决定显示或隐藏最新标记, 用字符串 compare 方法, 判断 NSOrdered...

##空白处理

* 当没有对应的数据时应该进行空白处理,
* 对于展示的图片可以自定义 ImageView, 设置其特有属性: didMoveToSuperview 方法中设置自动布局, 和父控件没有边距
* 在刷新数据的数据源方法时, 控制空白 View 的可见性
* 当选择后会从新获取数据, 这时应该让视图有下拉刷新的动画行为 ,告诉用户已经执行, 调用 MJ 刷新控件, 直接用 self.collectionVIew headerBegingRRefreshing] 就可以
* 页码默认是一, 没词配置参数时, 将上一次的拿来+1就可以, 即是创建默认是1也不要紧, 会改的
* 当 collectionView 的 cell 没有超过一个屏幕时, 默认是不能拖拽的, 要设置 scrollView 的属性: alwaysBounceVertical = yes

##详情页

* 需要加载网页上的内容 , 模型中有详情页, 但是直接加载网页的内容的话会有些不想要的 元素 , 我们需要通过 js 代码, 将加载网页中的内容去掉, 只留下对应标签, 这需要 OC 调用JS,
* MJ 框架中包装了一个宏, 使用这个宏就可以把自定义的类加上 NSCoding 和 Decoding

#跨平台开发的框架

* phonegap
* sencha touch
* jQueryMobile
* 这些都是 html5的框架, 也是 js 的库, 会 js 就对这些轻松点
* *
* *
* 有时候有些页面需要在指定页面跳转才能进去, 这时候只能先加载网页了然后分析网页源码- > 拿到点击相应按钮看跳转的页面的 url, -> 在加载好原来界面后, 即 webViewDidFinishLoad 方法里, 拼接对应的详情页(需要分析对应源码, 得出规律, 其实就是固定一串拼接 id), 用 OC 调用 JS 进行网页跳转, 从网页内用 js 跳转到目的页, : stringByEvaluatingJavaScriptFromString:js 代码]

#关于 JS

* js 代码卸载页面源码的哪个地方都可以, 一般可以写在head, 也可以写在最后<script>标签内
* 有时候代码很多, 我们就把它写到一个函数中, 在其他地方调用这个函数就可以
* 根据元素标签名获取节点
* 根据 class 名获取节点
* 因为一个网页只有一个 body 标签, 所以直接 document.body 获得, document 代表当前整个页面
* 移除节点: remove()
* 或者改变节点内容: 直接设置,
* 获取节点内容: .outerHTML/.innerHTML, 区别就是包含标签与否, 将其赋值给对应节点的 innerHTML
* webView 中显示内容都在 scrollView, 隐藏 scrollView, 就隐藏了内容,
*
*

##关于 autolayout

* 一般来说, 控件设置 autolayout 要是约束不全的话会报提醒, 因为位置/大小两个因素, 缺少了都不能确定位置,
* 但是有些控件比较特别, 少了某个约束不会报错, 因为系统会自动计算其某个约束属性, 像 Label, 会计算其高度, 跟随文字的多少而变化, 但是这样必须不限制行数
*

在返回数据不确定是什么数值, 想精确保留的话不要转成基本类型, 应该用 NSNumber 接收, 这样是什么样子的数传进来还是什么数
在返回的 json 属性里, 要算有属性中还有属性的, 一般这个属性可以转成一个对象, MJ 框架会自动判断类型, 我们只要写个类接收这个参数就行了

##日期判断:

* 使用 NSDateFormatter转换字符串和 date 之间, 需要定义时间的格式
* 使用 NSCalender 可以判断2个日期之间的间隔
*

##模型的重要,

* 根据 MVC 的模式, 视图的展示都是由模型提供数据的, 如果要修改上面的数据, 就去修改模型, 在刷新视图
*

##抽取父类,

* 相同类似的东西, 模型, 控件, 控制器等, 都可以抽取到父类中, 由父类完成相同的操作 再提供接口给子类实现差异化
*

#安装 cacoaPods 的坑,

  • Error installing cocoapods:
    activesupport requires Ruby version >= 2.2.2.
  • 你的 Ruby 环境版本必须大于等于2.2.2.于是,我输入了 gem –version.结果出来的版本号是2.6.0.我说这明明大于2.2.2.怎么还提示版本太低呢.结果一查才知道:rvm是用来管理ruby的,ruby的其中一个“程序”叫rubygems,简称 gem,而用来管理项目 的gem的,叫bundle.完全是不同的东西,他们相同的只是都可以管理gem.所以说不能用这个.得用rvm list.一查, ruby 的版本好像是1.9.8吧,看来是cocoapods 没骗我.接下来的工作就是升级 cocoapods 了.
  • http://www.it610.com/article/2113275.htm
  • 需要更新 HomeBrew
  • 更新 rvm, homebrew 后, 重新安装, 完成
  • http://www.cnblogs.com/yan520/p/5583362.html, 像这个里的问题又有了…..
  • http://www.jianshu.com/p/6e5c0f78200a/comments/2274069
    *
  • bug: linker command failed with exit code 1 (use -v to see invocation)
    之前项目里有文件和 pod install里的库是一样的, 删掉就好

  • bug: 用 UITextVIew 做富文本连接点击识别时, 获取链接的范围总是几乎{1, 0}之类的,
    — 需要将 textView 加到一个 view 中作为子控件, textView 自己不能交互, 用父控件的触摸事件来定位点击rect 和点

#还没学到的, 视频里漏了的知识点: 未做

- 网络 socket
- xmpp
- runtime
-

UIlabel 自动布局自适应高度

* 当设置好约束后, 设置其 preferredMaxLayoutWidth,  uiview 有个方法: systemLayoutSizeFittingSize:UILayoutFittingCompressedSiz可以拿到根据自动布局约束来获得紧凑的 Size, 这时可以拿到高,
* 但是 UILabel 没有设置选中文字, 也没有获取相对 rect 的方法, 默认也不能点击富文本的链接, 只能通过 UITextView 来显示富文本, 通过算出 link 的 rect, 判断是否点中, 发出通知来传递链接信息

UITextView 的高度自适应:

* 根据文字内容的多少, 决定高度, 如果不设置宽度, 就会根据富文本的换行来决定宽度,
* CGSize size = [self sizeThatFits:CGSizeMake(width, MAXFLOAT)]; 根据宽度来适应的 Size, 这里高度无限, 即未达到这个高度时会根据内容的实际尺寸, 超过了就安装设定的尺寸, 这样就能按照内容来设定高度了, 在获取这个之前, 要先有内容填充进去, 所以在内容填充时还要在设置这个方法才行, 为了保险, 在 layoutSubViews 中在设置一下也好

UItableViewCell 的高度自适应

* 将 cell 中的子控件都设置上约束, 让 cell 上四边都和子控件有约束, 在 iOS8之前, 可以像上面那样获取 label 的高度, 再设置 cell 的高, 也可以用一个模板 cell, 里面是约束好的控件, 将内容填入, 再 sizetoFit.... 就能拿到自适应的高
* iOS8开始, 只要设置好了约束, 在 tabelView 设置 estimateHei, 预估高度, 还有 rowheight= UITableViewAutomaticDimension;就可以了,,,,,,heiorrowAtIndexPath 这个代理方法都不用实现
*  可以用 beginUpdate  / endUpdate 来更新 tableView
  • bug: 将做好的显示属性文本的控件加到一个 view(containView) 上时, 虽然 view 可以根据里面 textView 的变化改变 size, 但是在更外满一层使用这个控件的 view, 不能根据containView 的 size 改变而改变, 只能拿到这个 containview 的高, 加上其自己本来能自动布局的高,

#注意: 在自定义控件的时候, 最好 initWithFrame 和 initWithCoder 一起实现, 将初始化代码放在 setup 类似的方法里在上面两个方法中调用, 免得在 xib 使用时没有调用其初始化方法,

  • bug: systemLayoutSizeFittingSize:UILayoutFittingCompressedSize的坑:

    • 在使用这个方法时, 只能自适应其中固定了尺寸宽高的控件, 像自己做的这个显示富文本的控件, 放在一耳光 view 里了, 默认 view 没有自动适应内容高度, 所以外面在fit 的时候, 就不会将这个 view 考虑在内, 或者考虑为0, 所以需要自己拿到改变的高自己计算高度,
      #注意: sizeThatFits的坑: 这个如果宽度不够, 一样只会取最少的返回, 所以要固定宽的话, 也要另外设置
  • bug: SQL 数据库要搜索的字段是TEXT的话, 执行语句里要加上’’,

  • bug: [_NSInlineData firstObject]: unrecognized selector sent to instance 0x7bae9200
    AFN 按理说返回的是 json并自动会转成字典或数组, 但是有时候会成这个会成这个类….奇怪
    可能是发送请求的时候共用了一个 manager, 在这个发送请求未返回时, 其他的操作又设定了这个的 responseSeriarazer 设定为其他的了
  • bug: 登录操作 POST 参数到对应地址后返回的是相当于加载主页的内容

#注意: 由于Stack View自动为我们管理Auto Layout constraints,我们只能调用layoutIfNeeded来实现动画。

#问题: scrollView 在使用自动布局时, 设置了上下左右后还是会提示有错, 有约束没有设置, 而且运行后有时滚动不了,
先猜想: 其布局和普通控件不一样, 果然, 它除了 pin 在四周的设置外, 还会根据子控件来设置其 contentSize, 没有足够的约束来计算的话就会报错, 可以在 scrollView 里设置一个 contentView 来填充其内部, 所有要在滚动视图的子控件都加在 contentView 中, 而且子控件要连贯其上下或者左右来设置约束, 这样这个 contetntView 就会根据内部的子控件来计算 size, scrollView 是根据这个 contentView 来算 contentSize, 如果不想哪个方向滚动, 可以设置 contentView 的高/宽 = scrollView.高/宽, 相当于 contentSize 的高.宽被设定死了, 在高/宽方向不滚动
Google 后发现其底部和右边的约束是对其 contentSize 的设置, 即约束里的 width 和 height 是对其滚动范围的设置
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithScrollViews.html

  • bug: 代码设置 contentOffset = xx, 无效, setContentOffset:animated:方法无效
    cao !!其实是有效果的1!!! 在更换了 xib 文件中的控件时忘了重新联建 scrollVIew! 到时其 = nil;
    在网上找到些这些问题的方法, 记录下, 以后要是真碰到了无效的话试试.

    1. 强制回主线程操作:
      dispatch_async(dispatch_get_main_queue(), ^{
      这里设置 offset
      });
      1. pagingEnable = no;, 设置完 offset 再设置回来
      2. delegate = no; 设置完 offset 再设置回来
  • bug: 这是在搜上面的问题时顺便搜到的, 以后可能有用, 问题是当 pop 回 scrollview 时, 里面的控件和 tabbar 会有奇怪的布局现象, 解决方法:
    In iOS 7/8/9 simple self.automaticallyAdjustsScrollViewInsets = NO; solved the problem in my case.
    原文连接: http://stackoverflow.com/questions/17404682/uiscrollviews-origin-changes-after-popping-back-to-the-uiviewcontroller

关于 css/xpath 选择器

http://www.cnblogs.com/ziyunfei/archive/2012/10/05/2710631.html

  • bug: library not found for -l AFNetworking

    1. 在使用了 pod 后打开的是 xcproject, 而不是 xcworkspace, 因为这些路径自动加入是 workspace 的事,
    2. 在没有使用 pod 的时候, 检查在那个Library Search Paths需要有这个库的路径
    3. 没有使用 pod 的话, 也要看这里有没有包含有这个库的路径, 不然会报找不到的错误
  • bug&warning: 关于 textView 内容更新及其尺寸的更新
    在内容更新后, 会自动更新内容, 但是设置的大小, 要先[self.attView sizeToFit], 或 sizeThatFit:方法重新计算, 其容器才能根据这个 textView自动计算出来的尺寸设置其他东西, 而且要在外层

关于 View 布局的注意, 当 view

// [la setNeedsLayout]; // 可以, 这个方法会调用其中的 updateConstrains 和 layoutSubviews 方法
// [la setNeedsDisplay]; // 不行, 只会调用updateConstraints
// [la setNeedsUpdateConstraints]; // 不行, 只会调用updateConstraints
[self invalidateIntrinsicContentSize] // 当改变了内部内容的尺寸时, 要调用这个

##所以, 当想要都调用 View 中两个方法的话, 只能使用setNeedsLayout

#textView 布局注意

- 在设置好宽度后, 直接 sizeToFit 就可以或者 sizeThatFit:, 但是在 view 中直接用textVIew的话好像不行, 要自定义一个 tetxView, 在 layoutSubView 中实现重新设置 size 才可以...
- 目前在 textView 的属性文本中添加的图片如果修改了尺寸的话, 好像它还是按照原来的尺寸来适应这个高度, 还要设置 attach.bounds 才能按照这个 bounds 来布局
  • bug: 在编译RegexKitLite的时候,报错如下:
    Undefined symbols for architecture i386:
    “_uregex_open”, referenced from:
    _rkl_getCachedRegex in RegexKitLite.o

    – 解决办法:
    在项目的编译设置中找到Other Linker Flags,然后在后面字段空白处双击,添加“-licucore”就可以了。

  • bug: 在用 analysis 时候(不小心点中的)发现在 AFN 里使用 rechability 的时候有内存泄露,
    搜到的方法: https://github.com/tonymillion/Reachability/issues/114

  • bug: 换了 TYAttributed 后, 在显示 headerView 正常, 但是显示cell 的时候会不显示文字, 且cell 的计算高度会递增
    目前根据一些测试猜测: 计算高度的时候用的是同一个 cell, 里面的控件可能对高度进行了缓存, 导致没次算的时候都会有上面一些的影响
    进一步发现, 还是自己的代码写的不够严谨, 在封装好的 Label 中, 对内部 TYAttributed 的使用是 append 富文本, 如果一致使用同一个, 就会一直拼接上去, 最后 attributedString 会越来越长, 造成计算的高度越来越大.
    注意自定义控件的时候会有复用的可能

  • bug: 单独抽出这个 cell 及 YCAttributedLabel 来测试时, 产生一样不识别, 且都挤在第一行上.

  • bug: 想在异步线程完成用自适应的方法计算出高度时,
    2016-10-18 17:20:33.133 tableViewtest[40456:2268670] This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release.

不然异步线程进行自动布局的相关操作

RunTime

简书的一篇教程
runtime 是 OC 底层的实现, 也是 OC 在其上封装的面向对象的基石, 所有的 OC 代码, 在编译的时候都被转成 runtime 的代码, OC 的动态特性也是因为 runtime,

它可以

  1. 运行时动态添加属性, 类, 方法 class_add…
  2. 运行时动态修改属性, 类, 方法 class_exchang..
  3. 运行时动态删除属性, 类, 方法 class_remove…
  4. 大概就是看它 API 里提供什么, class 开头, 或者是 Method 开头等
  5. 方法一般是要改哪个类就建立一个分类, 在里面实现要替换的方法, 在加载这个分类的 load 方法里实现替换, 具体替换的方法里在调用被替换的原方法(即调用自己名字的方法)
  6. 可以在源头把经常报错的源头给替换下方法做个判断来过滤这些错误
    参考帖子和里面的 Demo

    常用方法函数

    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
    //	发送消息
    objc_msgSend(p, @selector(eat));
    lang -rewrite-objc main.m 重新生成 c++代码查看最终生成代码
    objc_msgSend([Person class], @selector(eat));

    获取类方法
    Method imageWithName = class_getClassMethod(类, @selector(方法));
    // 交换方法地址,相当于交换实现方式
    method_exchangeImplementations(方法1, 方法2);

    // 添加方法, 调用未实现的方法时, 会调用这个方法, 可以判断是否需要动态添加, 然后添加上, 并实现好添加的函数,
    + (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(eat)) {
    class_addMethod(self, @selector(eat), eat, "v@:");
    }
    return [super resolveInstanceMethod:sel];
    }
    // 动态添加属性,
    // 首先定一个分类, 在这里添加属性
    // 定义关联的key
    static const char *key = "name";
    @implementation NSObject (Property)
    - (NSString *)name { // 根据关联的key,获取关联的值。
    return objc_getAssociatedObject(self, key); }
    - (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

    // 字典转模型
    // 获取类中的所有成员属性
    Ivar *ivarList = class_copyIvarList(self, &count)
    Ivar ivar = ivarList[i];
    ivar_getName(ivar)
    ivar_getTypeEncoding(ivar)

    思路:
    unsigned int count;

    // 获取类中的所有成员属性
    Ivar *ivarList = class_copyIvarList(self, &count);
    for (int i = 0; i < count; i++) {
    // 根据角标,从数组取出对应的成员属性
    Ivar ivar = ivarList[i];
    // 获取成员属性名
    NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
    // 处理成员属性名->字典中的key
    // 从第一个角标开始截取
    // 根据成员属性名去字典中查找对应的value

    // 二级转换:如果字典中还有字典,也需要把对应的字典转换成模型
    // 判断下value是否是字典
    if ([value isKindOfClass:[NSDictionary class]]) {
    // 字典转模型
    // 获取模型的类对象,调用modelWithDict
    // 模型的类名已知,就是成员属性的类型
    // 获取成员属性类型
    NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
    // 生成的是这种@"@\"User\"" 类型 -》 @"User" 在OC字符串中 \" -> ",\是转义的意思,不占用字符
    // 裁剪类型字符串
    // 根据字符串类名生成类对象
    if (modelClass) { // 有对应的模型才需要转
    // 把字典转模型
    // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
    // 判断值是否是数组
    // 判断对应类有没有实现字典数组转模型数组的协议返回指定数组中是哪个类的对象
    // 获取数组中字典对应的模型
    // 生成模型
    Class classModel = NSClassFromString(type);
    // 遍历字典数组,生成模型数组
    // 字典转模型
    // 添加模型到数组
    // 把模型数组赋值给value
    if 有值,才需要给模型的属性赋值
    // 利用KVC给模型中的属性赋值
    [objc setValue:value forKey:key];
    return objc;
    }

常见的使用

  1. 发送消息
  • 方法调用的本质,就是让对象发送消息。
  • objc_msgSend,只有对象才能发送消息,因此以objc开头.
  • 使用消息机制前提,必须导入#import <objc/message.h>
  • clang -rewrite-objc main.m 重新生成 c++代码查看最终生成代码
  1. 交换方法
  • 开发使用场景:系统自带的方法功能不够,给系统自带的方法扩展一些功能,并且保持原有的功能。
  • 方式一:继承系统的类,重写方法.
  • 方式二:使用runtime,交换方法.
  1. 动态添加方法
  • 开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
  • 经典面试题:有没有使用performSelector,其实主要想问你有没有动态添加过方法。

4.给分类添加属性

  • 原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。

5.字典转模型

  • 设计模型:字典转模型的第一步
    • 模型属性,通常需要跟字典中的key一一对应
    • 问题:一个一个的生成模型属性,很慢?
    • 需求:能不能自动根据一个字典,生成对应的属性。
    • 解决:提供一个分类,专门根据字典生成对应的属性字符串。
      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
      1. 发送消息:
      // 本质:让对象发送消息
      objc_msgSend(p, @selector(eat));
      // 用类名调用类方法,底层会自动把类名转换成类对象调用
      // 本质:让类对象发送消息
      objc_msgSend([Person class], @selector(eat));

      2. 交换方法
      先搞个分类
      + (void)load
      { // 交换方法
      // 获取imageWithName方法地址
      Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
      // 获取imageWithName方法地址
      Method imageName = class_getClassMethod(self, @selector(imageNamed:));
      // 交换方法地址,相当于交换实现方式
      method_exchangeImplementations(imageWithName, imageName);
      }
      + (instancetype)imageWithName:(NSString *)name
      {
      // 这里调用imageWithName,相当于调用imageName
      UIImage *image = [self imageWithName:name];
      if (image == nil) NSLog(@"加载空的图片");
      return image;
      }

3.动态添加方法
// 默认person,没有实现eat方法,可以通过performSelector调用,但是会报错。
// 动态添加方法就不会报错
[p performSelector:@selector(eat)];

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
@implementation Person
// void(*)()
// 默认方法都有两个隐式参数,
其中 types 参数为 "v@:“,按顺序分别表示:
v:返回值类型 void,若是 i 则表示 int
@:参数 id(self) ,表示 id 对象
::SEL(_cmd)
其实每一个 oc 方法都有两个隐式的参数 (id self, SEL _cmd),也可以说是由 C 语言函数再加着两个参数组成一个 oc 方法。
这些表示方法都是定义好的 (Type Encodings),关于 Type Encodings 的其他类型定义请参考官方文档
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
// 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(eat)) {
// 动态添加eat方法
// 第一个参数:给哪个类添加方法
// 第二个参数:添加方法的方法编号
// 第三个参数:添加方法的函数实现(函数地址)
// 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), eat, "v@:");

}

return [super resolveInstanceMethod:sel];
}
@end

4.给分类添加属性
主要是在需要动态的类创建分类, 然后里面声明一个 key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义关联的key
static const char *key = "name";
@implementation NSObject (Property)
- (NSString *)name
{
// 根据关联的key,获取关联的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

5.字典转模型

根据字典keyValue 生成属性描述
// 自动打印属性字符串

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
+ (void)resolveDict:(NSDictionary *)dict{
// 拼接属性字符串代码
NSMutableString *strM = [NSMutableString string];
// 1.遍历字典,把字典中的所有key取出来,生成对应的属性代码
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// 类型经常变,抽出来
NSString *type;
if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {
type = @"NSString";
}else if ([obj isKindOfClass:NSClassFromString(@"__NSCFArray")]){
type = @"NSArray";
}else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
type = @"int";
}else if ([obj isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
type = @"NSDictionary";
}
// 属性字符串
NSString *str;
if ([type containsString:@"NS"]) {
str = [NSString stringWithFormat:@"@property (nonatomic, strong) %@ *%@;",type,key];
}else{
str = [NSString stringWithFormat:@"@property (nonatomic, assign) %@ %@;",type,key];
}
// 每生成属性字符串,就自动换行。
[strM appendFormat:@"\n%@\n",str];
}];
// 把拼接好的字符串打印出来,就好了。
NSLog(@"%@",strM);
}
@end
  • 字典转模型的方式一:KVC
  • KVC字典转模型弊端:必须保证,模型中的属性和字典中的key一一对应。

    • 如果不一致,就会调用[<Status 0x7fa74b545d60> setValue:forUndefinedKey:]
      key找不到的错。
    • 分析:模型中的属性和字典的key不一一对应,系统就会调用setValue:forUndefinedKey:报错。
      1
      2
      	*  解决:重写对象的`setValue:forUndefinedKey:`,把系统的方法覆盖,
      就能继续使用KVC,字典转模型了。
  • 字典转模型的方式二:Runtime

    • 思路:利用运行时,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。
    • 步骤:提供一个NSObject分类,专门字典转模型,以后所有模型都可以通过这个分类转。
      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
      @implementation NSObject (Model)

      + (instancetype)modelWithDict:(NSDictionary *)dict
      {
      // 思路:遍历模型中所有属性-》使用运行时

      // 0.创建对应的对象
      id objc = [[self alloc] init];

      // 1.利用runtime给对象中的成员属性赋值

      // class_copyIvarList:获取类中的所有成员属性
      // Ivar:成员属性的意思
      // 第一个参数:表示获取哪个类中的成员属性
      // 第二个参数:表示这个类有多少成员属性,传入一个Int变量地址,会自动给这个变量赋值
      // 返回值Ivar *:指的是一个ivar数组,会把所有成员属性放在一个数组中,通过返回的数组就能全部获取到。
      /* 类似下面这种写法

      Ivar ivar;
      Ivar ivar1;
      Ivar ivar2;
      // 定义一个ivar的数组a
      Ivar a[] = {ivar,ivar1,ivar2};

      // 用一个Ivar *指针指向数组第一个元素
      Ivar *ivarList = a;

      // 根据指针访问数组第一个元素
      ivarList[0];

      */
      unsigned int count;

      // 获取类中的所有成员属性
      Ivar *ivarList = class_copyIvarList(self, &count);
      for (int i = 0; i < count; i++) {
      // 根据角标,从数组取出对应的成员属性
      Ivar ivar = ivarList[i];
      // 获取成员属性名
      NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
      // 处理成员属性名->字典中的key
      // 从第一个角标开始截取
      NSString *key = [name substringFromIndex:1];
      // 根据成员属性名去字典中查找对应的value
      id value = dict[key];

      // 二级转换:如果字典中还有字典,也需要把对应的字典转换成模型
      // 判断下value是否是字典
      if ([value isKindOfClass:[NSDictionary class]]) {
      // 字典转模型
      // 获取模型的类对象,调用modelWithDict
      // 模型的类名已知,就是成员属性的类型

      // 获取成员属性类型
      NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
      // 生成的是这种@"@\"User\"" 类型 -》 @"User" 在OC字符串中 \" -> ",\是转义的意思,不占用字符
      // 裁剪类型字符串
      NSRange range = [type rangeOfString:@"\""];
      type = [type substringFromIndex:range.location + range.length];
      range = [type rangeOfString:@"\""];
      // 裁剪到哪个角标,不包括当前角标
      type = [type substringToIndex:range.location];
      // 根据字符串类名生成类对象
      Class modelClass = NSClassFromString(type);
      if (modelClass) { // 有对应的模型才需要转
      // 把字典转模型
      value = [modelClass modelWithDict:value];
      }
      }
      // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
      // 判断值是否是数组
      if ([value isKindOfClass:[NSArray class]]) {
      // 判断对应类有没有实现字典数组转模型数组的协议
      if ([self respondsToSelector:@selector(arrayContainModelClass)]) {

      // 转换成id类型,就能调用任何对象的方法
      id idSelf = self;

      // 获取数组中字典对应的模型
      NSString *type = [idSelf arrayContainModelClass][key];

      // 生成模型
      Class classModel = NSClassFromString(type);
      NSMutableArray *arrM = [NSMutableArray array];
      // 遍历字典数组,生成模型数组
      for (NSDictionary *dict in value) {
      // 字典转模型
      id model = [classModel modelWithDict:dict];
      [arrM addObject:model];
      }
      // 把模型数组赋值给value
      value = arrM;
      }
      }
      if (value) { // 有值,才需要给模型的属性赋值
      // 利用KVC给模型中的属性赋值
      [objc setValue:value forKey:key];
      }
      }
      return objc;
      }
      @end
  • bug: 使用哦不继承_msgSend() 提示参数过多,
    // xcode6之前,苹果运行使用objc_msgSend.而且有参数提示
    // xcode6苹果不推荐我们使用runtime
    // 解决消息机制方法提示步骤
    // 查找build setting -> 搜索msg, 设为 NO
    // 最终生成消息机制,编译器做的事情
    // 最终代码,需要把当前代码重新编译,用xcode编译器,clang
    // clang -rewrite-objc main.m 查看最终生成代码

core data

Core Data是应用模型、持久化和大量对象图查找的最佳选择
它是专门针对模型层做的一个框架.
深入点的分析及 Demo
推荐开源库
快速使用
快速使用2
其他资料

在这之前, 要使用 coredata, 需要有这么一个环境来保存你项目中所需要的所有需要被 coredata 管理的模型类, 要有这个环境, 也就是使用这个 coredata 这个专门针对模型层的框架, 需要在项目中新建一个Coredata Model 对象, 里面会给你新建实体, 相当于你设置的模型类, 类中的其他对象属性就相当于里面的相关实体. ….目前暂时不知道怎么设置实体间的继承关系

使用它主要有这几个类需要了解, 可以这么理解, 和之前学的比较能接上轨

  • 将需要的组件创建出来形成的 coredata 堆栈
  • NSManagedObjectModel: 整个框架的操作通过这个 Model 类来先访问我们创建的对象模型图, 刚我们创建的文件里的实体连接了对应的类, 那个模型文件还要放到内存来操作, 对没有访问过的模型, 有的话就从这里拿而不会去调用 SQL 数据库, 但是, 一般我们只是把它加载进来, 不去对其直接操作
  • NSPersistentStoreCoordinator: 持久化存储调度器, 沟通模型图逻辑关系管理的 Context 和操作底层数据库实现的store, 可以说是枢纽吧, 同时, 还需要设置一个数据库, 添加操作数据库的 URL, 没有的话会自己创建
  • NSManagedObjectContext: 这个 Context 用于管理模型图的逻辑关系, 其通过调度者 cordinator 来与存储部分进行交流, 一般我们对模型对象的保存就是通过这个操作的
    -

  • bug: [NSError init] called; this results in an invalid NSError instance. It will raise an exception in a future release. Please call errorWithDomain:code:userInfo: or initWithDomain:code:userInfo:. This message shown only once.
    看来直接 init 的 NSError 不好哦, 以后可能会出问题

要用提到的那个方法

Crashlytics

在参考另一个 V2ex 客户端的作者的源码的时候, 发现里面使用了这个东西, 不知道是什么, 查了一下, 这下又长见识了~

iOS 开发工具——统计 Crash 的工具 Crashlytics

bug: *** Assertion failure in -[_UIAnimationCoordinator finishInteractiveAnimation], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.30.14/_UIAnimationCoordinator.m:154

出现在 MyV2 给导航栏加了hidesBarsOnSwipe = YES;后滚动列表出现

恩, 好了, 无解… 在设置导航栏的这个属性时出现这个问题…..好吧, 换个沉浸的方案吧…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
self.navigationController.navigationBarHidden = NO;
}
#pragma mark 滑动隐藏导航栏
//滑动隐藏导航栏 LiXingLe
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
if(velocity.y>0) {
[self.navigationController setNavigationBarHidden:YES animated:YES];
}
} else {
[self.navigationController setNavigationBarHidden:NO animated:YES];
}
}
- (void)viewDidLoad {
if ([self respondsToSelector:@selector(edgesForExtendedLayout)])
self.edgesForExtendedLayout = UIRectEdgeNone;
}

bug: Code Sign error: No provisioning profiles found: No non–expired provisioning profiles were found.Code Sign error: No provisioning profiles found: No non–expired provisioning profiles were found.

在工程是开发环境设置成9.2以上但是真机是9.0时出现, 吊针开发环境到9.0后警告消失

bug:

在真机上解析不出”tr” 的内容, 显示不出数据, 保存 tabs 字典到沙盒 Document 失败

#未做

关于宏使用的替换

如果是仅仅要对一个变量或常量来使用宏的话, 不太好, 会加长预编译的时间, 而且会把同一份常量赋值到搜游使用宏的地方, 浪费空间. 所以, 对于常量我们就只创造一个常量,
在.h 文件中, 声明外部常量,
在.m 文件中, 给这个常量赋值

1
2
3
4
5
.h
extern NSString * const xxx;

.m
NSString * const xxx = @"askjfhafhs"

离屏渲染

最近有看到好多这个概念, 之前没有碰到过, 现在学习一下
cornerRadius和maskToBounds独立作用的时候都不会有太大的性能问题,但是当他俩结合在一起,就触发了屏幕外渲染。
iOS 离屏渲染优化(附 DEMO)
离屏渲染学习笔记
http://www.jianshu.com/p/6d24a4c29e18
这个给出的方法
解决方法2, 使用贝塞尔曲线

在使用 git push 时, 没有指定分支和仓库的话会提示

Matching

matching 参数是 Git 1.x 的默认行为,其意是如果你执行 git push 但没有指定分支,它将 push 所有你本地的分支到远程仓库中对应匹配的分支。

Simple

而 Git 2.x 默认的是 simple,意味着执行 git push 没有指定分支时,只有当前分支会被 push 到你使用 git pull 获取的代码。 修改默认设置

从上述消息提示中的解释,我们可以修改全局配置,使之不会每次 push 的时候都进行提示。对于 matching 输入如下命令即可:

git config –global push.default matching

原文

RestFul 架构

阅读阮一峰的博客 理解 RESTful 架构

现在越来越多的看到, 好像一个网站就是一个软件一样, 提供着各种各样的服务,

这种 “互联网软件” 采用客户端 / 服务器模式,建立在分布式体系上,通过互联网通信,具有高延时(high latency)、高并发等特点。

提出人 Roy Thomas Fielding 这么说到

理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构

而 Restful 相关的意思, 就是

Fielding 将他对互联网软件的架构原则,定名为 REST,即 Representational State Transfer 的缩写。我对这个词组的翻译是 “表现层状态转化”。
REST 指的是一组架构约束条件和原则, 如果一个架构符合 REST 原则,就称它为 RESTful 架构。
要理解 RESTful 架构,最好的方法就是去理解 Representational State Transfer 这个词组到底是什么意思,它的每一个词代表了什么涵义。如果你把这个名称搞懂了,也就不难体会 REST 是一种什么样的设计。

关于 rest 的原则

  • Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的
  • 另一个重要的 REST 原则是分层系统,这表示组件无法了解它与之交互的中间层以外的组件。通过将系统知识限制在单个层,可以限制整个系统的复杂性,促进了底层的独立性。
    -

恩, 其实, 以上两个说明我都没看太懂, 就有个大概模糊的样子….
于是, 知乎上这个回答更清晰点, https://www.zhihu.com/question/28557115

关于 restful 在 iOS 实现的使用 restkit RestKit , 一个用于更好支持 RESTful 风格服务器接口的 iOS 库

#JS

js是运行在浏览器的脚本语言, 灵活. node.js 是运行 js 的环境, 封装自 Google 的 V8引擎,

弱类型, 好像对于类型的混合使用没有明确的限定, 都可以混合运算

定义变量: var a = 10 根据初始化的值来确定类型, number, String, boolean, 对象这几种类型, 数组也是对象类型
浏览器提示框: alert(‘…’)
控制台打印东西: console.log(….)
字符串拼接: var str = 10 + ‘10’ + ‘aklsjl’; 任何类型和字符串相加都是字符串
数组: var names = [‘akjsfk’, 10, ture, false]; 类型不限的…
或者像结构体或者字典一样 定义: var names = {name: ‘jack’, age: 10}, 访问直接 names.name, names.age
函数: 只用标识半数 function 函数名(){ 不用写返回类型, 有返回就直接返回, 有时候甚至形参都不用写, 用 arguments 代替就可以拿到传入所有参数}
对象: 定义: var dog = {age:10, name:’abu’, run: function(){}; eat: function(){}}
调用对象方法: dog.run()
类似类的定义: function Dog(){}
创建就成了: var dog1 = new Dog();

1
2
3
4
5
6
7
8
9
10
function Dog() = { // 还可以增加参数
this.name = null;
this.age = null;
this.run = function(){
console.log(this.name + '-run');
};
this.eat = function(meat){
console.log(this.name + '-eat-' + meat);
};
}

CoreAnimation

自带隐式动画, UIView 封装了 CAlayer, 我们自己创建添加上去的 layer 都带有隐式动画, 那些可动画属性会在属性改变的时候产生动画效果
可以通过 CATransaction 来禁止隐式动画

1
2


设置立体效果,(近大远小的效果)

1
2
3
CATransform3D transform = CATransform3DIdentity;
//眼睛离屏幕的距离
transform.m34 = -1 / 300.0;

渐变层

可以产生渐变颜色的效果, 添加到其他 layer 去作为子 layer,
CAGradientLayer:

1
2
3
4
5
6
7
8
9
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = self.bottomImageV.bounds;
//设置渐变的颜色
gradient.colors = @[(id)[UIColor redColor].CGColor,(id)[UIColor greenColor].CGColor,(id)[UIColor blueColor].CGColor];
//设置渐变的方向
gradient.startPoint = CGPointMake(0, 0);
gradient.endPoint = CGPointMake(1, 0);
//从哪个点开始渐变到下一个颜色
gradient.locations = @[@(0.5),@(0.8)];

bug: Xcode 7.2 no matching provisioning profiles found

更换帐号后重新运行在真机上, 出现,提示, 多运行几次让它自己修复后可行, 但是真机上描述文件还是旧的帐号, # 未做, 暂时未姐

单元测试

iOS 单元测试
1:该类中以 test 开头的方法且 void 返回类型的方法都会变成单元测试用例
2:单元测试类继承自 XCTestCase,他有一些重要的方法,其中最重要的有 3 个, setUp ,tearDown,measureBlock

leetcode

生命游戏
这个可以自己去搜一下, 就有很多人做了, 起码题目可以自己多读读, 理解下, 反正我是一开始看中文都看不懂的感觉…..
解题参考文章

可以这么简单理解下:

这些细胞的死亡和活着, 可以分为文中的四个状态
其中0和1是没有变化的状态, 后续处理可以忽略
遍历所有的细胞, 并判断其周围的细胞的生存状态, 以此判断之后自己的生存状态

很少有文章用 C 实现, 确实, 用 C 的话会比较麻烦是真的, 特别是数组传参的使用上, 但是这里用 OC 的话数组里只能装对象, 对于计算还需要转换, 比较麻烦…(现在想想, 或许还是 C 更麻烦点吧…)
C 语言传递二维数组做参数的参考博文

这里借鉴了文章中的方法, 用 C 来实现, 主要是在传递数组进来时的使用不像其他面向对象的语言那样方便. 实现是和文中的一样了, 但是验证的话目前我还没想到怎么验证, 只能是打印出来找找该死的细胞有没有死了…..

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
/**
* Game of Life */
void gameLife(int *board, int m, int n)
{
printf("\n");
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
printf("%d", *(board + i*n + j));
}
printf(",\n");
}
printf("\n");
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
int lives = 0;
// 判断上边
if(i > 0){
lives += *(board + i*n - n + j) == 1 || *(board + i*n - n + j) == 2 ? 1 : 0;
}
// 判断左边
if(j > 0){
lives += *(board + i*n + j - 1) == 1 || *(board + i*n + j - 1) == 2 ? 1 : 0;
}
// 判断下边
if(i < m - 1){
lives += *(board + i*n + n + j) == 1 || *(board + i*n + n + j) == 2 ? 1 : 0;
}
// 判断右边
if(j < n - 1){
lives += *(board + i*n + j + 1) == 1 || *(board + i*n + j + 1) == 2 ? 1 : 0;
}
// 判断左上角
if(i > 0 && j > 0){
lives += *(board + i*n - n + j - 1) == 1 || *(board + i*n - n + j - 1) == 2 ? 1 : 0;
}
//判断右下角
if(i < m - 1 && j < n - 1){
lives += *(board + i*n + n + j + 1) == 1 || *(board + i*n + n + j + 1) == 2 ? 1 : 0;
}
// 判断右上角
if(i > 0 && j < n - 1){
lives += *(board + i*n - n + j + 1) == 1 || *(board + i*n - n + j + 1) == 2 ? 1 : 0;
}
// 判断左下角
if(i < m - 1 && j > 0){
lives += *(board + i*n + n + j - 1) == 1 || *(board + i*n + n + j - 1) == 2 ? 1 : 0;
}
// 根据周边存活数量更新当前点,结果是0和1的情况不用更新
if(*(board + i*n + j) == 0 && lives == 3){
*(board + i*n + j) = 3;
} else if(*(board + i*n + j) == 1){
if(lives < 2 || lives > 3) *(board + i*n + j) = 2;
}
}
}
// 解码
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
*(board + i*n + j) = *(board + i*n + j) % 2;
}
}
}

void lifeTest()
{
int arr[][12] = {{1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1},
{1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0},
{1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0}};

for (int i = 0; i < 10; i++) {
gameLife(*arr, 3, 12);
}
}

数据结构

关于 hash:

hash 表的简单介绍和使用
简单的说就是将一个要存储的元素用 hash 算法计算出 hash 值, 将这个值对数组长度取模, 得到应该在这个数组的位置下标, 然后数组中存储的元素里是个结构: key, value, next. 一般是这样, 这个key 就是算出来的 hash 值, Value 为本来要存储的元素, Next 是当出现冲突的时候指向的一个链表节点, 由这个链表来存储冲突的元素

最小堆

最小堆是一中呢数据结构, 用二叉树形式表示, 根节点为最小的元素, 即堆顶, 父节点必须比子节点小. 当往这个树放入元素, 放入的元素是放在叶子节点, 然后与父节点比较判断大小, 比父节点小的话就替换, 然后继续向上的父节点比较, 知道父节点比自己小. 时间复杂度是 O(lgN).

JAVA

  • 和大多数面向对象的语言差不多, 这里标记不同点, 思想相通

  • 强类型, 解释型的语言, JAVA 编译器会将源码编译成字节码文件.class, 再由虚拟机运行, 运行时动态连接所需要的类, 最后生成执行文件由系统执行

  • 没有指针, 用的是引用, 相当于指针, 类, 实例, 数组, 都是引用类型
  • 类之间单继承, 接口之间多继承
  • Java 语言是分布式的:
    Java 语言支持 Internet 应用的开发,在基本的 Java 应用编程接口中有一个网络应用编程接口(java net),它提供了用于网络应用编程的类库,包括 URL、URLConnection、Socket、ServerSocket 等。Java 的 RMI(远程方法激活)机制也是开发分布式应用的重要手段。

  • 每个文件都是一个类, 且仅有一个 public 类, 文件名要和这个 public 类一样, 不然编译会报错

  • Java 的强类型机制、异常处理、垃圾的自动收集等是 Java 程序健壮性的重要保证
  • Java 通常被用在网络环境中,为此,Java 提供了一个安全机制以防恶意代码的攻击。对通过网络下载的类具有一个安全防范机制(类 ClassLoader), 并提供安全管理机制(类 SecurityManager)让 Java 应用设置安全哨兵。
  • 线程是一种特殊的对象,它必须由 Thread 类或其子(孙)类来创建
  • Java 语言的设计目标之一是适应于动态变化的环境。Java 程序需要的类能够动态地被载入到运行环境,也可以通过网络来载入所需要的类。这也有利于软件的升级。另外,Java 中的类有一个运行时刻的表示,能进行运行时刻的类型检查。
  • 非访问控制修饰符 : final: 最终, 即初始化后就不能修改其值, 常用于声明常量(OC 中是用const, abstract, strictfp
  • 调用枚举: 类.枚举.枚举值
  • 抽象关键字: abstract, 被声明为抽象的类和方法, 不能创建对象, 必须由继承自它的子类创建, 且子类需要实现里面的所以抽象方法, 抽象方法只能在抽象类中
  • 继承: 在 OC 中是冒号”:”, JAVA 中用 extends 来表示继承自什么类
  • finally: 主要是为了程序的健壮性和完整性,无论有没有异常发生都执行代码
  • implements: 表示一个类实现了接口
  • instanceof: 测试一个对象是否是某个类的实例, 类似 OC 的 [xxx isKindOf:]
  • interface: 接口,一种抽象的类型,仅有方法和常量的定义
  • new: 分配新的类实例
  • native: 表示方法用非 java 代码实现
  • package: 一系列相关类组成一个包
  • strictfp: 浮点数比较使用严格的规则
  • this: 相当于 self, 谁调用就表示谁
  • throw: 抛出异常
  • throws: 定义方法可能抛出的异常
  • transient: 修饰不要序列化的字段, 编译器会忽略这个字段
  • try: 表示代码块要做异常处理或者和 finally 配合表示是否抛出异常都执行 finally 中的代码
  • volatile: 标记字段可能会被多个线程同时访问,而不做同步, 这个修饰的变量会在值改变时强制修改共享区中的值, 在访问时强制去共享区中读取新值, 这样就保持每个线程获取到的都是一样的值
  • 接口: 在 Java 中,接口可理解为对象间相互通信的协议。接口在继承中扮演着很重要的角色。接口只定义派生要用到的方法,但是方法的具体实现完全取决于派生类。
  • Java 作为一种面向对象语言。支持以下基本概念:多态|继承|封装|抽象|类|对象|实例|方法|重载
  • 类变量必须声明为 static 类型, 默认初始值是 null,
  • 局部变量没有默认初始化值, 必须初始化了才能使用
  • 一个源文件中只能有一个 public 类, 一个源文件可以有多个非 public 类
  • 导入顺序:

    1
    2
    3
    package...
    import...
    import 语句和 package 语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。
  • JAVA 包: 包主要用来对类和接口进行分类。当开发 Java 程序时,可能编写成百上千的类,因此很有必要对类和接口进行分类。

    1
    2
      例如,下面的命令行将会命令编译器载入 java_installation/java/io 路径下的所有类
    import java.io.*;
  • 可以用.SIZE | .MIN_VALUE | .MAX_VALUE 来获得基本类型的取值范围

  • JAVA 中字符串加其他类型得到的还是字符串
  • 打印: system.out.println(“xxxx” + a + “ssss”);
  • Java 增强 for 循环:

    1
    2
    3
    4
    for(int x : numbers ){
    System.out.print( x );
    System.out.print(",");
    }
  • Java 语言为每一个内置数据类型提供了对应的包装类。所有的包装类(Integer、Long、Byte、Double、Float、Short)都是抽象类 Number 的子类。

  • 这种由编译器特别支持的包装称为装箱,所以当内置数据类型被当作对象使用的时候,编译器会把内置类型装箱为包装类。相似的,编译器也可以把一个对象拆箱为内置类型。Number 类属于 java.lang 包。
  • 当将一个基本数据类型字符传入到需要传入 Character 类型的对象时, 编译器会自动装箱, 反过来就是会自动拆箱
  • String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了。如果需要对字符串做很多修改,那么应该选择使用 StringBuffer & StringBuilder 类。
  • 用于获取有关对象的信息的方法称为访问器方法。String 类的一个访问器方法是 length() 方法,它返回字符串对象包含的字符数。
  • String 类提供了连接两个字符串的方法:string1.concat(string2); | “我的名字是 “.concat(“Runoob”); | “Hello,” + “ runoob” + “!”
  • 数组: int [] a = new [size]; | int [] a = {1,2,3,4}; new int [] {1,2,3,4,5};
  • 更多数组的方法
    -
  • 字符串: String str = “asklaf” | String str = new String(char [])
  • 求字符串长度: .length()
  • 连接字符串: str1.concat(str2) | str1 + str2
  • 格式化字符串: String.format()
  • 更多的 String 方法
  • 可变字符串: StringBuffer(线程安全) | StringBuilder(线程不安全)
  • 可变字符串的更多操作方法
  • 日期: 日期的一些使用方法
  • 休眠: Sleep(时间)函数能让当前线程休眠
  • 日历: Calendar c = Calendar.getInstance();//默认是当前日期, 修改某个日期的部分
  • .set 来设定日期的时间
  • 更多日期的方法

swift3, xcode8, 新环境接触

在xcode中, playGround中默认那个可以实时现实变量值的那栏是比较小的, 把它拉大即可看到
我发现, SSwift3中的命名更新, 解决了之前2.x 时候的痛点, 当时我就想吐槽他参数的那些混乱…

Xcode离线文档的安装:

在 Xcode 里下载文件真的是一种折磨, 特别是每次 Xcode 大版本更新都会遇到新的下载内容. 经过一翻周折, 本人找到一个方法可以轻松快速便捷地下载和安装对应的 Simulator 和 Doc 文件
在 Mac 下, 打开 Xcode, 进入 Preference 中的 Downloads 面板
点击任意的下载按钮
打开系统帮助工具 Console
稍等一会儿, 在 Xcode 里取消下载, 然后你会在 Console 里面看到对应的下载地址 (对应的 Cosnole Message 是 (DVTDownloadable: Download Cancelled. Downloadable: …) 之类的)
复制对应的链接地址, 到某雷或者任何比 Xcode 下载快的工具里
等待下载完成, 进入 /Users/#{Username}/Library/Cachesop
找到 com.apple.dt.Xcode 文件, 右击选择 Show Packages Contents
进入 Downloads 目录 (如果没有, 则手动创建一个 Downloads 目录)
将下载好的文件移动到 Downloads 目录 (最好不要改动文件名)
重启 Xcode, 回到 Downloads 面板, 点击对应下载好的 Simulator 或者 Doc
Where Amazing Happens

来源
有时候是下载完了->安装->找到安装后的Docset移动到Xcode的目录, 有时这样不成功的, 可以用上面的教程, 参考
有时候需要用命令行改权限的: 教程

第一个教程里的amazing事件应该是从放进去的文件里提取安装, 那个速度应该是安装的速度而非下载速度, 是走了Xcode的流量接口吧应该

#

安装 Python3

教程,老外的也可以搜国内的, 如果不想删除 Python2.7 , 也可以到国内教程那个安装完 Python3的就行

OC 混编 Swift

参考文章
还有这篇
这篇

一般流程是这样: 思想: 告诉 Swift/OC 需要用到哪些类, 提取头文件让编译器做语法转换

  1. 告诉Xcode、哪些Objective-c类要使用,新建.h头文件,文件名可以任意取,建议采用“项目名-Bridging-Header.h”命令格式。
  2. 设置Objective-C Bridging Header时,路径要配置正确,例如:创建的名为“ILSwift-Bridging-Header.h”文件,存于ILSwift项目文件夹的根目录下,写法如下:ILSwift/ILSwift-Bridging-Header.h
  3. 第二步可以通过创建一个 OC 文件来快速完成这个步奏, Xcode 会自动提示你创建文件, 选 yes 就好

至于 Swift 调用 Objective-C 的方法看文章后半部分

bug One of the two will be used. Which one is undefined.

http://m.blog.csdn.net/article/details?id=51354734

问题出在other linker flags。之前的编译方式是这样写的

-l”AFNetworking”

新工程里是这样的

1
2
-framework
"AFNetworking"

把所有第三方改掉,问题解决。

在 Swift3中使用 xib 文件上

collectionViewCCell:

在注册时直接注册一个 nib, 在需要拿到 cell 时会自动去缓存池中调用, 没有的话会自己创建

在 Swift3 中获取类类型

let cla = UIView.self 获取到 UIView 的类型
let view = cla()

获取对象是哪个类的
type(of: instance)

关于在 xib 文件中使用另外的 xib 文件

在 Swift 中不能在 init 里给 self 赋值, 而且里面 self 也是immutable的, 所以不能这么干

  1. 定义 xib 时一般是定义一个同名类文件, 然后在这个类文件中需要定义一个@IBOutlet 来引用这个你在 xib 里画的控件
  2. xib 中的 filesOwner 要设置成那个类文件
  3. 将里面一整个控件都拉线给刚刚设定的那个 outlet 接口, 然后里面的各个控件都可以按需要来拉线
  4. 在类文件中, 需要在几个方法里进行设置才能正确显示出控件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
override init(frame: CGRect) {
super.init(frame: frame)
self.config()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.config()
}
override func layoutSubviews() {
super.layoutSubviews()
self.moreBtnView.frame = self.bounds
}
func config() {
self.addSubview(Bundle.main.loadNibNamed("MoreButtonHeaderView", owner: self, options: nil)?.first as! UIView)
}

要想自己提供快速创建的方法的话, 请在方法中直接 init()

在 Swift3中使用 CollectionView & xib CCollectionView

#Autolayout

autoLayout 的动画实现

比较好的参考文章
autoLayout动画初体验

1
2
3
4
5
6
7
8
9
10
[self.view layoutIfNeeded];
//开始动画
[UIView animateWithDuration:3.0 animations:^{
[subView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(@300);
}];
//更新约束 在某个时刻约束会被还原成frame使视图显示
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
  1. 包起来的使用约束的动画, 需要在动画的每次调用(即包起来的方法)都对view进行layoutIfNeed的调用: self.view.layoutIfNeeded, 注意!: 修改约束的操作可以房外面, 当需要修改的时候把self.view.layoutIfNeeded用UIView.animateWithDuration包起来即可
  2. 改变某个控件的frame. 这种情况适用于自身的改变无需其他的View来配合布局, 是一个独立的视觉效果, 不然需要其他的有相关约束的View来进行配合的话, 就要更新约束了用1.的方法

参考使用帖子/)

主要是在 layoutIfNeeded 方法后还用 UIView 的动画方法包裹住改变约束的 code, 里面还是需要 layoutIfNeeded 来更新约束所改变的 frame

使用 autoLayout 时使父控件随自己的子控件来调整 size

  1. 使用 scrollView , 设定一个 View 作为 contentView, 子控件都布局在上面, 约束设置能撑起整个 View, 就会根据内容调整
  2. 有博客说在子控件的上下前后的路子被打通撑起来后, 父控件就会根据里面的约束来自动计算出自己的大小(不懂是不是真的, 应该不行, 付控件在其他地方作为子空间时, 那不就冲突了嘛…)

在 xcode8 开始, xcode8自动继承了之前的 vvdocument 插件, 可以按’command’+’option’+’/‘ 来给下面的函数或者是变量插入文档注释

关于在webView中调用的键盘加上accessoryView的方法

参考
参考

UISearchController

创建时传nil来表示使用当前控制器同一个view来显示搜索结果, 不需要在其他控制器显示, 如果在其他控制器显示, 旧创建一个控制器, 并在创建时将这个控制器传入

1
2
3
4
searchController.searchResultUpdater = self	// 设置更新代理, 会在searchBar内容改变时调用代理通知
searchController.dimsBackgroundDuringPresentation = false // 显示时暗化背景, 在其他View显示结果时很有用,
definesPresentationContext = true // 当UISearchController被激活时跳到下个界面时不会有searchBar留在界面上
tableView.tableHeaderView = searchController.searchBar // tableHeaderView显示searchBar

对于过滤(一定程度上的搜索)可以对数组中的元素进行过滤, 用filter函数, 传入闭包, 返回满足搜索条件的结果, 返回的是一个bool值, 符合条件的传入参数旧返回TRUE

#WKWebView
参考帖子
在支持iOS8以上的时候, 可以直接使用WKWebView来替代UIWebView, 来获得更好的性能和内存使用, 并且能和js的调用会更有效率

关于AutoLayout出现布局错误的提示 :

参考文章中调试那一段
如果不是很明确是哪个视图导致的问题,你就需要通过内存地址来辨认视图。最简单的方法是使用调试控制台。你可以打印视图本身或它父视图的描述,甚至递归描述的树视图。这通常会提示你需要处理哪个视图。

1
2
3
4
5
6
7
8
9
10
11
12
(lldb) po 0x7731880
$0 = 124983424 <UIView: 0x7731880; frame = (90 -50; 80 100);
layer = <CALayer: 0x7731450>>

(lldb) po [0x7731880 superview]
$2 = 0x07730fe0 <UIView: 0x7730fe0; frame = (32 128; 259 604);
layer = <CALayer: 0x7731150>>

(lldb) po [[0x7731880 superview] recursiveDescription]
$3 = 0x07117ac0 <UIView: 0x7730fe0; frame = (32 128; 259 604); layer = <CALayer: 0x7731150>>
| <UIView: 0x7731880; frame = (90 -50; 80 100); layer = <CALayer: 0x7731450>>
| <UIView: 0x7731aa0; frame = (90 101; 80 100); layer = <CALayer: 0x7731c60>>

一个更直观的方法是在控制台修改有问题的视图,这样你可以在屏幕上标注出来。比如,你可以改变它的背景颜色:

1
(lldb) expr ((UIView *)0x7731880).backgroundColor = [UIColor purpleColor]

确保重新执行你的程序,否则改变不会在屏幕上显示出来。还要注意将内存地址转换为 (UIView *) ,以及额外的圆括号,这样我们就可以使用点操作。另外,你当然也可以通过发送消息来实现:

(lldb) expr [(UIView *)0x7731880 setBackgroundColor:[UIColor purpleColor]]
另一种方法是使用 Instrument 的 allocation 模板,根据图表分析。一旦你从错误消息中得到内存地址(运行 Instruments 时,你从 Console 应用中获得的错误消息),你可以将 Instrument 的详细视图切换到 Objects List 页面,并且用 Cmd-F 搜索那个内存地址。这将会为你显示分配视图对象的方法,这通常是一个很好的暗示(至少对那些由代码创建的视图来说是这样的)。

你也可以通过改进错误信息本身,来更容易地在 iOS 中弄懂不可满足的约束条件错误到底在哪里。我们可以在一个 category 中重写 NSLayoutConstraint 的描述,并且将视图的 tags 包含进去:

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
@implementation NSLayoutConstraint (AutoLayoutDebugging)
#ifdef DEBUG
- (NSString *)description
{
NSString *description = super.description;
NSString *asciiArtDescription = self.asciiArtDescription;
return [description stringByAppendingFormat:@" %@ (%@, %@)",
asciiArtDescription, [self.firstItem tag], [self.secondItem tag]];
}
#endif
@end
如果整数的 tag 属性信息不够的话,我们还可以得到更多新奇的东西,并且在视图类中增加我们自己命名的属性,然后可以打印到错误消息中。我们甚至可以在 Interface Builder 中,使用 identity 检查器中的 “User Defined Runtime Attributes” 为自定义属性分配值。

@interface UIView (AutoLayoutDebugging)
- (void)setAbc_NameTag:(NSString *)nameTag;
- (NSString *)abc_nameTag;
@end

@implementation UIView (AutoLayoutDebugging)
- (void)setAbc_NameTag:(NSString *)nameTag
{
objc_setAssociatedObject(self, "abc_nameTag", nameTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)abc_nameTag
{
return objc_getAssociatedObject(self, "abc_nameTag");
}
@end

@implementation NSLayoutConstraint (AutoLayoutDebugging)
#ifdef DEBUG
- (NSString *)description
{
NSString *description = super.description;
NSString *asciiArtDescription = self.asciiArtDescription;
return [description stringByAppendingFormat:@" %@ (%@, %@)", asciiArtDescription, [self.firstItem abc_nameTag], [self.secondItem abc_nameTag]];
}
#endif
@end

通过这种方法错误消息变得更可读,并且你不需要找出内存地址对应的视图。然而,对你而言,你需要做一些额外的工作以确保每次为视图分配的名字都是有意义。

Daniel 提出了另一个很巧妙的方法,可以为你提供更好的错误消息并且不需要额外的工作:对于每个布局约束条件,都需要将调用栈的标志融入到错误消息中。这样就很容易看出来问题涉及到的约束了。要做到这一点,你需要 swizzle UIView 或者 NSView 的 addConstraint: / addConstraints: 方法,以及布局约束的 description 方法。在添加约束的方法中,你需要为每个约束条件关联一个对象,这个对象描述了当前调用栈堆栈的第一个栈顶信息(或者任何你从中得到的信息):

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
static void AddTracebackToConstraints(NSArray *constraints)
{
NSArray *a = [NSThread callStackSymbols];
NSString *symbol = nil;
if (2 < [a count]) {
NSString *line = a[2];
// Format is
// 1 2 3 4 5
// 012345678901234567890123456789012345678901234567890123456789
// 8 MyCoolApp 0x0000000100029809 -[MyViewController loadView] + 99
//
// Don't add if this wasn't called from "MyCoolApp":
if (59 <= [line length]) {
line = [line substringFromIndex:4];
if ([line hasPrefix:@"My"]) {
symbol = [line substringFromIndex:59 - 4];
}
}
}
for (NSLayoutConstraint *c in constraints) {
if (symbol != nil) {
objc_setAssociatedObject(c, &ObjcioLayoutConstraintDebuggingShort,
symbol, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
objc_setAssociatedObject(c, &ObjcioLayoutConstraintDebuggingCallStackSymbols,
a, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
}

@end
一旦你为每个约束对象提供这些信息,你可以简单的修改 UILayoutConstraint 的描述方法将其包含到输出日志中。

- (NSString *)objcioOverride_description {
// call through to the original, really
NSString *description = [self objcioOverride_description];
NSString *objcioTag = objc_getAssociatedObject(self, &ObjcioLayoutConstraintDebuggingShort);
if (objcioTag == nil) {
return description;
}
return [description stringByAppendingFormat:@" %@", objcioTag];
}
检出这个GitHub仓库,了解这一技术的代码示例。

#有歧义的布局
另一个常见的问题就是有歧义的布局。如果我们忘记添加一个约束条件,我们经常会想为什么布局看起来不像我们所期望的那样。UIView 和 NSView 提供三种方式来查明有歧义的布局:hasAmbiguousLayout,exerciseAmbiguityInLayout,和私有方法 _autolayoutTrace。

顾名思义,如果视图存在有歧义的布局,那么 hasAmbiguousLayout 返回YES。如果我们不想自己遍历视图层并记录这个值,可以使用私有方法 _autolayoutTrace。这将返回一个描述整个视图树的字符串:类似于 recursiveDescription 的输出(当视图存在有歧义的布局时,这个方法会告诉你)。

由于这个方法是私有的,确保正式产品里面不要包含调用这个方法的任何代码。为了防止你犯这种错误,你可以在视图的category中这样做:

1
2
3
4
5
6
7
@implementation UIView (AutoLayoutDebugging)
- (void)printAutoLayoutTrace {
#ifdef DEBUG
NSLog(@"%@", [self performSelector:@selector(_autolayoutTrace)]);
#endif
}
@end

自动布局中更新约束使其动画

直接使用update更新约束后, 系统会自动按没一点来变化, 如果是跳跃性的变化, 可能还需要加上UIView的动画包起来

Git Submodule

1
$ git submodule add 源地址 目标目录

使用pod创建私有库

参考文章

可以使用pod的一个命令在一个文件夹内create一个库, 会创建一个文件夹, 这里就是你所需要的所有文件,
在创建的过程中, 会需要输入email, 和需要确实是否含有某些功能的一些信息

完了, 就创建了一个pod工程, 资源文件可以拉到asset文件夹中, 提供的pod的文件都可以放到classes文件夹中,
完了再进入Example文件夹中pod install, 就可以在这个文件夹内的workspace工程项目中打开, code一个栗子

这个整个pod文件夹, 可以放到项目里, 在podFile中指定路径, 如:
pod ‘DDDropMenu’, :path => ‘./Vendor/DDDropMenu’

完整创建pod并上传到pod管理供别人使用大概就是下面几个步骤:

1.创建并设置一个私有的Spec Repo。
2.创建Pod的所需要的项目工程文件,并且有可访问的项目版本控制地址。
3.创建Pod所对应的podspec文件。
4.本地测试配置好的podspec文件是否可用。
5.向私有的Spec Repo中提交podspec。
6.在个人项目中的Podfile中增加刚刚制作的好的Pod并使用。
7.更新维护podspec

调试技巧

参考文章

使用IDE提供的功能

  1. 打断点处会出现当前作用域的变量, 右击变量, 选择”watch xxx”, 旧能在这个变量的值发生改变时候打印到控制台
  2. 右键断点 -> edit -> condition: 可以设置断点的条件
  3. 在2的基础上选择action: Log Message, 就能在断点的地方设置一个行为, 可以是执行脚本什么的, 打印@valueName@可以打印出变量值
  4. 添加Symbolic Break point: 在command+4下买呢的”+”添加, 在Symbol写上方法函数名, 就能在程序中所有被调用到这些方法函数时触发断点, 也能对特定了类监听这个断点, 就写成这个类调用这个方法的样子就好了
  5. edit scheme -> Diagnose: 开启僵尸对象, 在访问到僵尸对象即野指针时会触发断点

使用Console(lldb 命令)

参考1
参考2
参考3
参考4

Profile(instruments)

command + I : 选择profile模板

  1. Leak: 内存泄露工具, 左上红点开始监控, 就像录像一样, 录下程序运行过程中的问题, 他会自动加载allocation工具(可以监控内存分配)
  2. 可以直接运行analyze, 快速发现release bug in code,, 继承过程中父类方法缺失等问题
    更多profile的介绍

视图调试

在程序运行时, 点击调试栏三个矩形叠加的按钮‘Debug View Hierarchy‘, 可以显示View的层级结构

  • bug: 当一个控件更新他的frame时, 比如使用动画, 而在创建这个View时是使用了autolayout的方法设置的frame,在这个View更新时会使用autolayout的方案生成frame, 但是这个先后顺序不确定, 有时候设置的View.frame后达不到想要的效果, 所以在使用autoLayout布局时, 改变对应的地方时最好也要使用autoLayout. 不然一开始偶是哟个frame的方式设定位置.

  • bug: 设置tableView的sectionHeader的高度时, 出现设置无效的情况
    在plain的情况下, 需要在代理方法中指定高, 在grouped的时, 需要2个都指定, footer & header. 且返回的高度不能是0

  • bug: 根据View的frame设置位置时, 会出现不是自己要的情况
    当有导航栏时且View是在导航栏bottom开始时, 一开始View是原始大小, 显示出来后view的大小位置改变了, 所以计算出来的东西不同预期

  • bug: 在使用协议构造一个context的时候, 当传递到netApi时, context没了, 发生了野指针错误
    使用的协议是非引用型的, 在传递的过程中可能会丢失, 使Protocol: NSObjectProtocol, 解决问题.
    在OC中使用创建class 或者是Protocol, 还是继承自自有的NSObject和NSObjectProtocol比较好, 因为他们对这个框架的使用做了最基础的功能和限定

bug: 刷新令牌的时候, 显示返回empty

在请求过后Presenter就释放了, 返回后没有东西持有, 造成不读取数据的情况.
resolve: 设置一个closure, 在返回时候调用来释放自己(对于不需要的进行持有的对象)

bug: 在一个controller里, 自定义约束的cell正常, 在另一个controller里面约束崩溃, 其中一个是传有数据的, 猜想是这个的问题

这里面因为plus的机型上的是三倍屏, 当设置的约束有0.5时, 在@2x的时候, 正好是1px, 这个在@3x的时候会变成在0.33333…0.66666之间, 也就是在1px和2px之间, 得到的计算结果是2px, 和原先字啊@2x视图中设置时候的1px产生了冲突.
尽量不要有0.5像素的东西设置在约束里.

bug: 在试过持有preseter后发现获取refreshToken仍然获取不到.

resolve: 在更新后只设置了tokenBean的RefreshToken, UserBean的Token没有设置上去, 而从Secure里拿到的是UserInfo. 更新时把RefreshToken设置给userbean就行,.

Swift3 关于通过字符串调用函数:

在Swift3中我没有找到通过将String转换为function / selector的方法, 即不能像OC一样通过转换字符串来发送消息从而调用某个方法.
有时候我们想根据一个设定好点 modal来取出其指定的方法来执行, 这样就不用每次都判断.
在Swift3中的enum中, 可以设定enum为String, 可以通过字符串来转换成对应的枚举, 而且enum里可以有计算属性和函数. 函数中判断是哪个case, 可以执行不一样的函数, 通过这样的曲线旧可以从String到函数的调用

H5调用方法

在H5的调用方法中, 我们可以只注册一个方法, 这样在注入的JS, Swift代码中的register里旧不用写这么多的函数绑定, 通过传递一个字典参数(我们可以is核定指定方法的Key-Value为: func: XXXX), 就能转成enum, 再在对应的case中执行

替换back bar button item 的图标

1、上面的方法,只是利用了系统的自带”<”, 有人可能就问,那如果我想自定义图标呢?

//下面两行就是自定义,替换系统的“<”图标

1
2
3
4
5
【1】    [self.navigationController.navigationBar setBackIndicatorImage:[UIImage imageNamed:@"navibar_back_btn_bg_normal.png"]];
[self.navigationController.navigationBar setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"navibar_back_btn_bg_normal.png"]];

UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStyleBordered target:nil action:nil];
self.navigationItem.backBarButtonItem = backItem;

系统里的返回手势

参考资料
查考资料

计算图文混排后的高

1
2
3
4
RCLabel* info = [[RCLabel alloc] initWithFrame:CGRectMake(10,0,300,100)];
[info setFont:[UIFont boldSystemFontOfSize:14]];
info.componentsAndPlainText = [RCLabel extractTextStyle:content];
CGSize optimalSize = [info optimumSize];

发现: 只在extension中写默认实现的话, 在外面调用进来有可能会直接调用默认实现, 而不会走类中实现的方法…

bug: *** Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[<Landed.AboutViewController 0x7fc647db8ad0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key descLabel.’

常量的构造

可以使用struct, enum: String 来构造一些常量, 这样更有管理性.
这样的 struct 我们并不打算进行初始化, 所以我们将默认的初始化方法声明为 private. : private init() {}
我们可以使用static 来声明一个静态变量, 这样在使用这些定义的常量的时候旧不用每次都初始化 struct.
在类中使用 static 声明一个属性的话, 效果个 final class 是一样的, 子类将不能继承和重写这些属性
在需要使用的常量和他的变量名一致时, 使用 enum 会更有效率.

使用 Swift , 添加语法糖来使一些东西更好用, 不仅局限于 Swift 给的 Api

使用 extension Protocol 来扩展一些方法函数
associatedType aaa : bbb: 关联类型, 即声明一个类型, 类似 OC 中的 class xxx, 声明这个 Type: 遵守的bbb协议, 这样在这个 extension 中我们就可以使用这个类, 这个 Type 的具体类型, 在遵守了这个协议的 class 或者 struct 中提供, 即使用 typealias xxx = aaa

我们可以在 extension 中定义一个 enum, 来声明是在某个协议下使用的enum.

关于两个遵守了同一协议, 并 提供了相同的 Key 的时候, 这时候的操作是冲突的, 所以应该为 Key 做一个命名空间

1
2
3
4
5
extension KeyNamespaceable {
func namespace<T>(_ key: T) -> String where T: RawRepresentable {
return "\(Self.self).\(key.rawValue)"
}
}

这也是在协议 Protocol 中使用 self, 的方法之一, 使用泛型, 确定类型之后 self 即指代这个类型的对象实例. 如上, Self.self是获取类型
也可以使用 where 关键字来限定使用这个 extension 的条件
参考文章

!!!

bug: 使用自适应高度的cell 时, 在 plus 尺寸上会有breaking constraint 的 warning, 但是在其他屏幕的尺寸不会这样, 布局的时候也没有出现问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2017-03-31 23:42:15.351323 Landed[61878:815133] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x608000a9ec80 UIImageView:0x7fad98e39320.height == 82 (active)>",
"<NSLayoutConstraint:0x600000c83980 V:|-(20)-[UIImageView:0x7fad98e39320] (active, names: '|':UITableViewCellContentView:0x7fad98e39170 )>",
"<NSLayoutConstraint:0x600000c839d0 V:[UIImageView:0x7fad98e39320]-(21)-| (active, names: '|':UITableViewCellContentView:0x7fad98e39170 )>",
"<NSLayoutConstraint:0x600000c85ff0 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7fad98e39170.height == 123 (active)>"
)

Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x608000a91030 UIImageView:0x7fcab565eb10.height == 82 (active)>

解决方法: 通过调低某个相关约束的 prioraty…, 有可能是在3x 的机型上有精度问题, 在精度小的时候, 就会变成0, Break 约束

bug: 将一个 controller present 出来时, 背景不管怎么设置半透明或透明, 都会在显示完成后变成一块黑色!

使用 present 方法出来的 controller, window 会降其他的 View 移除, 其上显示的就是这个 controller 的 View, 所以这块黑色的就是 window 的 backgroundColor!!!, 他本来就是黑色的!!!

enumd 的高级用法

安装Cathge

按 github 上说的用 brew install Cathege就好

继承友盟分析工具

bug: 手势和 tableViewCell 的触摸事件冲突, Google 的结果也不能解决

参考
参考
参考

bug: Swift 继承 OC 的类不能重写.m 文件里的方法

难道要修改 oc 文件里的东西了? 不能通过继承来修改方法吗

bug:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2017-04-01 00:00:03:081 Landed[63651:842216] UIViewController+MBP: lodingMessage: 正在加载消息摘要
2017-04-01 00:00:03:112 Landed[63651:842216]
2017-04-01 00:00:03:113 Landed[63651:842216] Moya_Logger: [01/04/2017 00:00:03] Request: http://139.196.34.65:13001//message/conversation
Moya_Logger: [01/04/2017 00:00:03] Request Headers: ["X-Client-Id": "3000", "auth": ""]
Moya_Logger: [01/04/2017 00:00:03] HTTP Request Method: GET
2017-04-01 00:00:03:167 Landed[63651:842216] Moya_Logger: [01/04/2017 00:00:03] Response: <NSHTTPURLResponse: 0x60000083aaa0> { URL: http://139.196.34.65:13001//message/conversation } { status code: 500, headers {
Connection = close;
"Content-Length" = 109;
"Content-Type" = "application/json;charset=UTF-8";
Date = "Fri, 31 Mar 2017 16:00:03 GMT";
"X-TIMESTAMP" = 1490976003176;
} }
{
"exCode" : "PARAM_NULL",
"message" : "参数receiverId值为空",
"logRef" : "f84c7371-11bb-423d-a934-0328ce97aa2b"
}
2017-04-01 00:00:03:178 Landed[63651:842216] 参数receiverId值为空

bug: 在 viewController 中设置 subview 失败, subview 是 lazy 模式添加到 view.subviews 的,

1
2
3
4
5
6
7
8
9
10
11
12
13
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DDLogDebug("\(agents.isEmpty)")
self.agentInfoView.isHidden = agents.isEmpty
self.agentEmptyView.isHidden = !self.agentInfoView.isHidden

if !agentInfoView.isHidden {
agentInfoView.tableViewHeight.constant = CGFloat(agents.count * 100)
agentInfoView.setNeedsLayout()
agentInfoView.layoutIfNeeded()
}

self.view.backgroundColor = .clear

在控制台打印信息, 出现

1
2
3
4
(lldb) po agentEmptyView
fatal error: call of deleted method
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

解决 : 有可能是字啊设置时, 有些 View 还没有添加到其 superView 中, 在 lazy 创建时进行 addsubview 即可

bug: 子类中调用重写的父类方法: override 时, 不走自己override 的方法, 而是直接调用 super 的方法,

解决: 不要在 extension 中对 super 的方法进行 override!. 也不要把需要子类 override 的方法写到 extension!! extension 中有点私有方法的意思….

按照底层来说, 像 OC 的 Category 的话, 应为是运行时才决定只想的方法, 所以在运行的时候会去先找自己有没有这个方法的实现, 没有时才会去父类里面找, 而 Swift 的编译是静态的, 在编译的时候就决定了要到那哪里去执行这个方法, 而这个方法的声明是在父类, 即运行的时候会直接在本类的作用域下寻找, 没有才去父类, 而写在 extension 中的 override, 在编译时不会被当做一个实现的方法来识别

bug: 在打包的时候, 碰到这个错误 :

原因是有些 png 带有一些多余的信息, 导致打包时复制不过, 重新格式化一下这些 png 图片, 使用命令行, 进入到相关目录下, 执行

1
mkdir pngs; sips -s format png *.* --out pngs

这样会将目录下的文件全部重新格式化, 并将其保存到一个 pngs 目录下, 也可以这样

1
sips -s format png *.png

资源图片文件中不止有 png 的话, 可以将.png -> .*

1
2
3
sips -s format png *.*

陈老师说的是这个命令: find . -type f -name '*.png' -exec xattr -c {} \;

这些最好在只有图片的目录下做, 不清楚这个命令的作用, 有其他文件的话不要把这些文件也转成 png 了…

bug: 服务器错误??

UIViewController+MBP: showMBPError message: A server with the specified hostname could not be found.
2017-04-08 20:38:32:826 Landed[28180:5184854] 13333333333
2017-04-08 20:38:32:828 Landed[28180:5184854] Moya_Logger: [08/04/2017 20:38:32] Request: http://ykestate.lvjin.work/api/user/salt
Moya_Logger: [08/04/2017 20:38:32] Request Headers: [“Content-Type”: “application/x-www-form-urlencoded; charset=utf-8”]
Moya_Logger: [08/04/2017 20:38:32] HTTP Request Method: POST
Moya_Logger: [08/04/2017 20:38:32] Request Body: mobile=13333333333
2017-04-08 20:38:32:849 Landed[28180:5184854] Moya_Logger: [08/04/2017 20:38:32] Response: Received empty network response for salt(“13333333333”).
2017-04-08 20:38:32:850 Landed[28180:5184854] A server with the specified hostname could not be found.
2017-04-08 20:38:32:850 Landed[28180:5184854] error: A server with the specified hostname could not be found.
2017-04-08 20:38:32:850 Landed[28180:5184854] error code::::::::::
2017-04-08 20:38:32:850 Landed[28180:5184854] UIViewController+MBP: showMBPError message: A server with the specified hostname could not be found.
2017-04-08 20:39:42:821 Landed[28180:5184854] 13333333333
2017-04-08 20:39:42:822 Landed[28180:5184854] Moya_Logger: [08/04/2017 20:39:42] Request: http://ykestate.lvjin.work/api/user/salt
Moya_Logger: [08/04/2017 20:39:42] Request Headers: [“Content-Type”: “application/x-www-form-urlencoded; charset=utf-8”]
Moya_Logger: [08/04/2017 20:39:42] HTTP Request Method: POST
Moya_Logger: [08/04/2017 20:39:42] Request Body: mobile=13333333333
2017-04-08 20:39:42:934 Landed[28180:5184854] Moya_Logger: [08/04/2017 20:39:42] Response: <NSHTTPURLResponse: 0x6080020392c0> { URL: http://ykestate.lvjin.work/api/user/salt } { status code: 200, headers {
Connection = “keep-alive”;
“Content-Type” = “application/json;charset=UTF-8”;
Date = “Sat, 08 Apr 2017 12:39:42 GMT”;
Server = nginx;
“Transfer-Encoding” = Identity;
Vary = “Accept-Encoding”;
“X-TIMESTAMP” = 1491655182872;
} }
{
“salt” : “anvcA22J3wOOJx0AaTzj6zT2nHpqkv”
}
2017-04-08 20:39:42:939 Landed[28180:5184854] Moya_Logger: [08/04/2017 20:39:42] Request: http://ykestate.lvjin.work/api/user/login
Moya_Logger: [08/04/2017 20:39:42] Request Headers: [“Content-Type”: “application/x-www-form-urlencoded; charset=utf-8”]
Moya_Logger: [08/04/2017 20:39:42] HTTP Request Method: POST
Moya_Logger: [08/04/2017 20:39:42] Request Body: clientId=3000&mobile=13333333333&signature=99-qZo50uKnxDIGTyzNSXjbj0pAwUFb_UlBPFNKpwzQ
2017-04-08 20:39:42:994 Landed[28180:5184854] Moya_Logger: [08/04/2017 20:39:42] Response: <NSHTTPURLResponse: 0x608002039840> { URL: http://ykestate.lvjin.work/api/user/login } { status code: 500, headers {
Connection = “keep-alive”;
“Content-Length” = 102;
“Content-Type” = “application/json;charset=UTF-8”;
Date = “Sat, 08 Apr 2017 12:39:42 GMT”;
Server = nginx;
“X-TIMESTAMP” = 1491655182929;
} }
{
“logRef” : “ee187323-a46e-4a0c-ac5d-3db276642916”,
“message” : “密码错误”,
“exCode” : “user.login.error”
}
2017-04-08 20:39:42:995 Landed[28180:5184854] 密码错误
2017-04-08 20:39:42:995 Landed[28180:5184854] error: 密码错误
2017-04-08 20:39:42:996 Landed[28180:5184854] UIViewController+MBP: showMBPError message: 密码错误
2017-04-08 20:39:43:007 Landed[28180:5184854] start unlock

自定义 UICollectionViewLayout

参考1
参考2

其中想要用 collectionView 做为轮播图的话, 或者是像在滑动时停止的时候, 正好停在一个 cell 的区域, 就要在这个方法这种重写,

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
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
// guard abs(velocity.x) < 0.5 else {
// return proposedContentOffset
// }
let inset: Int = 15
let vcBounds = self.collectionView!.bounds
var candidateContentOffsetX: CGFloat = proposedContentOffset.x

for attributes in self.layoutAttributesForElements(in: vcBounds)! as [UICollectionViewLayoutAttributes] {

if abs(velocity.x) < 0.1 {
// 慢划处理 -- 根据中心确定位置
if vcBounds.origin.x < attributes.center.x {
candidateContentOffsetX = attributes.frame.origin.x - CGFloat(inset)
break
}
} else {
// 快划处理 -- 跳下一个
if velocity.x > 0 {
// 向右处理 -- 直接使用最后一个的位置
candidateContentOffsetX = attributes.frame.origin.x - CGFloat(inset)
} else {
// 向左处理 -- 直接使用第一个的位置
candidateContentOffsetX = attributes.frame.origin.x - CGFloat(inset)
break
}
}

}
collectionView?.decelerationRate = 0.0
print(String(describing: velocity))
return CGPoint(x: candidateContentOffsetX, y: proposedContentOffset.y)
}

第三方登录/分享

需要哪个平台的登录, 就需要在那哪个平台注册一个 APP, 然后拿到对应的 APPKey/ID, AppSecret (可能不同的平台有所不同, 反正是这两个东西)

在新版本的 iOS10的时候(现在不管怎么样都需要配置这些东西了), 需要获得隐私权限

在 info.plist 里声明这些权限

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
After ios 10 you have to define all the system feature access in inof.plist as below:

`Calendar`
Key : Privacy - Calendars Usage Description
Value : $(PRODUCT_NAME) calendar events
Reminder :
Key : Privacy - Reminders Usage Description
Value : $(PRODUCT_NAME) reminder use
Contact :
Key : Privacy - Contacts Usage Description
Value : $(PRODUCT_NAME) contact use
Photo :
Key : Privacy - Photo Library Usage Description
Value : $(PRODUCT_NAME) photo use
Bluetooth Sharing :
Key : Privacy - Bluetooth Peripheral Usage Description
Value : $(PRODUCT_NAME) Bluetooth Peripheral use
Microphone :
Key : Privacy - Microphone Usage Description
Value : $(PRODUCT_NAME) microphone use
Camera :
Key : Privacy - Camera Usage Description
Value : $(PRODUCT_NAME) camera use
Location :
Key : Privacy - Location Always Usage Description
Value : $(PRODUCT_NAME) location use

Key : Privacy - Location When In Use Usage Description
Value : $(PRODUCT_NAME) location use
Heath :
Key : Privacy - Health Share Usage Description
Value : $(PRODUCT_NAME) heath share use

Key : Privacy - Health Update Usage Description
Value : $(PRODUCT_NAME) heath update use
HomeKit :
Key : Privacy - HomeKit Usage Description
Value : $(PRODUCT_NAME) home kit use
Media Library :
Key : Privacy - Media Library Usage Description
Value : $(PRODUCT_NAME) media library use
Motion :
Key : Privacy - Motion Usage Description
Value : $(PRODUCT_NAME) motion use
Speech Recognition :
Key : Privacy - Speech Recognition Usage Description
Value : $(PRODUCT_NAME) speech use
SiriKit :
Key : Privacy - Siri Usage Description
Value : $(PRODUCT_NAME) siri use
TV Provider :
Key : Privacy - TV Provider Usage Description
Value : $(PRODUCT_NAME) tvProvider use
You can get detail information in this link.

可以直接打开 plist 的源码, 复制粘贴响应的权限 key-value 会快一些

1
2
3
4
5
6
7
8
9
10
11
12
相机权限描述:
<key>NSCameraUsageDescription</key>
<string>cameraDesciption</string>
通信录:
<key>NSContactsUsageDescription</key>
<string>contactsDesciption</string>
麦克风:
<key>NSMicrophoneUsageDescription</key>
<string>microphoneDesciption</string>
相机:
<key>NSPhotoLibraryUsageDescription</key>
<string>photoLibraryDesciption</string>

关于使用其他库时出现的 code sign 问题

原因在于在创建这个项目时, 别人用了他们的签名, 在使用时, 我们的签名出现了冲突或找不到作者的前面
– 可以修改项目的 identifier, 用自己的 com.自己.项目名, 差不多就这样, 还不行的还找到 info 的 general 项的 code signing, 搞搞

  • bug: 在跑别人的项目中时: The current deployment target does not support automated weak references
    说明:因为
    weak是只支持ios5级以后的项目。

解决方法:有些可以通过设置ios最低版本号解决,但有些还是解决不了,可以通过在podfile文件中在platform :ios这行,加一个ios的版本(5.0以上):应该可以解决(至少我是通过这种方法解决的)

参考

关于Swift的属性问题

更清晰了一点: 关于存储实现, 可以有属性监听器didSet, 这样就可以设置值的时候做一些操作

关于Swift中函数添加private限制

internal的函数, 如果在有OC的bridge时, internal的func/props都会进bridge的。那个bridge文件很大.

App Groups

在开发插件extension的过程中, 会用到共享数据, extension不能单独发布, 需要一个宿主APP, 但是其身份又是一个单独的APP.
在APP和其extension共享数据时, 就会用到APP groups.
参考文章
参考文章2
参考项目: TKeyboard
项目详情博客

statusbar 显示颜色的控制

bug: 修改viewcontroller中preferStatusBarStyle的属性后, 并不起作用.

参考博客
如果是在NavigationController中的controller的话, 没有实现那个child….的方法的话, 就不会走controller的方法, 如果需要在controller中控制statusbar的颜色的话, 在子类化的NavigationController中实现这个方法, 返回self.topViewcontroller, 或者在实现了preferStatusBarStyle属性里返回最后一个childController中设定的style

正则表达式

屏幕边缘右滑返回手势

在我们自定义了leftBarButton时, 系统的边缘手势返回动作会失效. 这个体验是很不好的
我们可以使用第三方框架, 也可以写一点代码来重新实现它的手势
参考资料

在设定leftbarbutton后系统侧滑不响应

一、让应用支持侧滑返回

导入FDFullscreenPopGesture库,pod安装一下即可,安装后无须再加任何代码,应用所有页面都将支持全屏侧滑。github地址:https://github.com/forkingdog/FDFullscreenPopGesture
如果你或者你们产品不喜欢全屏侧滑,还是喜欢在屏幕左边缘处才触发侧滑,那可以看看第二种方式:
写一个UINavigationController的子类,设置导航控制器的代理为自己,实现下面这个代理方法:

1
2
3
4
5
6
7
8
9
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
// 让系统的侧滑返回生效
self.interactivePopGestureRecognizer.enabled = YES;
if (viewController == self.viewControllers[0]) {
self.interactivePopGestureRecognizer.delegate = self.tz_PopDelegate; // 不支持侧滑
} else {
self.interactivePopGestureRecognizer.delegate = nil; // 支持侧滑
}
}

在viewDidLoad方法中,给popDelegate赋值: self.popDelegate = self.interactivePopGestureRecognizer.delegate 。这样操作后,即使给控制器加了leftBarButtonItem,系统的侧滑返回也能生效了。当然,我所使用的绝大部分APP,都能有侧滑返回的效果,想必上面的内容大家都是掌握的~

获取崩溃日志

  1. 在手机中直接查看: 10.3之前: 通用 -> 关于本机 -> 诊断与用量; 10.3之后: 隐私 -> 分析
  2. xcode: window -> Oganizer -> crash
  3. iTunes: iTunes Connect(Manage Your Applications - View Details - Crash Reports)获取用户的crash日志
  4. 程序获取数据:
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
/// 创建一个类, 实现这个方法.
void uncaughtExceptionHandler(NSException *exception) {
// 异常的堆栈信息

NSArray *stackArray = [exception callStackSymbols];

// 出现异常的原因

NSString *reason = [exception reason];

// 异常名称

NSString *name = [exception name];

NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];

NSLog(@"%@", exceptionInfo);

NSMutableArray *tmpArr = [NSMutableArray arrayWithArray:stackArray];

[tmpArr insertObject:reason atIndex:0];

//保存到本地 -- 当然你可以在下次启动的时候,上传这个log

[exceptionInfo writeToFile:[NSString stringWithFormat:@"%@/Documents/error.log",NSHomeDirectory()] atomically:YES encoding:NSUTF8StringEncoding error:nil];
}

/// 然后在在appdelegate中注册这个获取异常的方法

参考帖子

  1. 使用第三方: cocoalamberjack
    配置时启用将log输出到日志: filelog
    可以从DDLogFileManager protocol下面的两个协议中获取Log文件rolling时的通知,便可以在此时将Log文件上传:

//Notifications from DDFileLogger

  • (void)didArchiveLogFile:(NSString *)logFilePath;
  • (void)didRollAndArchiveLogFile:(NSString *)logFilePath;

参考文章

打包发布版本

在打包发布版本的时候, 主要要保存号打包的文件, 因为当有crash信息上传到友盟时, 可以根据这个文件来定位错误. 不然就呵呵了…

UIKit中面向协议编程 #protocol#
不能在协议扩展里调用来自 Objective-C 的成员
不能使用 where 字句限定 struct 类型
不能定义多个以逗号分隔的 where 从句,类似于 if let 语句
不能在协议扩展内部存储动态变量
该规则同样适用于非泛型扩展
静态变量应该是允许的,但截至 Xcode 7.0 还会打印 “静态存储属性不支持泛型类型” 的错误。
与非泛型扩展不同,不能调用 super 来执行一个协议扩展 @ketzusaka 指出可以通过 (self as MyProtocol).method() 来调用
因为这个原因,协议扩展没有真正意义上的继承概念
不能在多个协议扩展中部署重名的成员方法
Swift 的运行时只会选择最后部署的协议,而忽略其他的
举个例子,如果你有两个协议扩展都实现了相同的方法,那么只有后部署的协议方法的会被实际调用,不能从其他扩展里执行该方法
不能扩展可选的协议方法
可选协议要求 @objc 标签,不能和协议扩展一起使用
不能在同一时刻声明一个协议和他的扩展
如果你真的想要声明实现放在一起,那就使用 extension protocol SomeProtocol {} 吧,因为声明实现都在同一位置,只提供协议实现就好,声明可以省略。

官方 protocol的使用文档

A/B 测试的基本概念

简单的理解可以参考这个 blog
简单的说:

所谓 A/B 测试,简单来说,就是为同一个目标制定两个方案(比如两个页面),让一部分用户使用 A 方案,另一部分用户使用 B 方案,记录下用户的使用情况,看哪个方案更符合设计目标。当然,在实际操作过程之中还有许多需要注意的细节。

更多的可以参考这篇

js 的对象

js 中 this 所指, 可以看成就是指明了一个域, 有时候在一个对象里定义的函数中使用了对象内的域(属性)的时候. 因为这些属性在字面被使用时, 还没有被弄出来, 需要使用this来让这个公式在运行时去this所指的地方去找这行属性.

1
2
3
4
5
6
7
8
var b = {
a: 8,
b: 9,
c: 10,
toString(){
return (this.a + this.b + this.c).toString();
}
}

测试 的技能

起因: 和别人合作开发小程序的过程中, 其对于改动了一些东西, 但是对于使用到的地方没有改全, 导致在用到的时候会出现 error.

如果有一个测试, 可以在改动什么东西的时候能够把之前的东西测试一遍, 保证需要有的能够痛, 保证不该有的有处理.
这即回归测试

回归测试是软件测试的一种,旨在检验软件原有功能在修改后是否保持完整。

回归测试过程

  1. 识别出软件中被修改的部分
  2. 从原基线测试用例库“T”中,排除所有不再适用的测试用例,确定对新版本依然有效的测试用例,创建新的基线测试用例库“TN”
  3. 依据一定的策略从TN中选择测试用例测试被修改的软件
  4. 如果必要,生成新的测试用例集“T1”,用于测试TN无法充分测试的软件部分
  5. 用T1执行修改后的软件
    第2和第3步测试验证修改是否破坏了现有的功能,第4和第5步测试验证修改工作本身。

推荐:

jemter, 对回归测试和自动化测试都蛮方便

终端

在终端想要用某些软件打开某些文件的时候, 需要
open -a [软件的路径] [打开的文件],
可是谁会每次都这样…
所以, 在使用的终端(bash/zsh)的配置文件中, 把这个文件名之前的路径做一个别名: alias [自定义命令]='open -a [软件的路径]', 然后source ~/.zshrc(bash的话把最后改成.bashrc)

终端软件收集

the_silver_searcher: 搜索文件内容, 使用ag '搜索的内容'
github 地址

bug

1
2
3
4
5
[!] Error installing SCRecorder
[!] /usr/bin/git clone https://github.com/rFlex/SCRecorder.git /var/folders/qp/zd_ttf394yxbkkb1s9tqvgth0000gn/T/d20171023-86130-ep28n4 --template= --single-branch --depth 1 --branch v2.7.0

Cloning into '/var/folders/qp/zd_ttf394yxbkkb1s9tqvgth0000gn/T/d20171023-86130-ep28n4'...
fatal: unable to access 'https://github.com/rFlex/SCRecorder.git/': Failed to connect to 127.0.0.1 port 1080: Connection refused

git 域名链接到本地 socks 端口时被 refused 了
stackoverflow 有了相关解答

查看得知, 在~/.gitconfig文件里, 将 git 的代理指向了1080端口, 但是现在的 socks5 端口在换了 ssr 后被设置成了1086, 应该是在安装 ssr 时, ssx 还在占用1080端口, 所以默认的1080端口并没有设置上, 将config 文件中的端口改成对应的就可以了

xcode 新工程搭建套路

  1. 用 xcode 创建工程
  2. 使用 pod init
  3. 将之前工程的结构目录来一遍

bug:

Command /Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc failed with exit code 1

在项目中有同名文件

gitignore

有时候添加了新的文件之后才添加的.gitignore 文件, 这样本来不应该 track 的文件也被 track 了, 这时候可以这么用:

1
2
git rm --cached *.xcuserstate
git rm --cached *.xcuserdata

Swift3 中 protocol 里定义的函数返回 Self 的使用

参考
其意思是需要你返回一个当前类, 且子类也需要实现这个函数的意思

所以你
不然声明这个实现类不会有之类: final class,
不然告诉之类必须实现自己的 init(): required init()

UItextView, UILabel 设置行间距

参考

还是得用上 AttributedString 来设置才行

修改Commit的作者信息或者其他的

有时候作者信息错了, 获取者忘了设置的时候, 可以用rebase来修改author的信息
参考帖子
首先rebase到没有错的那次, 将pick 修改为e, 然后保存退出, 这个rebase就会按照顺序进行pick操作. 或者Edit操作, Edit的时候会卡住rebase流程, 让你修改一些信息

1
2
3
4
git rebase -i HEAD~n
# 保存修改退出后会继续操作
# 这是修改Commit提交作者的信息
git commit --amend --author "baurine <2008.hbl@gmail.com>"

修改完后 rebase 会提示 success.
这时本地的 repo 就应该是想要的样子了.
但是远程上的还是没修改过的, 直接 push 的默认是有冲突的不允许的. 需要加上 --force 参数才行

1
git push --force

然后远程上的分支也是自己这样的了

CAlayer 中的 Mask 的应用

参考

Protocol in Swift3

需要某个delegate满足遵守多个协议的时候:
使用 &来链接多个协议:
(protocol1 & protocol2)? 可选类型时可以用括号将协议包起来

再Mac中读写NTFS的方法

参考比较全的知乎回答

使用mounty后文件出现问题的参考

修改/etc/fstab 文件的渐变方法

命令行方法

增强命令行

lldb 中文话插件等一些调试的插件

Swift 中的单例

目前比较好的形式:

1
2
3
4
class TheOneAndOnlyKraken {
static let sharedInstance = TheOneAndOnlyKraken()
private init() {} //This prevents others from using the default '()' initializer for this class.
}

参考有测试的帖子

优化代码结构:

可以在函数中设置进行对象设置的时候返回 self, 这样就能进行链式调用, 代码的可读性更好

躺了一道深坑

URLEncode, 再 apple 底层是符合规范的认为”+”是合法的 URL string, 而不会转码到%2B, 但是有些地方会喜欢转换, 所以出现了当 qurey string 中有”+”的时候, 会直接传送到后端. 后端的框架会进行 decoding, 这时候就会把”+”转成” “空格

有些网络框架会对此进行处理, 把”+”替换成%2B. 但是, 也有默认的拼接到 querey string 的时候会这样做, 而这默认是在.GET 方法中进行的, 其他的方法默认是将参数放到 body 里

只能自己将参数全部的+ 替换成%2B, 再拼到 url 后.

所以: 还是按照规范来开发, 该放哪就放哪, 可以少走很多坑

参考
自己生成一个字符集来 escaped

在 Alamofire 中写有转义的方法:
URL encode 的方法

1
2
3
4
5
6
7
8
9
public func escape(_ string: String) -> String {
let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="

var allowedCharacterSet = CharacterSet.urlQueryAllowed
allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")

return string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
}

拼接参数的方法

1
2
3
4
5
6
7
8
9
10
private func query(_ parameters: [String: Any]) -> String {
var components: [(String, String)] = []

for key in parameters.keys.sorted(by: <) {
let value = parameters[key]!
components += queryComponents(fromKey: key, value: value)
}

return components.map { "\($0)=\($1)" }.joined(separator: "&")
}

国际化

参考
如果项目中的文字等东西要适配多个语言版本的话, 做一下项目的语言国际化

  • 选中project->Info->Localizations,然后点击”+”,添加需要国际化/本地化的语言,如下图(默认需要勾选Use Base Internationalization)

  • iOS 应用中的文字国际化

app 构建

因为在 pod 中引入的库, 不算是一个 internal 的部分, 所以在引用其中代码的时候会需要 import 一下. 但是, 每个需要都 import 一下的话, 总是感觉并不友好.
这里想到一个比较好的…可以说是风格吧. 就是对经常使用的库中经常用到的方法做一个对接的封装, 主要封装一些满足自己项目开发的函数, 这样再其他地方调用就是在调自己的函数, 在这些函数声明的地方猜 import 这些库

文件上传 | 断点续传

普通的是通过 NSFileHandle 将文件添加到 data(内存读取) 中的, 这样有个问题就是当文件一大, 内存会爆.

NSInputStream

这个嘞有按照 offset 读取文件接口, (其实是 NSStream 的接口, 且带哟隐蔽性质)

Swift4 出来拉

需要兼容还没有适配 Swift4 的 lib, 需要在 xcode 中配置, 或者在 Podfile 中配置

关于使用Swift的一些坑

七大 Swift 陷阱以及如何避免它们

苹果的 Swift 语言已经过精心雕琢,用于优化软件的创建。与任何雄心勃勃的事业一样,难免会有一些粗糙的地方导致程序无法按照预期正常工作。为了避免出现这种不愉快的意外,让我们回顾一下这些陷阱,让您能够在自己的代码中避免它们:
仔细检查重写协议扩展的属性名称。
对于在协议扩展中定义的每个属性,都要在协议本身中声明它。
不要用可能需要动态分配的新属性来扩展导入的协议。
如果新的属性可能需要动态分配,避免使用一个限制条件来扩展协议。
避免将有副作用的表达式结果赋值给有可选链接的左侧。
避免在闭包中使用输入输出参数。
避免用输入输出参数进行柯里化,因为如果以后将它改为明确创建闭包,代码将会失败。

内存管理的注意事项, 以及内存检查工具的使用

UIKit Dynamics

苹果再 iOS7 退出了 UIKit Dynamics, 这个库可以方便的使用其中提供的物理特效, 如信息应用的消息列表滚动的那种弹簧效果, 就是由 UIKit Dynamics 提供的现成的弹簧效果

关于 Swift 和 OC 间动态特性的讨论

Realm 中讨论的一偏

无疑 OC 的运行时特性是很重要的, 但是在 Swift 中被大大削弱, 以至于在很多之前 OC 中惯于使用的技巧不能很好的在 Swift 中实现

文章里简单介绍了 runtime 对于 OC 类和对象的定义, 然后引申出在 OC 中我们常用的几个运行时的方法, 然后在 Swift 中, 这些替代的使用方式

注: 在WWDC 2017 session 212 中, 已经就这一特性进行补充, 斯巴拉西! 不仅不用对 Class 进行 @objc 声明, 也不用对属性进行 @dynamic 声明, 而且 KVO 和 KVC 对原生 Swift 类型有效, 而且还优化了 keypath 的定义方式, 使 keypath 的使用更有效率! ! 轰动你, 四把拉稀!!

关于在项目中不要使用 Swift 的推荐, 主要是说关于自己工具集的构建

嘛…我还是会使用 Swift 的, 毕竟我还是认为, 如果需求复杂, 或者哟很多东西在其他替代框架没有的时候, 原生还是一个更好的选择, 做好代码管理, 接口优化, 加上一些熟悉的 snips, 构建项目应该不会很难,

而且拒使用 RN 以来, 又很多问题的解决不如原生来的实在, 比如说性能的损耗, 体积, 等等一些方面.

但是通过这篇文章, 可以看到更多人对这个的思考, 也许是自己没有想过的, 也提到了很多框架, 是自己没有听说过的, 拓宽自己的视野, 阅读真好啊~

权限设置

1
2
3
UIApplicationOpenSettingsURLString
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
[[UIApplication sharedApplication]openURL:url];

用这些能跳转到应用自己的应用

自定义操作符

1
2
3
4
5
infix operator >>> {}

func >>>(lhs:Disposable, rhs:DisposeBag) {
rhs.addDisposable(lhs)
}

定义一个 disposebag , 方便在哥各个类中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protocol DisposeBagProvider {
var disposeBag: DisposeBag {get}
}

extension DisposeBagProvider where Self: AnyObject {

var disposeBag: DisposeBag {
get {
if let bag = objc_getAssociatedObject(self, &AssociatedKeys.DisposeBagKey) as? DisposeBag {
return bag
}

let bag = DisposeBag()
objc_setAssociatedObject(self, &AssociatedKeys.DisposeBagKey, bag, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

return bag
}
}
}

private struct AssociatedKeys {
static var DisposeBagKey = "dispose_bag_key"
}

自动证书签名

现在xcode 有了自动签名的整数, 不哟个再手动下载证书导入xcode什么的了, 但是如果Apple developer 上有什么更新时xcode不一定会及时更新这些整数信息, 这时候, 将xcode自动管理的整数删掉, 就会重新下载了
将这个目录下的所有文件删掉就可以了: ~/Library/MobileDevice/Provisioning Profiles

关于约束的调试

除了使用snap进行约束的话, 可以在console处看到会break 什么约束, 但是哪个页面的哪个View的约束, 还是只有地址. 这就很难看了….

通过添加symbolic breakpoint断点, 并设置为UIViewAlertForUnsatisfiableConstraints, 并添加po [[UIWindow keyWindow] _autolayoutTrace](OC) 或者expr -l objc++ -O -- [[UIWindow keyWindow] _autolayoutTrace](Swift), 就能在断点的时候打印页面结构, 这样就方便多了

注, 实际发现, 这个方法并不能百分百打印出来对应的信息

bug: The connection to service named com.apple.commcenter.coretelephony.xpc was invalidated

在iphone-xr模拟器中, xcode10 的情况下, 打开需要网络请求的地方, 出现这个报错

https://stackoverflow.com/a/53913380

schemerunenvironment中添加上这个变量: OS_ACTIVITY_MODE = disable