Apple File Conduit “2”
make
命令得到一个名为
dumpdecrypted.dylib
的动态库
1 | ssh root@ip |
如果觉得WIFI慢,可以使用usbmux进行转发
1 | ps -ef |
通过进程列表可以拿到
App可执行文件名称
以及App可执行文件绝对路径
1 | cycript -p App可执行文件名称 |
1 | [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0] |
1 | @import mjcript |
先进入沙盒目录下
1 | cd Documents目录 |
1 | DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib App可执行文件绝对路径 |
eg:
1 | DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /var/containers/Bundle/Application/D99C9215-D954-4067-BFD8-0F542C086F54/BitAutoPlusStore.app/BitAutoPlusStore |
问题
如果出现以下错误:
1 | could not load inserted library 'dumpdecrypted.dylib' into hardened process because no suitable image found |
在Mac环境下将dumpdecrypted.dylib进行签名
1 | security find-identity -v -p codesigning |
1 | codesign --force --verify --verbose --sign "Apple Development: Cheng Ren (A699T56LQP)" dumpdecrypted.dylib |
自动释放池存储在池本身排出时发送发布消息的对象。
如果你使用自动引用计数(ARC),你不能直接使用自动释放池。相反,你可以使用@autoreleasepool块。
1 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
可以写成:
1 | @autoreleasepool { |
在我们的ARC模式下,我们不可以直接使用自动释放池,可以用@autoreleasepool的block块代替。@autoreleasepool的block块比直接使用NSAutoreleasePool实例使用更有效。即使不是ARC模式,也可以使用。
Q:主线程创建临时变量,什么时候会销毁?
]]>YYDiskCache
是YYCache
组件中负责磁盘缓存的。涉及知识点:
由于YYDiskCache
是对YYKVStorage
的封装调用
3种存储方式
1 | typedef NS_ENUM(NSUInteger, YYKVStorageType) { |
本地文件路径
1 | /path/ |
YYKVStorage
会根据存储方式进行数据的增删改查;
YYKVStorage
不是线程安全的
1 | create table if not exists manifest ( |
日志模式采用wal方式;sqlite3.7版本之后支持;
1 | // 在合适的时机,将 `sqlite-wal` 文件合并到 `sqlite` 文件。因为`sqlite-wal`文件过大会影响性能 |
数据流context
1 | @interface YYKVStorageItem : NSObject |
1 |
读写的时候都用到了该锁
1 | - (void)setObject:(id<NSCoding>)object forKey:(NSString *)key |
YYDiskCache
也使用了LRU的算法,定时清理
YYMemoryCache
是YYCache
组件中负责内存缓存的。涉及知识点:
LRU
(Least recently used,最近最少使用)
核心思想:如果数据最近被访问过,那么将来被访问的几率也更高”。
通过上面的思想,整理思路如下
为什么使用链表而不使用顺序表?为什么使用双向链表?
- 链表的增加和删除的操作时间复杂度是O(1),而顺序表涉及到扩容的问题(开新内存,内存拷贝)
- 如果我们想删除最后一个元素,链表的操作是拿到最后一个元素的前一个元素,并将它的next设置成null,那怎么拿到最后一个元素的上一个元素呢?如果队列中已经有了元素,需要把它提到队头,那怎么拿到该元素的上一个元素呢?
我们可以维护一个链表的头节点和尾节点,但是没办法找到尾节点的上一个节点,所以需要双向链表
1 | @interface _YYLinkedMapNode : NSObject { |
1 | @interface _YYLinkedMap : NSObject { |
1 | @implementation YYMemoryCache { |
CFMutableDictionaryRef
的使用异步release
pthread_mutex
基类 Core.swift
1 | import Foundation |
调用方 main.swift
1 | 剑指_Offer_09_用两个栈实现队列.run() |
算法实现类
1 | class CQueue { |
在
Swift
中实现方法交换须满足条件:
NSObject
dynamic
关键字标记class FeiniaoSign
1 | class FeiniaoSign: |
.py main
1 | import os |
LLDB命令语法特性,可简写print
命令可简写成,p
,pr
,pri
,prin
。其他命令以此类推。
他们可以输出,可以调用方法,可以写表达式。
print
/p
:打印变量po
: 打印值;如果是对象,打印其description的信息语法
1 | p 变量名 |
示例
1 | (lldb) p object |
执行一个表达式,并将表达式返回的结果输出。
可以执行表达式来动态改变程序运行的轨迹。 假如我们在运行过程中,突然想把 self.view 颜色改成红色,看看效果。我们不必写下代码,重新run,只需暂停程序,用expression改变颜色,再刷新一下界面,就能看到效果。
语法
1 | expression 变量名 = 数值 |
示例
1 | expression $1 = 10 |
查看内存中读取一个地址的信息
语法
1 | x/数量-格式-字节大小 内存地址 |
示例
1 | (lldb) x 0x00000001010c7c70 |
- 格式
- x:16进制;
- f:浮点数;
- d:十进制
- 字节大小
- b - byte 1字节
- h - half word 2字节
- w - word 4字节
- giant word 8字节
修改内存中的值
语法
1 | memory write 内存地址 数值 |
示例
1 | memory write 0x0000010 10 |
语法
1 | breakpoint list |
参数
1 | # 查看某一组的信息 |
语法
1 | breakpoint set –name c函数名/oc方法 |
示例
1 | # C |
语法
1 | breakpoint set –file 文件名 –line 行号 |
语法
1 | breakpoint set --file 文件名 --selector 方法名 |
语法
1 | breakpoint delete 断点组id |
语法
1 | bt |
时间 | 版本 | 功能 |
---|---|---|
1991 | 0.9 | 只支持GET 请求方法获取文本数据(比如HTML文档),且不支持请求头、响应头等,无法向服务器传递太多信息 |
1996 | 1.0 | 支持POST 、HEAD 等请求方法,支持请求头、响应头等,支持更多种数据类型(不再局限于文本数据)浏览器的每次请求都需要与服务器建立-个TCP连接,请求处理完成后立即断开TCP连接 |
1997 | 1.1 | 最经典、使用最广泛的版本支持PUT 、DELETE 等请求方法采用持久连接 (Connection: keep-alive),多个请求可以共用同一个TCP连接 |
2015 | 2.0 | |
2018 | 3.0 |
口由万维网协会 (W3C)、互联网工程任务组 (ETF) 协调制定,最终发布了一系列的RFC
在RFC 5234中表明:ABNF用作internet中通信协议的定义语言
ABNF是最严谨的HTTP报文格式描述形式
方法名 | 用途 |
---|---|
GET | 常用于读取的操作,请求参数直接拼接在URL的后面(浏览器对URL是有长度限制的) |
POST | 常用于添加、修改、删除的操作,请求参数可以放到请求体中(没有大小限制) |
HEAD | 请求得到与GET请求相同的响应,但没有响应体;使用场景:下载大文件前,先获取其大小,再決定是否要下载。以此可以节约带宽资源 |
OPTIONS | 用于获取目的资源所支持的通信选项,比如服务器支持的请求方法 |
PUT | 用于对已存在的资源进行整体覆盖 |
PATCH | 用于对资源进行部分修改(资源不存在,会创建新的资源) |
DELETE | 用于删除指定的资源 |
TRACE | 请求服务器回显其收到的请求信息,主要用于HTTP请求的测试或诊断 |
CONNECT | 可以开启一个客户端与所请求资源之间的双向沟通的通道,它可以用来创建隧道 (tunnel)可以用来访问采用了 SSL (HTTPS) 协议的站点 |
字段 | 说明 | 示例 |
---|---|---|
User-Agent | 浏览器的身份标识字符串 | User-Agent: Mozilla/5.0 (X11;Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0 |
Host | 服务器的域名、端口号 | Host: localhost:80 |
Date | 发送该消息的日期和时间 | Date: Tue, 15 Nov 1994 08:12:31 GMT |
Referer | 表示浏览器所访问的前一个页面正是前一个页面的某个链接将浏览器带到了当前这个页面 | Referer: https://www.baidu.com |
Content-Type | 请求体的类型 | Content-Type: multipart/form-data |
Content-Length | 请求体的长度(字节为单位) | Content-Length: 232 |
Accept | 能够接受的响应内容类型(Content-Types) | Accept: text/plain |
Accept-Charset | 能够接受的符集 | Accept-Charset: GB2312,utf-8;q=0.7,*; q=0.7 |
Accept-Encoding | 能够接受的编码方式列表 | Accept-Encoding: gzip, deflate |
Accept-Language | 能够接受的响应内容的自然语言列表 | Accept-Language: en-US |
Range | 仅请求某个实体的一部分。字节偏移以0开始 | Range: bytes=500-999 |
Origin | 发起一个针对跨域资源共享的请求 | Origin: https://www.baidu.com |
Cookie | 之前由服务器通过set-Cookie发送的cookie | Cookie: Version=1; Skin=new; |
Connection | 该浏览器根要优先伅用的连接类型 | Connection: keep-alive |
Cache-Control | 用来指定在这次的请求/响应链中的所有缓存机制都必须遵守的指令 | Cache-Control: no-cache |
q值越大,优先级越高。默认1.0
字段 | 说明 | 示例 |
---|---|---|
Content-Type | 响应体的类型 | Content-Type: text/html; charset=utf-8 |
Content-Encoding | 内容所使用的编码类型 | Content-Encoding: gzip |
Content-Length | 响应体的长度(字节为单位) | Content-Length: 348 |
Content-Disposition | 一个可以让客户端下载文件并建议文件名的头部 | Content-Disposition: attachment;filename=”fname.ext” |
Accept-Ranges | 服务器支持哪些种类的部分内容范围 | Accept-Ranges: bytes |
Content-Range | 这条部分消息是属于完整消息的哪部分 | Content-Range: bytes 21010-47021/47022 |
Access-Control-Allow-Origin | 指定哪些网站可参与到跨来源资源共享过程中 | Access-Control-Allow-Origin:* |
Location | 用来进行重定向,或者在创建了某个新资源时使用 | Location: http://www.w3.org |
Set-Cookie | 返回一个Cookie让客户端去保存 | Set-Cookie: UserID=JohnDoe; Max Age=3600; Version=1 |
Connection | 针对该连接所预期的选项 | Connection: close |
Cache-Control | 向从服务器直到客户端在内的所有缓存机制告知,它们是否可以缓存这个对象。单位为秒 | Cache-Control: max-age=3600 |
数据流:已建立的连接内的双向字节流,可以承载一条或多条消息
所有通信都在一个TCP连接上完成,此连接可以承载任意数量的双向数据流
消息:与逻辑HTTP请求或响应消息对应,由一系列帧组成
帧:HTTP/2
通信的最小单位,每个帧都包含帧头 (会标识出当前帧所属的数据流)
来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装
HTTP/2.0
采用二进制格式传输数据,而非HTTP/1.1
的文本传输
二进制格式在协议的解析和优化扩展上带来更多的优势和可能
客户端和服务器可以将 HTTP消息分解为互不依赖的帧,然后交错发送,最后再在另一端把它们重新组装起来
不必再为绕过 HTTP/1.1限制而做很多工作
比如image sprites、合并CSS\JS. 内嵌CSS\JS\Bas64图片,城名分片等
HTTP/2
标准允许每个数据流都有一个关联的权重和依赖关系
服务器可以使用此信意通过控制CPU,内存和其他资源的分配设定数据流处理的优先级,在资源数据可用之后,确保将高优先级响应以最优方式传输至客户端。
HTTP/2
使用HPACK
压缩请求头和响应头,可以极大减少头部开销,进而提高性能。
服务器可以对一个客户端请求发送多个响应,除了对最初请求的响应外,服务器还可以向客户端推送额外资源,而无需客户端额外明确地请求。
HTTP/2
是基于TCP
协议的, 传输过程相当于队列,串行发送。如果第一个数据包丢失,为了保证有序性,后续数据要等到队头数据重传后才可继续。
由于HTTP/2
的连接是基于TCP,并且前身SPDY的原因又做TLS/SSL安全套接层,所以握手时间太长。
TCP基于4要素 (源1P、源端口、目标1P、目标端口),切换网络时至少会有一个要素发生变化,导致连接发生变化。当连接发生变化时,如果还使用原来的TCP连接,则会导致连接失败,就得等原来的连接超时后重新建立连接,所以我们有时候发现切换到一个新网络时,即使新网络状況良好,但内容还是需要加载很久,如果实现得好,当检测到网络变化时立刻建立新的TCP连接,即使这样,建立新的连接还是需要几百毫秒的时间。
QUIC的连接不受4要素的影响,当4要素发生变化时,原连接依然维持。QUIC连接不以4要素作为标识,而是使用一组Connection 1D(连接D)来标识一个连接。即使IP或者端口发生变化,只要Connection 1D没有变化,那么连接依然可以维持
当设备连接到Wi-Fi时,将进行中的下载从蜂窝网络连接转移到更快速的Wi-Fi连接
当Wi-Fi连接不再可用时,将连接转移到蜂窝网络连接
Wireshark
使用捕获过滤器表达式作用在wireshark开始捕获数据包之前,只捕获符合条件的数据包
协议列表 |
---|
tcp |
udp |
arp |
tcp |
ip |
ip6 |
逻辑关键字 |
---|
or |
and |
取反关键字 |
---|
no |
协议是tcp
并且端口号是443
1 | tcp port 443 |
协议是tcp
并且端口号不是443
1 | tcp and not port 443 |
域名是baidu.com
1 | host baidu.com |
网卡的mac地址
1 | ether host 00:00:5e:00:53:10 |
显示过滤器表达式作用在在wireshark捕获数据包之后,从已捕获的所有数据包中显示出符合条件的数据包,隐藏不符合条件的数据包。
逻辑关键字 |
---|
|| |
&& |
取反关键字 |
---|
! |
106.152.175.199
并且端口号是56147
1 | ip.addr == 106.152.175.199 && tcp.port == 56147 |
106.152.175.199
并且端口号不是56147
1 | ip.addr == 106.152.175.199 && tcp.port != 56147 |
106.152.175.199
并且源IP地址是16.52.175.199
1 | ip.dst == 106.52.175.199 && ip.src == 16.52.175.199 |
1 | http.request.uri contains ".js" |
1 | http contains "username" |
ssl-key-log-file=/tmp/.ssl-key.log
1 | /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --user-data-dir=/tmp/chrome --ssl-key-log-file=/tmp/.ssl-key.log |
首选项
->protocol
->TLS
/tmp/.ssl-key.log
进程是资源分配的最小单位,线程是CPU调度的最小单位
并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
串行:一个程序执行没结束前,另一个不能开始,要一个一个的执行。
并行:一个程序执行没结束,另一个已经开始。
同步:当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续执行下去。
异步:当程序1调用程序2时,程序1径自继续自己的下一个动作,不受程序2的的影响。
含有n个数字的圆圈中每次删除第m个元素,求最后剩下的数字
1 |
@autoreleasepool{} 本质上是一个结构体:
autoreleasepool会被转换成__AtAutoreleasePool
__AtAutoreleasePool 里面有两个函数objc_autoreleasePoolPush(),objc_autoreleasePoolPop().,其实一些列下来之后实际上调用得是AutoreleasePoolPage类中得push 和 pop两个类方法
push就是压栈操作,
pop就是出栈操作于此同时对其对象发送release消息进行释放
initialize
这个方法是第一次给某给类发送消息的时候调用,并且只会调用一次。 如果某一个类一直没有被用到,此方法也不会执行。initialize
先初始化父类, 在初始化子类,子类的initialize 会覆盖父类的方法。
分类中实现了initialize
会覆盖本来的initialize方法,如果多个分类都执行了initialize ,那么只是执行最后编译的那个。
load当程序被加载的时候就会调用, 其加载顺序为, 如果子类实现类load 先执行父类 -> 在执行子类,而分类的在最后执行。
如果子类不实现load,父类的load就不会被执行。
load是线程安全的,其内部使用了锁,所以我们应该避免在load方法中线程阻塞。
load在分类中,重写了load方法, 不会影响其主类的方法。即不会覆盖本类的load方法
当有多个类的时候,每个类的load的执行顺序和编译顺序一致。
pod install
的执行流程今年总结主要关键词:成长,管理
今年的工作没有以往的纯粹,以前只需要对相关任务进行拆解开发并提测,今年所做的是挑战性有大幅增强,以前大多面对的事,今年大多面对的是人。因为做项目不是自己可以独立完成的,需要大家通力配合才能有好的结果。所以在不影响现有平台的工作前提下,我大多精力做的就是沟通协调的工作。虽说项目结果不是特别好,但是收获一帮可以为之奋斗的兄弟。👍🏻
Swift
,Flutter
等技术进行落地;2022年TODO:
NSDictionary
和NSMutableDictionary
。下面介绍个特殊的。NSMapTable
是更广泛意义的NSMutableDictionary
,区别于NSMutableDictionary
,NSMapTable
有如下特性:
使用方法
1 | NSObject *object = [[NSObject alloc] init]; |
NSMapTableOptions介绍(源码中是使用静态常量做关联)
1 | enum { |
在objc源码中,找到了关于NSMapTable的类似实现。可以参考下
Hash冲突解决方案:开放地址法
NXMapTable存储结构(精简过)
1 | typedef struct _NXMapTable { |
NXMapTablePrototype存储结构(精简过)
1 | typedef struct _NXMapTablePrototype { |
MapPair存储结构
1 | typedef struct _MapPair { |
NXMapTable的
构造器NXCreateMapTable
和NXCreateMapTableFromZone
1 | NXMapTable *NXCreateMapTable(NXMapTablePrototype prototype, unsigned capacity) { |
取数据
1 | void *NXMapGet(NXMapTable *table, const void *key) { |
插入数据
1 | void *NXMapInsert(NXMapTable *table, const void *key, const void *value) { |
删除数据
1 | void *NXMapRemove(NXMapTable *table, const void *key) { |
纯Swift
类来说,没有动态特性。方法和属性不加任何修饰符的情况下,这个时候其实已经不具备我们所谓的Runtime
特性了,这和C++方法调度(V-Table调度) 是不谋而合的。纯Swift
类,方法和属性添加@objc
标识的情况下,当前我们可以通过Runtime API
拿到,但是在我们的OC中是没法进行调度的。NSObject
的类,如果我们想要动态的获取当前的属性和方法,必须在其声明前添加@objc
关键字。否则也是没有办法通过Runtime
API 获取的。@objc
和dynamic
关键字1 | class Teacher: NSObject { |
Macbook Pro
(Retina, 15-inch, Mid 2015)60G
**1 | brew install cmake ninja |
macOS,Xcode,swift最好用对应的版本,以防等待半个小时失败后骂骂咧咧
创建文件夹,并进入该文件夹
1 | mkdir swift-source && cd swift-source |
开始克隆代码
1 | git clone --branch swift-5.3.2-RELEASE git@github.com:apple/swift.git |
5.4版本克隆下来842M
1 | # 如果在这个文件夹请忽略 |
1 | ./swift/utils/update-checkout --tag swift-5.4-RELEASE --clone |
执行结果
- 如果出现Git错误请执行如下命令
1
git config --global http.postBuffer 524288000 && git config --global http.sslVerify "false"
- 文件大小大概3.67G左右(包含开始克隆的Swift源码)
编译的过程中可以使用ninja
,也可以使用Xcode
。由于Xcode
需要和特定版本绑定,并且Xcode
编译之后的支持性不是特别好。推荐使用njnja
来作为编译工具。
使用ninja
编译(推荐)
1 | ./swift/utils/build-script -r --debug-swift-stdlib --lldb |
使用Xcode
编译
1 | ./swift/utils/build-script -x -R --debug-swift |
输出没有Error代表编译完成
]]>编译是挺耗时并且烧设备。我的Mac温度到了95℃
现在市面上有很多的场景需要Key-Value对存储方式。比如iOS的键值编码,后端的缓存(Redis和Memcached),和一些KVStore配置等都需要这种结构存储。为什么不用数组,链表等其他方式呢?因为它“快”,为什么快呢?下面就来探索一下。
根据关键字(Key Value)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称作散列函数,存放记录的数组成为散列表。
当两个Key值通过哈希函数映射的索引为同一位置时,则产生哈希冲突
解决方法:
填入表中的元素个数 / 哈希表的长度
注:一般3/4的时候需要对哈希表扩容
代码检测
1 | # 生成用于后续代码检测的解析文件 |
环境配置
1 | cd MCShow |
拷贝依赖
1 | startInfo="开始引入依赖库" |
分发
1 | offical="Fir.im-Official" |
通知
1 | cd MCShow |
指针类型
。C | Swift |
---|---|
const Pointee * | UnsafePointer<Pointee> |
Pointee * | UnsafeMutablePointer<Pointee> |
const void * | UnsafeRawPointer |
void * | UnsafeMutableRawPointer |
1 | var age = 10 |
1 | var age = 10 |
不存在访问冲突
1 | func increment(_ num: inout Int) -> Int { |
存在访问冲突
1 | var step = 1 |
报错
如何解决
1 | var step = 1 |
tulpe是一块结构体内存内存,所以修改内部的数据也会报错
如果下面的条件可以满足,就说明重叠访问结构体的属性是安全的
1 | func test() { |
1 | swiftc -emit-sil main.swift >> ./main.sil |
1 | xcrun swift-demangle s4main7TeacherC3ageSivg |
功能点:
捕捉会话:AVCaptureSession
捕捉设备:AVCaptureDevice
捕捉设备输入:AVCaptureDeviceInput
捕捉设备输入:AVCaptureOutput(抽象类)
AVCaptureStillImageOutput
AVCaptureMovieFileOutput
AVCaptureAudioDataOutput
AVCaptureVideoDataOutput
捕捉连接:AVCaptureConnection
捕捉预览:AVCaptureVideoPreviewLayer
代表当前对象
代表当前类型
Self一般作为返回值类型,限定返回值和方法调用者必须是同一类型(也可以作为参数类型)
1 | protocol Runnable { |
输出结果
1 | 10 |
静态派发
是三种派发方式中最快的。CPU
直接拿到函数地址并进行调用。编译器优化时,也常常将函数进行内联,将其转换为静态派发方式,提升执行速度。C++
默认使用静态派发;在Swift
中给函数加上final
关键字,也会变成静态派发
。
优点:
使用最少的指令集,办最快的事情。
缺点:
静态派发最大的弊病就是没有动态性,不支持继承。
编译型语言中最常见的派发方式,既保证了动态性也兼顾了执行效率。
函数所在的类会维护一个“函数表”(虚函数表),存取了每个函数实现的指针。
每个类的 V-Table 在编译时就会被构建,所以与静态派发相比多出了两个读取的工作:
优点:
缺点:
Swift
类扩展里面的函数无法动态加入该类的函数表中,只能使用静态派发的方式。消息机制是调用函数最动态的方式。由于Swfit
使用的依旧是Objective-C
的运行时系统,消息派发其实也就是Objective-C
的Message Passing(消息传递)
。由于消息传递大家看的文章很多了,这里不做过多赘述。
1 | id returnValue = [obj messageName:param]; |
优点:
缺点:
所幸的是
objc_msgSend
会将匹配的结果缓存到一个映射表中,每个类都有这样一块缓存。若是之后发送相同的消息,执行速率会很快。
Swift的派发机制受到4个因素的影响:
类型\位置 | 初始声明 | 扩展 |
---|---|---|
值类型 | 静态派发 | 静态派发 |
协议 | 函数表派发 | 静态派发 |
类 | 函数表派发 | 静态派发 |
NSObject子类 | 函数表派发 | 静态派发 |
声明在 协议 或者 类 中的函数是使用函数表派发的
声明在 扩展 中的函数则是静态派发
给函数添加关键字的修饰也会改变其派发方式。
添加了final
关键字的函数无法被重写,使用静态派发,不会在vtable
中出现,且对objc
运行时不可见。
函数均可添加dynamic
关键字,为非objc类和值类型的函数赋予动态性,但派发方式还是函数表派发。
该关键字可以将Swift函数暴露给Objc运行时,但并不会改变其派发方式,依旧是函数表派发。
这两个关键字组合使用,将采用消息派发
的方式
告诉编译器将此函数静态派发,但将其转换成SIL代码后,依旧是函数表派发
。
static 关键字会将函数变为静态派发,不会在 vtable 中出现。
类型 | 静态派发 | 函数表派发 | 消息派发 |
---|---|---|---|
值类型 | 所有方法 | - | - |
协议 | extension | 主体创建 | - |
类 | extension/final/static | 主体创建 | @objc + dynamic |
NSObject子类 | extension/final/static | 主体创建 | @objc + dynamic |
除此之外,编译器可能将某些方法优化为静态派发。例如,私有函数。
Swift
会尽可能的去优化函数派发方式。当一个类声明了一个私有函数时,该函数很可能会被优化为静态派发。
这也就是为什么在Swift
中使用target-action
模式时,私有selector
会报错的原因(Objective-C
无法获取#selector
指定的函数)。
另一个需要注意的是,NSObject子类
中的属性
,如果没有使用dynamic
修饰的话,这个优化会默认让KVO
失效。因为,这个属性的getter
和setter
会被优化为静态派发。虽然,代码可以通过编译,不过动态生成的KVO
函数就不会被触发。
当判断某段程序在某种状态肯定会出问题后,我们可以增加断言处理,提供很好的提示
增加断言之后
ps:Xcode大多数情况是不会给出好的错误提示的,需要我们去增加断言处理
在build setting
增加Other Swift Flags
修改断言的默认行为-assert-config Release
:强制关闭断言-assert-config Debug
:强制开启断言
CustomStringConvertible
和CustomDebugStringConvertible
1 | class Person { |
调用
1 | let p = Person() |
打印结果
1 | person age: 10 |
]]>当处于Release模式的时候,debugPrint也仍然会输出。目前看不出区别
当在控制台po的时候,调用的是
CustomDebugStringConvertible
的debugDescription
方法
今年总结主要关键词:自我驱动
今年处在各种救火的路上,也让我学到了很多知识。
今年年初的时候,心里有一股奋斗的力量,到了年底已经感受不到这股力量了,不知道是项目失败或者是救火还是疫情导致的,所有的计划都赶不上变化,变得快,跟不上了就懒得跟了。身边好多同事都有这种感觉。
新的一年2021。给自己的要求:适应变化,及时调整
今年的收获比较务实,有底层知识的探索,有新语言,新业务的实践,更有跨平台方案的落地。收获不大,但是一步一个脚印。
2021年TODO:
Flutter
开发Swift
语言iOS底层
知识算法
和数据结构
邓白氏邮件163邮箱可能收不到,建议用QQ邮箱统一管理账户(邓白氏/AppleId)
00852 25161111
(香港)1 | > 对面说英文,你就说`你好`,她会说`你好`,等待帮你转接; |
1 | Dear Customer, |
1 | D-U-N-S Number Request Form Free20161007.doc |
1. 注册准备物料
用香港IP进行注册
注册完成后,苹果会通过邮件回复,索要更多信息
邓白氏可能没有那么及时同步,尽量等1-2天再注册。
2. 邮件准备物料
回复邮件后,会让打电话联系他。但是咱们注册的是香港的,打电话是说英文和粤语。那我们找一个国内的账号进行联系就好。
3. 支付准备
]]>支付费用
HK$788
支付后24h后账号可用
操作环境
操作系统:MacOS 11
开发工具:Xcode12
CPU:i7 2.2GHz 4核8线程
内存:16GB
业务驱动技术提升
1 | defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES |
该插件会罗列单个编译单元的时长
首次启动该工程,根据配置指导走完后,需重新运行。
以下配置都是在工程对应Target
的Build Settings
中操作
在编译项目时,将同属于一个Module
(可以理解为一个 Target、一个 Package)的所有源代码都串起来,进行整体的一个分析与优化,区别于 Single-File Optimizatin
(单文件优化,以下简称SFO),WMO
可以更好的统筹全局,去inline
函数调用、排除死函数(即写了却从不调用的函数)等
在User-Define
中增加SWIFT_WHOLE_MODULE_OPTIMIZATION
选项卡,值为Yes
在
Release
模式中,建议打开此选项。
将Debug Information Format
选项中,
Debug
模式的值DWARF with dSYM
改为DWARF
;Release
模式的值是DWARF with dSYM
,因为一般工程都会集成Bugly,而Bugly问题分析是需要.dSYM
文件的,如果不生成,线上崩溃是没有办法定位信息的。DWARF意思是矮小的,不作数的。
将Build Active Architecture Only
选项里的Debug
模式的值No
改为Yes
Release
模式的值必须是No
;虽说现在苹果全线都Arm架构了,但是仍然有arm64,armv7,armv7s等区分。
在设置编译优化之后,Xcode断点和调试信息会不正常,所以一般静态库或者其他Target可以这样设置。
1 | defaults write com.apple.Xcode PBXNumberOfParallelBuildSubtasks 4 |
使用虚拟磁盘。编译过程中需要大量的磁盘 IO,这主要发生在Derived Data
目录下,因此如果内存足够,可以考虑划出4G
左右的内存,建一个虚拟磁盘,这样将会把磁盘IO
优化为内存IO
,从而提高速度。
由于打包机器每次都会重编译,因此不需要担心重启机器后缓存丢失的问题。
第一步:启动Xcode,依次点击File->New->Project
。
第二步:双击Framework & Library
栏目下的Cocoa Touch Framework
项。
第三步:在Choose options for your new project
窗口填写Project的相关信息。我们这里的Product Name
为ChannelSDK
。然后点击Next
按钮。
第四步:选择 ChannelSDK
Project的保存位置,并单击Create
按钮。此时Xcode会打开当前Project窗口。
第一步:依次点击File->New->Workspace
。
第二步:将Workspace的名字填写为 ChannelSDK
,选择存储位置为第一步创建的 ChannelSDK Project
根目录下。
第三步:点击保存,此时Xcode会打开ChannelSDK Workspace
窗口
第四步:在当前的 ChannelSDK Workspace
窗口,依次点击File->Add Files to “ChannelSDK”
。选择ChannelSDK.xcodeproj
文件,将之前创建好的Project添加到当前工作空间中。最后点击Add
按钮
第五步:此时,ChannelSDK Workspace
的目录结构如下图。如果仅仅只有一个工程文件,那么关掉Xcode,重新打开ChannelSDK Workspace
。ChannelSDK Workspace
文件名是ChannelSDK.xcworkspace
第一步:启动Xcode,依次点击File->New->Project
。双击App
图标。
第二步:Product Name
为Example
。然后点击Next
按钮。
第三步:选择Example Project
的保存位置为ChannelSDK Project
同级目录,并单击Create
按钮
第四步:出现下面窗口后,关闭该窗口
第五步:返回到ChannelSDK Workspace
窗口,依次点击File->Add Files to “ChannelSDK”
,选择Example.xcodeproj文件。
第六步:点击Add
按钮,将Example Project
添加到ChannelSDK Workspace
中。
第一步:在ChannelSDK Workspace
窗口中,单击Example Target
, 依次点击General->Embedded Binaries
,单击添加(+)按钮。
第二步:找到ChannelSDK.framework,单击Add
按钮。
第一步:选中ChannelSDK Project
中的ChannelSDK Group
,依次点击File->New->File
。选择Cocoa Touch Class
,点击Next
按钮
第二步:填入Class为ChannelSDK
,然后单击Next
按钮,选择默认的保存位置
第三步:在ChannelSDK.swift中写个方法
1 | public class func version() -> String { |
第四步:在AppDelegate中调用
1 | print("\(ChannelSDK.version())") |
跟Swift基本相同。但是在开发的时候,要注意
import的方式要用
<>
而不是""
,
在ChannelSDK.h中,声明公开的头文件
ChannelSDKVersionString[]
值无参考意义,ChannelSDKVersionNumber
可以设置Android跟iOS开发上很大的区别
iOS | Android | |
---|---|---|
审核要求 | 需兼容很多新特性 | 对新特性几乎无要求 |
系统更新 | 升级体验新系统用户量大 | 不升级都卡,升级更卡;设备碎片化严重 |
开发层面 | 使用新系统特性,工程最小兼容的版本低于新特性兼容版本那么Xcode会报错提示不兼容不会造成运行时崩溃 | 使用新系统特性工程最小兼容的版本低于新特性兼容版本那么AS会报错提示不兼容但是有全局开关可关闭这一类型提示会造成运行时崩溃 |
由于在审核层面的硬性要求,导致iOS开发者的Xcode经常是最新的大版本;而Android则不需要升级。
Google为了让老版本系统用上新版本的特性,就提供了很多的版本支持库,用于兼容。
由于兼容库太多,所以Google提供了AndroidX兼容库统一管理,去避免兼容库太多,每个版本的小版本太多导致的开发低效,重复依赖等问题
抽象类
以数据作为Key。如:数字,字符
以Object对象作为Key。如:Text()
可以保证Key的唯一性
一旦使用它,就不存在Element复用了
抽象类
首先怀疑是配置方式、编译缓存等导致的问题。针对这些猜测做了以下测试:
结论:系统缓存问题
完全抛弃系统缓存的启动图,由自己生成的启动图替换。
用户安装应用后,系统会自动生成启动图并缓存至沙盒目录,接着用户启动应用时,我们通过代码将沙盒目录下缓存的启动图文件全部替换为我们通过代码生成的启动图。
1 | NSString *launchScreenName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"]; |
通过测试,该方法不会报权限问题
1 | - (BOOL)moveItemAtPath:(NSString *)srcPath |
graph TDA(应用启动)-->B[监听被Kill通知]B-->C[应用Kill回调]C-->D[获取当前App版本号]D-->E{指定目录下以版本号命名的启动图是否存在}E--YES-->F[读取该文件]E--NO-->G[清空该目录文件]subgraph 此步骤耗时 G-->H[生成自定义启动图] H-->I[以版本号为名写入指定目录]endI-->J[计算默认启动图MD5]F-->JJ-->K{遍历系统缓存启动图目录}K--遍历中-->L{iPhone Or iPad}L--iPad-->M{对比图片宽高是否相同}M--YES-->X{对比图片md5值是否相同}X-->N[替换系统缓存启动图]K--遍历完成-->S(应用杀死)N--继续遍历-->KL--iPhone-->XX--NO-->K
缓存路径:
iOS13.0 及以上:
1 | Library/SplashBoard/Snapshots/${PRODUCT_BUNDLE_IDENTIFIER} - {DEFAULT GROUP}; |
iOS13.0 以下:
1 | Library/Caches/Snapshots/${PRODUCT_BUNDLE_IDENTIFIER}; |
图片格式:
iOS10.0 及以上:KTX;
iOS10.0 以下:PNG。
]]>系统缓存图目录读写权限:
iOS10.0 及以上:有权限;
iOS10.0 以下:无权限。
如果设备开启了本地的服务,会弹出允许本地网络的权限提示框。
在Info.plist中增加隐私配置
1 | Privacy - Local Network Usage Description |
如果不加该配置,提审会被拒。
1 | building for iOS Simulator, but linking in object file built for iOS, xxxx for architecture arm64 |
原因:
Xcode12
以前,我们通过Valid Architectures
选项来配置支持的架构。Xcode12
中, Valid Architectures
这一选项被删掉了,改变成了VALID_ARCHS
。在
Xcode11
中,x86_64
的架构支持是默认支持的,就算不写也支持。但Xcode12
的时候,苹果将要推出了ARM架构的Mac,看来全面采用ARM架构已经成为苹果的趋势了,这使得Xcode其实就没必要再对x86_64
架构默认支持。故需要手动添加上才能跑模拟器。
解决方案:
在VALID_ARCHS
选项上增加x86_64
的架构配置
如果Cocoapods管理的组件,需要支持模拟器
需要手动设置User-Defined
的VALID_ARCHS
打开某款App,频繁提示** Pasted from ***
苹果强化了对系统剪切板内容访问的提示,适度减少访问频次吧。不过现在是大家都玩口令的年代,这很难控制住…
iOS14中,苹果对UIPageControl
控件进行了调整,定制性更强了。可以通过官方的API设置图片了,而且也有几个样式供我们选择。
注意点
1 | [_pageControl setValue:[UIImage imageNamed:@"xxx"] forKeyPath:@"pageImage"]; |
上面代码在iOS14中,会出现崩溃。跟去年的UITextFlied
设置_placeholderLabel.textColor.
一样抓狂的感觉。
如果封装SDK,尽可能的不要给系统UI控件通过KVC赋值和访问私有属性。
自定义子视图,曾经可以添加在self
、self.contentView
上均可。 以后需要注意了,必须按照苹果的规范。
1 | // Custom subviews should be added to the content view. |
影响: 不添加在 contentView 上的子视图,存在点击事件无法响应以及视图被系统默认高亮效果覆盖的问题。
iOS14
之前,IDFA的开关是统一管理的,开启关闭和还原都是针对所有App,但是好歹苹果还是默认开启的,如果要关闭,需要用户自己去设置页面关闭。WIFI地址是所有WIFI共用一个设备地址IDFA
,Apple不再默认开启,用户可以在设置->隐私->Tracking
或者App的权限管理页面来设置。WIFI地址目前是每个网络都配一个虚拟地址归因真的是越来越难了
如何应对
Privacy - Tracking Usage Description
。AppTrackingTransparency
框架,调用ATTrackingManager.requestTrackingAuthorization
方法来请求权限。ASIdentifierManager.shared().advertisingIdentifier.uuidString
来获取IDFA。在使用时会进行权限访问的申请,如果拒绝,将是一串无意义的0。
影响
可以尝试SKAdNetwork解决,统计更准确。但是应对场景过于单一。
MyClass
+class_rw_t
(dirty memory) + class_ro_t
(clean memery)三个数据结构存储信息class_rw_t
拆解成class_rw_t
+class_rw_ext_t
官方解释:通过统计发现,开发中大多数的类是没有通过Runtime机制为其添加属性,方法等信息的。所以就没必要为每个类都开辟这个一段空间,造成不必要的浪费。。
dirty memory
脏内存。指可读可写内存
clean memory
干净内存。指只读内存
影响
如果App内部没有通过系统API去访问脏内存的数据(属性或方法),那么就会产生错误,甚至崩溃。
解决
官方建议:访问内存信息,请一定要使用官方提供的API,这样在以后不断的迭代优化过程中,才不会出现兼容问题。
在iOS14中,Apple优化了照片的隐私权限,可以让用户选择为该App共享哪些图片,而不是以前的允许访问(所有)图片。
影响
AssetLibrary
, 请使用PhotoKit
UIImagePickerController
,请使用PHPickerViewController
特性
照片App
完全相似官方说明
Also please don’t prompt for photo library access before showing the picker and don’t require the user to grant you access before showing the picker. There’s no need to do any of this. And it doesn’t help with the users trust into your app.If your app leverages PhotoKit to access the photos library please reconsider if it really needs to have access to the library or if you can use PHPicker instead.We really hope you like the new picker and API. We’re looking forward to you adopting it in your apps. Thank you.
另外,在显示选择器之前,请不要提示对图库的访问权限,也不要要求用户在显示选择器之前授予您访问权限。 无需执行任何操作。 如果您的应用程序利用PhotoKit访问照片库,请重新考虑它是否真的需要访问该库或是否可以使用PHPicker,我们真的希望您喜欢该应用程序。 新的选择器和API。 我们期待您在应用中采用它。 谢谢。
小组件功能
具体开发实现可以参考苹果的官方教程
为您介绍WidgetKit
SwiftUI新功能
为小组件添加配置和智能
为小组件构建SwiftUI视图
小组件仅支持SwiftUI开发
苹果对App Clip的使用场景非常明确,系统对调起方式做了严格的过滤,支持的发起入口有如下几种:
App Clip
及数据
依赖主APP
虽然对于用户来说不需要下载主App,但开发者必须使App Clip跟随主App一同提交审核,App Clip并不能够独立开发并提审。(这与Apple设计之初的理念是一致的,目的是为了快速体验功能,而不是替代App)
独立的Target
在开发上,也是完全Native的实现,类似于一个新增的Extension target
,例如Keyboard Extension
、iMessage Extension
等。
App Clip
不仅支持SwiftUI
,也支持UIKit
,包括很多人还在坚守的Objective-C
,完全都没有问题,并不存在上手难度。
数据共享
由于Target依赖于主App,所以Target间的资源共享都是完全OK的,只需要在资源归属上勾选上App Clip
就可以了;同时,与Extension一致,App Clip
可以通过App Groups
来与主App共享数据。
引导转化
Apple建议开发者可以在App Clip
的视图中嵌入SKOverlay
,当用户在App Clip
中完成相关任务后展示SKOverlay
,这样可以较好的引导用户
比如可以将其放置在用户的付款确认界面之后。
新特性SKOverlay
支持一对多
同时,主工程支持多个App Clip
Target,目前并不清楚Apple对于数量的限制是多少,但是估计能够满足大部分App主要功能的拆分,以某团为例,可以存在多个App Clip
:单车、外卖、酒店住宿、打车等等。
支持不同参数
只需要提供不同参数,就可以针对不同场景不同需求来提供不同的 App Clip
体验,例如官方提供的统一连锁下不同咖啡馆举例
如何处理Universal Link和App Clip URL?
官方Demo提供的解决方案是通过编译宏APPCLIP
来做分支处理,这样能够最大程度共用代码:
1 | import SwiftUI |
其中handleUserActivity
就是在处理App Clip
链接。
当然,直接将主App和App Clip
的代码分开也是可以的,但是这可能对于两者区别较大的产品更适合,但同时也违反了Apple的初衷,希望App Clip
仅仅是主App的一部分,这可能会导致审核遇阻。
与小程序的异同
有些人认为App Clip
是苹果小程序,像微信一样。如果仅从两者设计之初的理念来比较,确实比较相似,但是从其他方面来讲,两者差异较大:
App Clip
一步到位,实施降维打击。App Clip
明确是主App功能的一部分,不能做主App无关的内容,但是微信小程序没有该限制,并且支持独立发布。也正是由于定位不同,微信小程序完全可以与App Clip
共分天下,微信小程序已经形成生态圈,国内很多开发商专注于小程序的开发,都没有App,自然也就用不上App Clip
,虽然App Clip
必然会抢占小程序的市场,但是目前看,还是无法从根本上动摇小程序的地位。
下载后用Xcode运行,会启动一个Mac程序。
ignore similar name
防止误删定期检查
防止误删
重复资源(主要指图片)不是指命名重复而是内容相同。fdupes
是Linux下的一个工具,可以在指定的目录及子目录中查找重复的文件。fdupes
通过对比文件的MD5签名,以及逐字节比较文件来识别重复内容。
项目中图片分两处存放,Assets.xcassets和images文件夹,所以在这两个目录查找就可以。
1 | fdupes -r xxx/images xxx/Images.xcassets |
AppCode代码静态检查
1 | 菜单栏 -> code -> Inspect Code |
Strip Linked Product
、Make Strings Read-Only
、Symbols Hidden by Default
设置为YES生成LinkMap文件,可以查看可执行文件的具体组成
可借助第三方工具解析LinkMap文件: https://github.com/huanxsd/LinkMap
项目角色
什么是XP
迭代长度的不同
在迭代中, 是否允许修改需求
Scrum这样处理的理由是:如果优先问题的解决者,由于其它事情耽搁,不能认领任务,那么整个进度就耽误了。 另外一个原因是,如果按优先级排序的User Story #6和#10,虽然#6优先级高,但是如果#6的实现要依赖于#10,则不得不优先做#10.
软件的实施过程中,是否采用严格的工程方法,保证进度或者质量
以下关于 objc_msgSend 和 fishhook 的说法中,哪些是正确的?
A. objc_msgSend 是使用 C 语言编写的
B. objc_msgSend 在运行时根据对象和方法的 selector 找到对应的函数指针,然后执行
C. 可以使用 fishhook 来 hook objc_msgSend
D. fishhook 通过重新绑定符号,实现对 c 方法的 hook
答案:B, D
A 选项,objc_msgSend 是使用汇编语言编写的。
C 选项,hook objc_msgSend 还需要用到汇编。
Auto Layout 的布局工作过程描述,哪些是正确的?
A. Layout Engine 在碰到约束变化后不会重新计算布局
B. Constraints Change 不会触发约束变化
C. Layout Engine 会将视图、约束、优先级、固定大小通过计算转换成最终的大小和位置
D. 在 Layout Engine 里,每当约束发生变化,就会触发 Deffered Layout Pass
答案:C, D
A 选项,Layout Engine 在碰到约束变化后会重新计算布局,获取到布局后调用 superview.setNeedLayout(),然后进入 Deferred Layout Pass。
B 选项,Constraints Change 表示的就是约束变化,添加、删除视图时会触发约束变化。
链接器对代码做的事情,哪些是正确的?
A. 去项目文件里查找目标代码文件里已经定义的变量
B. 扫描项目中的不同文件,将所有符号定义和引用地址收集起来,并放到全局符号表中
C. 计算合并后长度及位置,生成同类型的段进行合并,建立绑定
D. 对项目中不同文件里的变量进行地址重定位
答案:B, C, D
A 选项,查找没有定义的变量。
下面哪些工具可以作为静态分析器?
A. OCLint
B. Infer
C. Injection
D. ImageOptim
答案:A, B
C 选项,Injection 的工具用来动态地将 Swift 或 Objective-C 的代码在已运行的程序中执行,以加快调试速度,同时保证程序不用重启。
D 选项,ImageOptim 是图片压缩工具。
关于减少 App 包大小,哪些说法是正确的?
A. 通过 AppCode 找出无用代码
B. LinkMap 结合 Mach-O 找无用代码
C. 使用 LSUnusedResources 进行图片压缩
D. 使用 App Thining 可以减少包大小
答案:A, B
C 选项,LSUnusedResources 是找无用图片的工具,TinyPng 或 ImageOptim 才是图片压缩的工具。
下面哪些问题是信号可捕获的?
A. 内存被打爆
B. NSNotification 线程问题
C. 后台任务超时
D. 野指针
答案:B, D
A 选项和 C 选项是无法通过信号捕获的。
下面哪个异常编码,是表示 App 在一定时间内无响应而被 watchdog 杀掉的?
A. 0xdeadfa11
B. 0xc00010ff
C. 0x8badf00d
D. 0xdead10cc
答案:C
A 选项,表示 App 被用户强制退出。
B 选项,表示 App 因为运行造成设备温度太高而被杀掉。
D 选项,表示死锁,当 App 在后台运行时,持有了系统资源。
导致卡顿的原因,以下说法哪些是正确的?
A. 复杂 UI 、图文混排的绘制量过大
B. 在主线程上使用锁
C. 在主线程做大量的 IO 操作
D. 运算量过大,CPU 持续高占用
答案:A, C, D
B 选项,主线程上使用锁不一定会导致卡顿,死锁和主子线程抢锁才会。
以下哪些方法可以获取内存上限值?
A. 通过 JetsamEvent 日志计算内存限制值
B. 通过 XNU 获取内存限制值
C. 通过内存警告获取内存限制值
D. 通过 hook malloc_logger 函数来获取内存限制值
答案:A, B, C
D 选项,hook malloc_logger 用来定位谁分配的内存。
关于线程,以下哪些说法是正确的?
A. UIKit 是线程安全的
B. UIKit 没有使用多线程技术
C. 常驻线程一直存在于内存中
D. 线程开多了会有内存问题
答案:B, C, D
A 选项,UIKit 不是线程安全的。
以下哪些是可以在 iOS 中使用的 GUI 框架?
A. WebKit
B. Flutter
C. Blink
D. Texture
答案:A, B, D
C 选项,Blink 是谷歌从 WebKit 中 WebCore 拉出的分支,用于 Chrome 中,无法用在 iOS 中。
关于 iOS 开发中使用的开源库,下面哪些说法是正确的?
A. ReactiveCocoa、RxSwift、React.js 都是 iOS 响应式框架
B. Lottie 框架可以解决动画制作与开发隔离,以及多平台统一的问题
C. SkyLab 可以用来作 A/B 测试
D. Promise 可以提高 JSON 解析的性能
答案:B, C
A 选项,React.js 是前端响应式框架。
D 选项,Promise 是一种专门针对异步数据操作编写的一套统一规则的模式,可以用于事件总线的数据订阅和数据发布事件。
iOS 系统可以分为哪几层?
A. 最一层是 Topaz 层
B. 第二层是应用框架层
C. 第三层是微内核层
D. 第四层是 Darwin 层
答案:B, D
A 选项,第一层是户体验层,主要是提供用户界面。这一层包含了 SpringBoard、Spotlight、Accessibility。Topaz 层是 Fuchsia 的系统基础应用层。
C 选项,第三层是核心框架层,是系统核心功能的框架层。
关于 iOS 的 Runtime,以下说法哪些是正确的?
A. 可以帮助我们在运行时进行方法交换
B. 在原方法执行之前插入自定义方法
C. 直接使用 Runtime 方法交换开发很安全,无风险
D. Runtime 不光能够进行方法交换,还能够在运行时处理 Objective-C 特性相关(比如类、成员函数、继承)的增删改操作
答案:A, B, D
C 选项,直接使用 Runtime 的方法进行方法交换会有很多风险,更安全的是使用 Aspects。
关于函数调用,以下哪些说法是正确的?
A. dlsym() 无法通过函数名字符串获取函数地址
B. 编译时需要按照调用惯例针对不同 CPU 架构编译,生成汇编代码,确定好栈和寄存器
C. Objective-C 的函数调用采用的是发送消息的方式
D. Objective-C 具备动态调用函数的能力
答案:B, C, D
选项 A,dlsym() 可以通过函数名字符串拿到函数地址。
以下关于 libffi 的说法,哪些是正确的?
A. libffi 中 ffi 的全称是 Foundation Function Interface
B. libffi 还提供了可移植的高级语言接口,可以不使用函数签名间接调用 C 函数
C. libffi 通过发送消息的方式来进行函数调用
D. libffi 的作用类似于一个动态的编译器,在运行时就能够完成编译时所做的调用惯例函数调用代码生成
答案:B, D
A 选项,全称是 Foreign Function Interface。
C 选项,libffi 是通过调用 ffi_call(函数调用) 来进行函数调用。
关于 ffi_call_SYSV 处理过程,以下说法哪些是正确的?
A. ffi_call_SYSV 会先分配 stack 和 frame,保存记录 fn、rvalue、closure、flags
B. 将向量参数传到寄存器,按照参数放置规则,调整 sp 的位置
C. 将参数放入寄存器,存放完毕,就开始释放上下文,留下栈里的参数
D. 调用完函数指针,直接保存返回值
答案:A, B, C
选项 D,调用完函数指针,保存返回值之前需要重新读取 rvalue 和 flags,析构部分栈指针。
以下关于 JavaScriptCore 的说法,哪些是正确的?
A. JavaScriptCore 框架主要由 JSVirtualMachine 、JSContext、JSValue 类组成
B. JSVirtualMachine 里只包含一个 JSContext,一个 JSContext 中可以有多个 JSValue
C. JavaScriptCore 内部是由 Parser、Interpreter、Compiler、GC 等部分组成
D. 原生线程可以将类方法和属性提供给 JavaScriptCore 使用
答案:A, C, D
B 选项,JSVirtualMachine 里可以有多个 JSContext。
关于渲染原理,下面说法哪些是正确的?
A. CPU 内部流水线结构拥有并行计算能力,一般用于显示内容的计算
B. GPU 专门用来处理渲染内容的计算,比如视图创建、布局、图片解码等
C. GPU 的主要工作是将 3D 坐标转化成 2D 坐标,然后再把 2D 坐标转成实际像素
D. CPU 处理完渲染内容的计算,然后将图形结果显示在屏幕像素中
答案:A, C
B 选项,GPU 并行计算能力强,通过计算将图形结果显示在屏幕像素中,渲染内容的计算,通常使用 CPU 完成。
D 选项,CPU 专门用来处理渲染内容的计算,比如视图创建、布局、图片解码等,内容计算完成后,再传输给 GPU 进行渲染。
以下动态化方案的说法,哪些是正确的?
A. JIT 技术就是在 App 运行时创建机器代码,同时执行这些机器代码
B. LLVM IR 是 SSA 形式的
C. SSA 主要解决的是,多种数据流分析时种类多、难以维护的问题
D. LLVM 里有针对 iOS 系统可用的解释器
答案:A, B, C
D 选项,LLVM 没有专门针对 iOS 做解释器,因为 iOS 动态化在 LLVM 所有工作中的优先级并不高。
1 | F(0) = 0, F(1) = 1 |
示例 1:
1 | 输入:2 |
示例 2:
1 | 输入:3 |
示例 3:
1 | 输入:4 |
提示:
1 | 0 ≤ N ≤ 30 |
方法一
1 | // 通过递归的方式计算 - 有性能问题 |
方法二
1 | // 时间复杂度: O(n) |
1 | // 时间复杂度: O(n) |
]]>Swift中如果数值越界,会崩溃。建议采用大值数作为容器。
并不是所有的Widget都会被独立渲染!
只有继承RenderObjectWidget
的才会创建RenderObject对象!
在Flutter渲染的流程中,有三颗重要的树!Flutter引擎是针对Render树进行渲染!
每一个Widget都会创建一个Element对象。
隐式调用createElement
方法,Element加入Element树中。
它会创建三种Element
Q: Render树用于渲染,Widget用于定义开发。那么Element有什么意义?
A: Element持有Widget,同时也持有RenderObject。拿到context就可以更方便做事。
接下来我将分场景来介绍Dart
和Native
之间的通信。
几种通信场景:
在讲解 Flutter
与 Native
之间是如何传递数据之前,我们先来了解下 Flutter
与 Native
的通信机制,Flutter
和 Native
的通信是通过Channel来完成的。
消息使用Channel(平台通道)在客户端(UI)和主机(平台)之间传递,如下图所示:
Flutter中消息的传递是完全异步的;
Dart | Android | iOS |
---|---|---|
null | null | nil (NSNull when nested) |
bool | java.lang.Boolean | NSNumber numberWithBool: |
int | java.lang.Integer | NSNumber numberWithInt: |
int, if 32 bits not enough | java.lang.Long | NSNumber numberWithLong: |
double | java.lang.Double | NSNumber numberWithDouble: |
String | java.lang.String | NSString |
Uint8List | byte[] | FlutterStandardTypedData typedDataWithBytes: |
Int32List | int[] | FlutterStandardTypedData typedDataWithInt32: |
Int64List | long[] | FlutterStandardTypedData typedDataWithInt64: |
Float64List | double[] | FlutterStandardTypedData typedDataWithFloat64: |
List | java.util.ArrayList | NSArray |
Map | java.util.HashMap | NSDictionary |
持续通信
,收到消息后可以回复此次消息
**,如:Native将遍历到的文件信息陆续传递到Dart,再比如:Flutter将从服务端陆陆续获取到信息交个Native加工,Native处理完返回等;一次性通信
**:如Flutter调用Native拍照;持续通信
,收到消息后无法回复此次消息
**;通过长用于Native向Dart的通信,如:手机电量变化,网络连接变化,陀螺仪,传感器等;这三种类型的类型的Channel都是全双工通信,即A <=> B,Dart可以主动发送消息给platform端,并且platform接收到消息后可以做出回应,同样,platform端可以主动发送消息给Dart端,dart端接收数后返回给platform端。
构造方法原型
1 | + (instancetype)messageChannelWithName:(NSString*)name binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger codec:(NSObject<FlutterMessageCodec>*)codec; |
FlutterMessageHandler handler
- 消息处理器,配合BinaryMessenger
完成消息的处理;在创建好BasicMessageChannel
后,如果要让其接收Dart发来的消息,则需要调用它的setMessageHandler
方法为其设置一个消息处理器。
BasicMessageChannel.MessageHandler原型
1 | typedef void (^FlutterMessageHandler)(id _Nullable message, FlutterReply callback); |
^(NSString* message, FlutterReply reply)
- 用于接受消息,message是消息内容,reply是回复此消息的回调函数;send方法原型
1 | - (void)sendMessage:(id _Nullable)message; |
id message
- 要传递给Dart的具体信息;FlutterReply callback
- 消息发出去后,收到Dart的回复的回调函数;在创建好BasicMessageChannel
后,如果要向Dart发送消息,可以调用它的sendMessage
方法向Dart传递数据。
1 | //用MessageChannel传递数据 |
构造方法原型
1 | const BasicMessageChannel(this.name, this.codec); |
String name
- Channel的名字,要和Native端保持一致;MessageCodec<T> codec
- 消息的编解码器,要和Native端保持一致,有四种类型的实现具体可以参考Native端的介绍;setMessageHandler方法原型
1 | void setMessageHandler(Future<T> handler(T message)) |
构造方法原型
1 | //会构造一个FlutterStandardMethodCodec类型的MethodCodec |
FlutterBinaryMessenger* messenger
- 消息信使,是消息的发送与接收的工具;NSString* name
- Channel的名字,也是其唯一标识符;FlutterMethodCodec codec
- 用作MethodChannel
的编解码器;setMethodCallHandler方法原型
1 | - (void)setMethodCallHandler:(FlutterMethodCallHandler _Nullable)handler; |
FlutterMethodCallHandler handler
- 消息处理器,配合BinaryMessenger完成消息的处理;在创建好MethodChannel后,需要调用它的setMessageHandler方法为其设置一个消息处理器,以便能加收来自Dart的消息。
FlutterMethodCallHandler原型
1 | ^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) |
用于接受消息,call是消息内容,它有两个成员变量NSString类型的call.method
表示调用的方法名,id 类型的call.arguments
表示调用方法所传递的入参;FlutterResult result
是回复此消息的回调函数;
1 | - (void)initMethodChannel{ |
构造方法原型
1 | const MethodChannel(this.name, [this.codec = const StandardMethodCodec()]) |
String name
- Channel的名字,要和Native端保持一致;MethodCodec codec
- 消息的编解码器,默认是StandardMethodCodec,要和Native端保持一致;invokeMethod方法原型
1 | Future<T> invokeMethod<T>(String method, [ dynamic arguments ]) |
String method
:要调用Native的方法名;[ dynamic arguments ]
:调用Native方法传递的参数,可不传;1 | import 'package:flutter/services.dart'; |
构造方法原型
1 | //会构造一个FlutterStandardMethodCodec类型的MethodCodec |
FlutterBinaryMessenger* messenger
- 消息信使,是消息的发送与接收的工具;NSString* name
- Channel的名字,也是其唯一标识符;FlutterMethodCodec codec
- 用作EventChannel
的编解码器;setStreamHandler方法原型
1 | - (void)setStreamHandler:(NSObject<FlutterStreamHandler>* _Nullable)handler; |
(FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(FlutterEventSink)eventSink
- Flutter Native监听事件时调用,Object args
是传递的参数,EventChannel.EventSink eventSink
是Native回调Dart时的会回调函数,eventSink
提供success
、error
与endOfStream
三个回调方法分别对应事件的不同状态;(FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments
- Flutter取消监听时调用;1 |
|
构造方法原型
1 | const EventChannel(this.name, [this.codec = const StandardMethodCodec()]); |
String name
- Channel的名字,要和Native端保持一致;MethodCodec codec
- 消息的编解码器,默认是StandardMethodCodec,要和Native端保持一致;receiveBroadcastStream方法原型
1 | Stream<dynamic> receiveBroadcastStream([ dynamic arguments ]) |
在Flutter的API中提供了Native在初始化Dart页面时传递数据给Dart的方式,这种传递数据的方式比下文中所讲的其他几种传递数据的方式发生的时机都早。
因为很少有资料介绍这种方式,所以可能有很多同学还不知道这种方式,不过不要紧,接下来我就向大家介绍如何使用这种方式来传递数据给Dart。
Android向Flutter传递初始化数据initialRoute
Flutter允许我们在初始化Flutter页面时向Flutter传递一个String类型的initialRoute
参数,从参数名字它是用作路由名的,但是既然Flutter给我们开了这个口子,那我们是不是可以搞点事情啊,传递点我们想传的其他参数呢,比如:
1 | FragmentTransaction tx = getSupportFragmentManager().beginTransaction(); |
然后在Flutter module通过如下方式获取:
import 'dart:ui';//要使用window对象必须引入String initParams=window.defaultRouteName;//序列化成Dart obj 敢你想干的...
通过上述方案的讲解是不是给大家分享了一个新的思路呢。
接下来,我们先来看一下如何在Android
上来传递这些初始化数据。
在Flutter 中Native向Dart传递消息可以通过BasicMessageChannel
或EventChannel
都可以实现:
BasicMessageChannel
的方式如何实现?
EventChannel
的方式如何实现?
以上就是使用不同Channel实现Native到Dart通信的原理及方式,接下来我们来看一下实现Dart到Native之间通信的原理及方式。
在Flutter 中Dart向Native传递消息可以通过BasicMessageChannel
或MethodChannel
都可以实现:
BasicMessageChannel
的方式MethodChannel
的方式混合开发的一些其他应用场景:
在原有项目中加入Flutter页面,在Flutter项目中加入原生页面
原生页面中嵌入Flutter模块
Flutter页面中嵌入原生模块
如果你想要在已有的原生 App 里嵌入一些 Flutter 页面,有两个办法:
将Flutter集成到现有的iOS应用中需要如下几个主要步骤:
1 | - flutter_hybrid |
flutter_hybrid
下面分别是flutter模块
,原生Android模块
,与原生iOS模块
,并且这三个模块时并列结构
在做混合开发之前我们首先需要创建一个Flutter module。
假如你的Native项目是这样的:xxx/flutter_hybrid/FlutterHybridiOS
:
1 | $ cd xxx/flutter_hybrid/ |
上面代码会切换到你的iOS项目的上一级目录,并创建一个flutter模块:
1 | .android |
上面是flutter_module
中的文件结构,你会发现它里面包含.android
与.ios
,这两个文件夹是隐藏文件,也是这个flutter_module
宿主工程:
.android
- flutter_module
的Android宿主工程;.ios
- flutter_module
的iOS宿主工程;lib
- flutter_module
的Dart部分的代码;pubspec.yaml
- flutter_module
的项目依赖配置文件;因为宿主工程的存在,我们这个
flutter_module
在不加额外的配置的情况下是可以独立运行的,通过安装了Flutter与Dart插件的AndroidStudio打开这个flutter_module
项目,通过运行按钮是可以直接运行它的。
接下来我们需要配置我们iOS项目的Flutter module依赖,接下来的配置需要用到CocoaPods,如果你还没有用到CocoaPods,可以参考https://cocoapods.org/上面的说明来安装CocoaPods。
Podfile
文件中添加flutter
依赖Podfile
文件可以通过: 1 | pod init |
Podfile
文件,填入依赖信息1 | flutter_application_path = 'path/to/my_flutter/' |
1 | flutter_application_path = '../flutter_module/' |
Podfile
配置1 | flutter_application_path = 'path/to/my_flutter/' |
1 | target 'FlutterHybridiOS' do |
除此之外,老版本flutter还需要添加 build phase以构建Dart代码
根据上图的提示创建一个build phase
,然后展开Run Script
并添加下面配置:
1 | "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build |
最后记得根据上图的提示,将Run Script
到紧挨着Target Dependencies phase
的下面,接下来就可以通过⌘B
构建你的项目了。
通过
flutter doctor
命令查看你的Flutter版本,不同Flutter版本需要添加的配置是不同的:
在iOS项目的根目录运行:
1 | pod install |
你会看到:
1 | pod install |
当你在flutter_module/pubspec.yaml
添加一个Flutter插件时,需要在flutter_module
目录下运行:
1 | flutter packages get |
来刷新podhelper.rb
脚本中插件列表,然后在iOS目录下运行:
1 | pod install |
这样以来podhelper.rb
脚本才能确保你的插件和Flutter.framework能够添加到你的iOS项目中。
目前Flutter还不支持Bitcode,所以集成了Flutter的iOS项目需要禁用Bitcode:
用XCode打开你的项目如:xxx.xcworkspace
:
然后在:
1 | Build Settings->Build Options->Enable Bitcode |
目录下中禁用Bitcode:
如果在这个过程中遇到问题可以查看Under the hood文档。
至此,我们已经为我们的iOS项目添加了Flutter所必须的依赖,接下来我们来看如何在Object-c中调用Flutter模块:
在Object-c中调用Flutter模块有两种方式:
FlutterViewController
的方式;FlutterEngine
的方式;// flutter_hybrid ▸ FlutterHybridiOS ▸ FlutterHybridiOS ▸ ViewController.m #import <Flutter/Flutter.h>#import "AppDelegate.h"#import "ViewController.h"#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h> // 如果你需要用到Flutter插件时FlutterViewController *flutterViewController = [FlutterViewController new];GeneratedPluginRegistrant.register(with: flutterViewController);//如果你需要用到Flutter插件时[flutterViewController setInitialRoute:@"route1"]; [self presentViewController:flutterViewController animated:true completion:nil];
通过这种方式我们可以使用flutterViewController setInitialRoute
的方法为传递了字符串“route1”来告诉Dart代码在Flutter视图中显示哪个小部件。 Flutter模块项目的lib/main.dart
文件需要通过window.defaultRouteName
来获取Native指定要显示的路由名,以确定要创建哪个窗口小部件并传递给runApp:
import 'dart:ui';import 'package:flutter/material.dart';void main() => runApp(_widgetForRoute(window.defaultRouteName));Widget _widgetForRoute(String route) { switch (route) { case 'route1': return SomeWidget(...); case 'route2': return SomeOtherWidget(...); default: return Center( child: Text('Unknown route: $route', textDirection: TextDirection.ltr), ); }}
1 | // AppDelegate.h |
1 | // AppDelegate.m |
如果你的项目的AppDelegate.h已经有了别的集成,那么可惨参考实现FlutterAppLifeCycleProvider的方式进行配置。
1 | // flutter_hybrid ▸ FlutterHybridiOS ▸ FlutterHybridiOS ▸ ViewController.m |
因为我们在AppDelegate.m中提前初始化了
FlutterEngine
,所以这种方式打开一个Flutter模块的速度要比第一种方式要快一些。
在上文中,我们无论是通过直接使用FlutterViewController
的方式还是通过FlutterEngine
的方式,都允许我们在加载Flutter module时传递一个String类型的initialRoute
参数,从参数名字它是用作路由名的,但是既然Flutter给我们开了这个口子,那我们是不是可以搞点事情啊,传递点我们想传的其他参数呢,比如:
1 | [flutterViewController setInitialRoute:@"{name:'devio',dataList:['aa','bb',''cc]}"]; |
然后在Flutter module通过如下方式获取:
1 | import 'dart:ui';//要使用window对象必须引入 |
通过上述方案的讲解是不是给大家分享了一个新的思路呢。
注意使用
FlutterEngine
的方式中调用setInitialRoute
会无效,你会发现Dart拿到的永远是“/”,这个是Fltter SDK的一个Bug,所以如果必须依赖setInitialRoute
那么请使用方式一的形式;
接下来就是在编写Flutter module中的lib下编写Dart带了,快去Enjoy Coding
吧!!!
接下来,我们就可以运行它了,经过上述步骤,我们就可以以运行普通iOS项目的方式来通过XCode运行一个集成了Flutter的iOS项目了。
大家知道我们在做Flutter开发的时候,它带有热重启/重新加载的功能,但是你可能会发现,混合开发中在iOS项目中集成了Flutter项目,Flutter的热重启/重新加载功能好像失效了,那怎么启用混合开发汇总Flutter的热重启/重新加载呢:
flutter attach
;1 | $ cd flutter_hybrid/flutter_module |
Android SDK built for x86 • emulator-5554 • android-x86 • Android 8.1.0 (API 27) (emulator)
iPhone X • 3E3FA943-715F-482F-B003-D46F5902C56C • ios • iOS 12.1 (simulator)
1 |
|
注意-d
后面跟的设备ID。
运行APP,然后你会看到:
1 | $ flutter attach |
说明连接成功了,接下来就可以通过上面的提示来进行热加载/热重启了,在终端输入:
混合开发的模式下,如何更好更高效的调试我们的代码呢,接下来我就跟大家分享一种混合开发模式下高效调试代码的方式:
关闭APP(这步很关键)
点击AndroidStudio的Flutter Attach
按钮(需要首先安装Flutter与Dart插件)
启动APP
接下来就可以像调试普通Flutter项目一样来调试混合开发的模式下的Dart代码了。
除了以上步骤不同之外,接下来的调试和我们之前课程中的Flutter调试技巧都是通用的。
大家在运行iOS工程时一定要用Xcode运行,因为Flutter模式下的AndroidStudio运行的是Flutter module下的.ios
中的iOS工程。
发布iOS应用我们需要有一个99美元的账号用于将App上传到AppStore,或者是299美元的企业级账号用于将App发布到自己公司的服务器或第三方公司的服务器。
接下来我们就需要:
因为官方文档中有详细的说明,在这我就不再重复了。
]]>1 | import 'package:flutter/material.dart'; |
1 | import 'package:flutter/material.dart'; |
在Flutter中有一个RefreshIndicator
,它是一个下拉刷新的widget,通过它我们可以实现列表的下拉刷新。
1 | import 'package:flutter/material.dart'; |
如何实现上拉加载更多?
为了给列表添加上了加载更多的功能,我们可以借助ScrollController
,列表支持设置controller
参数,通过ScrollController
监听列表滚动的位置,来实现加载更多的功能。
1 | import 'package:flutter/material.dart'; |
Dart
是一门单线程的语言,那么Dart对异步操作对支持,可以使我们在编写Dart程序时可以异步的来执行耗时操作。从而可以在等待一个操作完成的同时进行别的操作以下是一些常见的异步操作:要在Dart中执行异步操作,可以使用Future类和async/await关键字。
在Dart中,实际上有两种队列:
]]>因为
microtask queue
的优先级高于event queue
,所以如果microtask queue
有太多的微任务, 那么就可能会霸占住当前的event loop
。从而对event queue中的触摸、绘制等外部事件造成阻塞卡顿。
为了解决这个问题,Dart语言将任务的执行模式分成两种:同步和异步。
Future
表示在接下来的某个时间的值或错误,借助Future
我们可以在Flutter实现异步操作。
它类似于ES6中的Promise,提供
then
和catchError
的链式调用;
Future
是dart:async
包中的一个类,使用它时需要导入dart:async
包,Future有两种状态:
future.then
获取future的值与捕获future的异常async
,await
future.whenComplete
future.timeout
future.then
获取future的值与捕获future的异常1 | import 'dart:async'; |
如果catchError与onError同时存在,则会只调用onError;
Future
的then`的原型:
1 | Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError}); |
第一个参数会成功的结果回调,第二个参数onError可选表示执行出现异常。
Future
是异步的,如果我们要将异步转同步,那么可以借助async await
来完成。
1 | import 'dart:async'; |
有时候我们需要在Future
结束的时候做些事情,我们知道then().catchError()
的模式类似于try-catch
,try-catch
有个finally
代码块,而future.whenComplete
就是Future
的finally。
1 | import 'dart:async'; |
完成一个异步操作可能需要很长的时间,比如:网络请求,但有时我们需要为异步操作设置一个超时时间,那么,如何为Future
设置超时时间呢?
1 | import 'dart:async'; |
运行上述代码会看到:TimeoutException after 0:00:02.000000: Future not completed
。
FutureBuilder
是一个将异步操作和异步UI更新结合在一起的类,通过它我们可以将网络请求,数据库读取等的结果更新的页面上。
FutureBuilder的构造方法
1 | FutureBuilder({Key key, Future<T> future, T initialData, @required AsyncWidgetBuilder<T> builder }) |
这个builder函数接受两个参数BuildContext context 与 AsyncSnapshot
AsyncSnapshot还具有hasData和hasError属性,以分别检查它是否包含非空数据值或错误值。
现在我们可以看到使用FutureBuilder的基本模式。 在创建新的FutureBuilder对象时,我们将Future对象作为要处理的异步计算传递。 在构建器函数中,我们检查connectionState的值,并使用AsyncSnapshot中的数据或错误返回不同的窗口小部件。
https://flutter-academy.com/async-in-flutter-futurebuilder/
FutureBuilder的使用?
1 | import 'dart:convert'; |
Flutter官方推荐我们在Flutter中用Http进行网络请求。
Http
是Flutter社区开发的一个可组合的、跨平台的用于Flutter的网络请求插件。
pubspec.yaml
中引入http插件;http.get
发送请求;1 | dependencies: |
1 | Future<http.Response> fetchPost() { |
http.get()
返回一个包含http.Response
的Future
:
http.Response
:类包含一个成功的HTTP请求接收到的数据;虽然发出网络请求很简单,但如果要使用原始的Future<http.Response>
并不简单。为了让我们可以开开心心的写代码,我们可以将http.Response
转换成我们自己的Dart对象。
创建一个CommonModel类
首先,我们需要创建一个CommonModel类,它包含我们网络请求的数据。它还将包括一个工厂构造函数,它允许我们可以通过json创建一个CommonModel对象。
1 | class CommonModel { |
将http.Response
转换成一个CommonModel
对象
现在,我们将更新fetchPost函数以返回一个Future
1 | Future<CommonModel> fetchPost() async { |
1 | import 'dart:convert'; |
]]>在上述代码中我们通过
fetchPost().then
获取Fluter的返回结果,其实Future
可以理解为ES5中的Promise。
可直接访问站点页面。appid
必须替换!!!
1 | https://itunesconnect.apple.com/apps/appid/appstore/platform/ios/resolutioncenter |
1 | Guideline 3.0 - Business |
1 | App内是否有大约$99美元的商品 |
1 | 删除定价过高的商品,重新配置 |
1 | Guideline 2.3.1 - Performance |
2.3.1
1 | 1.App本身是否使用混淆技术 |
3.1.1
1 | 1.可能是接入了支付宝或其它支付sdk |
软文
1 | 通过跟审核人员的电话沟通,我方已经了解App存在的问题。 |
电话预留
1 | Dear Reviewer, |
1 | Guideline 2.3.1 - Performance |
1 | 1.可能是接入了支付宝SDK |
1 | 10.6 – Apple and our customers place a high value on simple, refined, creative, well thought through interfaces. They take more work but are worth it. Apple sets a high bar. If your user interface is complex or less than very good, it may be rejected |
1 | 1. 移除检测App更新的按钮和提示 |
1 | Other - Other |
1 | 1. App第一个版本审核就会很慢 |
1 | 1. 耐心等待就好了 |
NSData *
类型的数据,从中解析出文件类型。1 | typedef NSInteger ImageFormat NS_TYPED_EXTENSIBLE_ENUM; |
1 |
|
在Flutter中动画分为两类:基于tween或基于物理的。
在为widget添加动画之前,先让我们认识下动画的几个朋友:
在Flutter中,Animation
对象本身和UI渲染没有任何关系。Animation
是一个抽象类,它拥有其当前值和状态(完成或停止)。其中一个比较常用的Animation
类是Animation<double>
。
Flutter中的Animation对象是一个在一段时间内依次生成一个区间之间值的类。Animation对象的输出可以是线性的、曲线的、一个步进函数或者任何其他可以设计的映射。 根据Animation
对象的控制方式,动画可以反向运行,甚至可以在中间切换方向。
Animation
还可以生成除double
之外的其他类型值,如:Animation<Color>
或 Animation<Size>
;Animation
对象有状态。可以通过访问其value属性获取动画的当前值;Animation
对象本身和UI渲染没有任何关系;CurvedAnimation
将动画过程定义为一个非线性曲线。
1 | final CurvedAnimation curve = |
注: Curves 类定义了许多常用的曲线,也可以创建自己的,例如:
1 | class ShakeCurve extends Curve { |
AnimationController
是一个特殊的Animation
对象,在屏幕刷新的每一帧,就会生成一个新的值。默认情况下,AnimationController在给定的时间段内会线性的生成从0.0到1.0的数字。 例如,下面代码创建一个Animation对象:
1 | final AnimationController controller = new AnimationController( |
AnimationController
派生自Animation<double>
,因此可以在需要Animation
对象的任何地方使用。 但是,AnimationController
具有控制动画的其他方法:
forward()
:启动动画;reverse({double from})
:倒放动画;reset()
:重置动画,将其设置到动画的开始位置;stop({ bool canceled = true })
:停止动画;当创建一个AnimationController时,需要传递一个vsync参数,存在vsync时会防止屏幕外动画消耗不必要的资源,可以将stateful对象作为vsync的值。
注意: 在某些情况下,值(position,值动画的当前值)可能会超出AnimationController的0.0-1.0的范围。例如,fling()函数允许您提供速度(velocity)、力量(force)、position(通过Force对象)。位置(position)可以是任何东西,因此可以在0.0到1.0范围之外。 CurvedAnimation生成的值也可以超出0.0到1.0的范围。根据选择的曲线,CurvedAnimation的输出可以具有比输入更大的范围。例如,Curves.elasticIn等弹性曲线会生成大于或小于默认范围的值。
默认情况下,AnimationController对象的范围从0.0到1.0。如果您需要不同的范围或不同的数据类型,则可以使用Tween来配置动画以生成不同的范围或数据类型的值。例如,以下示例,Tween生成从-200.0到0.0的值:
1 | final Tween doubleTween = new Tween<double>(begin: -200.0, end: 0.0); |
Tween是一个无状态(stateless)对象,需要begin和end值。Tween的唯一职责就是定义从输入范围到输出范围的映射。输入范围通常为0.0到1.0,但这不是必须的。
Tween继承自Animatable<T>
,而不是继承自Animation<T>
。Animatable与Animation相似,不是必须输出double值。例如,ColorTween指定两种颜色之间的过渡。
1 | final Tween colorTween = |
Tween对象不存储任何状态。相反,它提供了evaluate(Animation<double> animation)
方法将映射函数应用于动画当前值。 Animation对象的当前值可以通过value()
方法取到。evaluate函数还执行一些其它处理,例如分别确保在动画值为0.0和1.0时返回开始和结束状态。
Tween.animate
要使用Tween对象,可调用它的animate()方法,传入一个控制器对象。例如,以下代码在500毫秒内生成从0到255的整数值。
1 | final AnimationController controller = new AnimationController( |
注意animate()
返回的是一个Animation,而不是一个Animatable。
以下示例构建了一个控制器、一条曲线和一个Tween:
1 | final AnimationController controller = new AnimationController( |
在下面的实例中我们为一个logo添加了一个从小放大的动画:
1 | import 'package:flutter/material.dart'; |
注意,在上述代码中要实现这个动画的关键一步是在addListener()的回调中添加setState的调用这样才能触发页面重新渲染,动画才能有效,另外也可以通过AnimatedWidget来实现,在下文中会讲到。
有时我们需要知道动画执行的进度和状态,在Flutter中我们可以通过Animation的addListener
与addStatusListener
方法为动画添加监听器:
1 |
|
可对照学习为widget添加动画的例子;
我们可以将AnimatedWidget理解为Animation的助手,使用它可以简化我们对动画的使用,在为widget添加动画的学习中我们不难发现,在不使用AnimatedWidget的情况下需要手动调用动画的addListener()并在回调中添加setState才能看到动画效果,AnimatedWidget将为我们简化这一操作。
在下面的重构示例中,LogoApp现在继承自AnimatedWidget而不是StatefulWidget。AnimatedWidget在绘制时使用动画的当前值。LogoApp仍然管理着AnimationController和Tween。
1 |
|
什么是AnimatedBuilder?
AnimatedBuilder是用于构建动画的通用widget,AnimatedBuilder对于希望将动画作为更大构建函数的一部分包含在内的更复杂的widget时非常有用,其实你可以这样理解:AnimatedBuilder是拆分动画的一个工具类,借助它我们可以将动画和widget进行分离:
在上面的实例中我们的代码存在的一个问题: 更改动画需要更改显示logo的widget。更好的解决方案是将职责分离:
接下来我们就借助AnimatedBuilder
类来完成此分离。AnimatedBuilder
是渲染树中的一个独立的类, 与AnimatedWidget类似,AnimatedBuilder
自动监听来自Animation对象的通知,不需要手动调用addListener()
。
我们根据下图的 widget 树来创建我们的代码:
1 | import 'package:flutter/animation.dart'; |
如何使用Hero动画?
什么是Hero动画?
在 Flutter中可以用 Hero widget创建这个动画。当 hero 通过动画从源页面飞到目标页面时,目标页面逐渐淡入视野。通常, hero 是用户界面的一小部分,如图片,它通常在两个页面都有。从用户的角度来看, hero 在页面之间“飞翔”。接下来我们一起来学习如何创建Hero动画:
实现标准hero动画
1 | import 'package:flutter/material.dart'; |
Hero的函数原型
1 | const Hero({ |
实现径向hero动画
1 | import 'dart:math' as math; |
Flutter中一个用来展示图片的widget。
在加载项目中的图片资源时,为了让Image能够根据像素密度自动适配不同分辨率的图片,请使用
AssetImage
指定图像,并确保在widget树中的“Image” widget上方存在MaterialApp
,WidgetsApp
或MediaQuery
窗口widget。
Image 支持以下类型的图片:JPEG
, PNG
, GIF
, Animated GIF
, WebP
, Animated WebP
, BMP
, 和 WBMP
。
要加载网络图片,我们需要使用Image.network
构造方法:
1 | Image.network( |
demo:
1 | import 'package:flutter/material.dart'; |
要加载项目中的静态图片,需要一些两步:
pubspec.yaml
文件中声明图片资源的路径;AssetImage
访问图片;pubspec.yaml
声明图片路径:
1 | assets: |
1 | # 代表images文件夹下所有资源 |
使用AssetImage
访问图片图片:
1 | Image( |
除了我们使用Image
的构造方法手动指定AssetImage
之外,还可通过Image.asset
来加载静态图片:
1 | Image.asset(my_icon.png, |
两者是等效的。
1 | import 'dart:io'; |
第一步:
在pubspec.yaml中添加path_provider插件;
第二步:导入头文件
1 | import 'dart:io'; |
为了设置Placeholder我们需要借助FadeInImage,它能够从内存,本地资源中加载placeholder。
第一步:
安装transparent_image插件。
第二步:
1 | import 'package:flutter/material.dart'; |
第一步
配置本地资源图片:
1 | flutter: |
第二步
加载本地资源图片作为placeholder:
1 | FadeInImage.assetNetwork( |
在Flutter中我们可以借助cached_network_image插件,来从网络上加载图片,并且将其缓存到本地,以供下次使用。
1 | import 'package:flutter/material.dart'; |
在Flutter中我们可以借助Icon来加载icon:
1 | const Icon(this.icon//IconDate, { |
从Icon的构造方法可以很清楚的看出Icon构造方法需要一个默认的类型为IconData类型的参数,我们可以构造一个自己的IconData,也可以使用Flutter提供的material_fonts。
通过如下代码我们可以使用Flutter内置的material_fonts:
1 | import 'package:flutter/material.dart'; |
使用自定义的我们需要构造一个:
1 | const IconData( |
首先我我们需要向使用字体一样,在pubspec.yaml中配置我们的icon:
1 | fonts: |
接下来就可以使用了:
1 | child: new Icon(new IconData(0xf5566,fontFamily: "devio"),size: 100.0,color: Colors.blueAccent,) |
Container
包含一个子widget
,自身具备alignment
、padding
等属性,方便布局过程中摆放child
属性名 | 类型 | 说明 |
---|---|---|
key | key | Container唯一标识符,用于查找更新 |
alignment | AlignmentGeometry | 控制child的对齐方式,如果Container或者Container的父节点尺寸大于child的尺寸,该属性设置会起作用,有很多种对齐方式 |
padding | EdgeInsetsGeometry | Decoration内部的空白区,如果有child,child位于padding内部 |
color | Color | 用来设置Container背景色,如果foregroudDecoration设置的话,可能会覆盖color效果 |
decoration | Decoration | 绘制在child后面的修饰,设置了Decoration的话,就不能设置color属性,否则会报错,此时应该在Decoration中进行颜色的设置 |
foregroundDecoration | Decoration | 绘制在child前面的装饰 |
width | double | Container的宽度,设置为double.infinity可以强制在宽度上撑满,不设置,则根据child和父节点两者一起布局 |
height | double | Container的高度,设置为double.infinity可以强制在高度上撑满 |
constraints | BoxConstraints | 添加到child上额外的约束条件 |
margin | EdgeInsetsGeometry | 围绕在Decoration和child之外的空白区域,不属于内容区域 |
transform | Matrix4 | 设置Container的变换矩阵,类型为Matrix4 |
child | Widget | Container中的内容Widget |
padding与margin的不同之处:padding是包含在Content内,而margin是外部边界。设置点击事件的话,padding区域会响应,而margin区域不会响应。
文本组件Text负责显示文本和定义显示样式。
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
data | String | 数据为要显示的文本 | |
maxLines | int | 0 | 文本显示的最大行数 |
style | TextStyle | null | 文本样式,可定义文本的字体大小、颜色、粗细等 |
textAlign | TextAlign | TextAlign.center | 文本水平方向对齐方式,取值右center、end、justify、left、right、start、values |
textDirection | TextDirection | TextDirection.ltr | 有些文本书写方向从左到右,如英语、中文,有些则是从右到左,比如乌尔都语。从左到右使用TextDirection.ltr从右到左使用TextDirection.rtl |
textScaleFactor | double | 1.0 | 字体缩放系数,比如设置为1.5,那么字体会放大1.5倍 |
textSpan | TextSpan | null | 文本块,TextSpan里可以包含文本内容及样式 |
富文本组件RichText使用多个不同风格的widget显示文本,要显示的文本使用TextSpan对象树来描述,每个对象都有一个用于该子树的关联样式。文本可能多行,也可能显示在同一行,取决于布局约束。
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
text | TextSpan | - | 要显示的的文本 |
textAlign | TextAlign | TextAlign.start | 文本水平方向对齐方式,取值右center、end、justify、left、right、start、values |
textDirection | TextDirection | TextDirection.ltr | 有些文本书写方向从左到右,如英语、中文,有些则是从右到左,比如乌尔都语。从左到右使用TextDirection.ltr从右到左使用TextDirection.rtl |
softWrap | bool | true | 是否自动换行 |
overflow | TextOverflow | TextOverflow.clip | 内容超出文本范围处理方式,默认截断处理 |
textScaleFactor | double | 1.0 | 文本缩放比例,默认100%比例显示 |
maxLines | int | - | 最大显示行数 |
详细介绍
图片组件Image显示图像的组件,有多种构造函数:
new Image
:从ImageProvider获取图像new Image.asset
:加载资源图片new Image.file
:加载本地图片文件new Image.network
:加载网络图片new Image.memory
:加载Uint8List资源图片属性名 | 类型 | 说明 |
---|---|---|
image | ImageProvider | 抽象类,需要自己实现获取图片数据的操作 |
width/height | double | Image显示区域的宽度和高度设置,这里需要把Image和图片两个区分开。图片本身有大小,Image Widget是图片的容器,本身也有大小。宽度和高度的配置经常和fit属性搭配 |
fit | BoxFit | 图片填充模式,具体取值见 BoxFit取值表 |
color | Color | 图片颜色 |
colorBlendMode | BlendMode | 在对图片进行手动处理的时候,可能用到图层混合,如改变图片颜色,此属性可以对颜色进行混合处理,有多种模式 |
alignment | Alignment | 控制图片的摆放位置,比如图片放置在右下角则为Alignment.bottomRight |
repeat | ImageRepeat | 此属性可以设置图片的重复模式。moRepeat为不重复,Repeat为x和y方向重复,repeatX为x方向重复,repeatY为y方向重复 |
centerSlice | Rect | 当图片需要被拉伸显示时,centerSlice定义的矩形区域会被拉伸,可以理解为我们在图片内部定义9个点用作拉伸,9个点为”上”,”下”,”左”,”右”,”上中”,”下中”,”左中”,”右中”,”正中” |
matchTextDirection | bool | matchTextDirection与Derection配合使用。TextDirectio有两个值分别为:TextDirection.ltr从左到右展示图片,TextDirection.rtl为从右到左展示图片 |
gaplessPlayback | bool | 当ImageProvider发生变化后,重新加载图片的过程中,原图片的展示是否保留。值为true则保留,值为false则不保留,直接等待下一张图片加载 |
值 | 描述 |
---|---|
BoxFit.fill | 全图显示,显示可能拉伸,充满 |
BoxFit.contain | 全图显示,显示原比例,不需充满 |
BoxFit.cover | 显示可能拉伸,可能裁剪,充满 |
BoxFit.fitWidth | 显示可能拉伸,可能裁剪,宽度充满 |
BoxFit.fitHeight | 显示可能拉伸,可能裁剪,高度充满 |
BoxFit.none | 原始大小 |
BoxFit.scaleDown | 效果和BoxFit.contain差不多,但是该属性不允许显示超过原图大小(可小不可大) |
Flutter中,可以像web开发一样使用iconfont,iconfont即字体图标
,它是将图标做成字体文件,然后通过指定不同的字符而显示不同的图片。
在Flutter开发中,iconfont和图片相比有如下优势:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
icon | IconData | null | 展示的具体图标,可使用Icons图标列表中的任意一个图标即可,如Icons.phone表示一个电话的图标 |
color | Color | null | 图标的颜色 |
style | TextStyle | null | 文本样式,可定义文本的字体大小、颜色、粗细等 |
size | Double | 24.0 | 图标的大小,注意需要带上小数位 |
textDirection | TextDirection | TextDirection.ltr | Icon组件里也可以添加文本内容。有些文本书写的方向是从左到右,有些则是从右到左。从左到右使用TextDirection.ltr从右到左使用TextDirection.rtl |
图标按钮组件IconButton是基于Meterial Design
风格的组件,可以响应按下的事件,并且按下时带水波纹效果。如果它的onPressed
回调函数为null
,那么这个按钮处于禁用状态,并且不可按下。
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
alignment | AlignmentGeometry | Alignment.center | 定义IconButton的Icon对齐方式,默认为居中。Alignment可以设置x,y的偏移量 |
icon | Widget | null | 展示的具体图标,可以使用Icons图标列表中任意一个图标即可,如Icons.phone表示一个电话图标 |
color | Color | null | 图标组件的颜色 |
disabledColor | Color | ThemeData.disabledColor | 图标组件禁用状态的颜色,默认为主题里的禁用颜色,也可以设置为其他颜色 |
iconSize | double | 24.0 | 图标的大小,注意需要带上小数点 |
onPressed | VoidCallback | null | 当按钮按下时会触发此回调事件 |
tooltip | String | “” | 当按钮长按下时的提示语句 |
RaisedButton是Material Design
中的button,一个凸起的材质矩形按钮,它可以响应按下事件,并且按下时会带一个触摸效果。
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
color | Color | null | 组件的颜色 |
disabledColor | Color | ThemeData.disabledColor | 组件禁用状态的颜色,默认为主题里的禁用颜色,也可以设置为其他颜色 |
onPressed | VoidCallback | null | 当按钮按下时会触发此回调事件 |
child | Widget | - | 按钮的child通常为一个Text文本组件,用来显示按钮的文本 |
enable | bool | true | 按钮是否为禁用状态 |
配置镜像
1 | #Flutter |
来到你对应Shell的配置文件进行配置。
如果你使用默认的bash那么配置~/.bash_profile
如果你使用zsh (Mac新 系统默认是zsh)那么配置~/.zshrc
配置Flutter环境变量(将Flutter命令行工具的路径配置到Shell的配置文件中)
1 | #Flutter |
检查Flutter环境
1 | flutter doctor |
出现如下结果就完成了环境配置
如果出现错误,请把下面Android环境安装完成
网上搜索吧
Flutter
安装后重启
1 | google() |
1 | maven { url 'https://maven.aliyun.com/repository/google' } |
1 | buildscript { |
快捷键 | 备注 |
---|---|
Ctrl + option + O | 删除未使用的import |
option + Enter | 自动import未导入的文件 |
Shift + F6 | 重命名(如果是Touch Bar就是Shift + Rename…) |
CMD + -/+ | 折起/展开代码块 |
CMD + . | 折起/展开选中代码 |
CMD + , | 进入设置页面 |
CMD + [ | 光标回到上一次编辑的位置 |
CMD + ] | 光标回到下一次编辑的位置 |
CMD + L | 定位某一行,甚至某一个字符 |
CMD + / | 注释 |
CMD + Y | 查看选中类的属性 |
CMD + O | 快速打开(一般用于快速打开某个文件) |
option + Enter | 扩展功能(很重要!) |
Option + up(↑) | 选中上一层代码(比如选中当前光标的单词,选中当前小部件的所有代码,自己试~) |
CMD + option + M | 将选中代码提取到某个方法中 |
CMD + option + L | 格式化代码 |
CMD + Shift + -/+ | 折起/展开所有代码块 |
Option + Shift + Up/Down | 上下移动行 |
Command + Shift + Up/Down | 上下移动方法 |
查看小部件源码
项目都是独立的,可单独运行
作为iOS开发工程师,在学习Flutter过程中,要对Android系统开发也要有些了解,要不然处处是坑;如果想开发一个项目,至少需要iOS和Android两个高级工程师来做,因为Flutter毕竟是跨平台UI的解决方案,它不是原生,在处理底层事件和高性能需求的时候离不开原生,需要制定通讯协议和解决方案;
个人觉得全量开发的可能性非常非常小,苹果也不允许的,而且目前很多公司的产品改造成Flutter的成本太大,ROI不高,不过flutter确实越来越完善了,如果swiftUI没发力,原生开发肯定会被严重蚕食
]]>你愿意在下一个项目中尝试 Flutter 吗?
Foundation
层由dart:ui
实现。 dart:ui
提供了Framework
能够运行的最基础功能,比如绘图,界面刷新,触屏,鼠标等事件的原始信息等。Rendering
层由几个子模块组成:Animation
, Painting
, Gestures
. 在这一层,Flutter提供RenderObject
,实现完整的布局,绘制功能。Widgets
层是开发者最常接触到的一层。 Widget
是对RenderObject
的封装。在Widget
层,Flutter实现了响应式开发框架。Material
,Cupertino
层: 在这一层,Flutter提供了一系列的Widget
,其中Material Widget
实现了Material Design
。Cupertino
提供了一系列的iOS-Style
的控件。]]>转载自Google官方文档
NSSet
和NSMutableSet
。下面介绍个特殊的。NSHashTable
是更广泛意义的NSSet
,区别于NSSet/NSMutableSet
,NSHashTable
有如下特性:
Hash冲突解决方案:拉链法
使用方法
1 | NSString *str = @"10"; |
NSHashTableOptions介绍(源码中是使用静态常量做关联)
1 | enum { |
在objc源码中,找到了关于NSHashTable的类似实现。可以参考下
NXHashTable存储结构(精简过)
1 | typedef struct { |
NXHashTablePrototype存储结构(精简过)
1 | typedef struct { |
HashBucket存储结构
1 | typedef union { |
NXHashTable的构造器NXCreateHashTable
和NXCreateHashTableFromZone
1 | NXHashTable *NXCreateHashTable (NXHashTablePrototype prototype, unsigned capacity, const void *info) { |
取数据
1 | // 通过hash函数(data指针位运算得到的散列数)->取模(得到下标)-> 加上头指针(得到数据的具体地址) |
插入数据
1 | void *NXHashInsert (NXHashTable *table, const void *data) { |
扩容
1 | // 思路: |
删除某个元素
1 | void *NXHashRemove (NXHashTable *table, const void *data) { |
macOS Catalina、macOS Mojave 或 macOS High Sierra
这些 macOS 版本的安装器都会以名为Install macOS Catalina
、Install macOS Mojave
或Install macOS High Sierra
的 App
的形式直接下载到您的“应用程序”文件夹。如果安装器在下载后打开,请退出而不要继续安装。
要获取正确的安装器,请从运行
macOS Sierra 10.12.5
或更高版本或者El Capitan 10.11.6
的 Mac 中进行下载。
OS X El CapitanEl Capitan
会以磁盘映像形式下载。在与El Capitan
兼容的 Mac 上,打开磁盘映像,并运行其中名为InstallMacOSX.pkg
的安装器。随即会在您的“应用程序”文件夹中安装一个名为Install OS X El Capitan
的 App
。您将通过这个 App(而不是磁盘映像或 .pkg 安装器)创建可引导安装器。
如果需要恢复
macOS Sierra
(10.12)版本,需要先恢复到OS X El Capitan
(10.11),然后在AppStore中找到macOS Sierra
(10.12)的镜像进行升级。
连接要用于可引导安装器的 USB 闪存驱动器或其他宗卷。确保这个驱动器或宗卷至少有 12GB 可用储存空间,并已格式化为 Mac OS 扩展格式。
打开应用程序
文件夹内实用工具
文件夹中的终端
。
在终端
中键入或粘贴以下命令之一。这些命令假设安装器仍位于您的“应用程序
文件夹中,并且 MyVolume
是 USB 闪存驱动器
或您正在使用的其他宗卷的名称
。如果不是这个名称,请将命令中的 MyVolume
相应地替换为您的宗卷名称
。
Catalina:
1 | sudo /Applications/Install\ macOS\ Catalina.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume |
Mojave:
1 | sudo /Applications/Install\ macOS\ Mojave.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume |
High Sierra:
1 | sudo /Applications/Install\ macOS\ High\ Sierra.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume |
El Capitan:
1 | sudo /Applications/Install\ OS\ X\ El\ Capitan.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume --applicationpath /Applications/Install\ OS\ X\ El\ Capitan.app |
键入相应的命令后,请按下 Return
键。
出现提示时,请键入您的管理员密码,然后再次按下 Return
键。在您键入密码时,终端
不会显示任何字符。
出现提示时,请键入Y
以确认您要抹掉宗卷,然后按下 Return
键。创建可引导安装器过程中,终端
将显示进度。
当终端
提示操作已完成时,宗卷的名称将与您下载的安装器名称相同,例如Install macOS Catalina
。您现在可以退出终端
并弹出宗卷。
如果您的 Mac 运行的是
macOS Sierra
或更低版本
,请使用--applicationpath
参数,具体方法与在适用于 El Capitan 的命令中使用这个参数的方法类似。
U盘
插入兼容的 Mac。option
键U盘
,进入等待磁盘工具``抹掉
硬盘,完成后退出该工具重新安装MacOS
, 一路Next经过了一年AI的学习,这门技术带给我无限的惊叹和崇拜。最近我也逐渐看清了这门技术与我而言在职业发展上的定位。
经过了这一年,我真的特别累,自己学新知识,自己学算法,因为我身边的朋友给不了我任何的帮助,孤独注定是痛苦的。
对于我个人来讲,特别渴望吸取新技术。这也是我的缺点,这一年我盲目的扩张自己的技术,忽略了自己的专业。当身边的朋友从工程师爬到了移动架构的层级,而我还停留在工程师,我慕了;这种差距对于我而言,打击挺大的。之前我一直对自己说,你也在不断的学习,只要自己学到了,就不要管其他的,因为专注的方向不一样。
最近我开始研究AI工程师在市场上的需求和要求,我发现这个行业对学历要求高,对经验要求也高。而且这个技术现在已经到达了瓶颈期,如果要想玩点儿新潮的,高精尖,要阅读国外的论文,趁论文没有被框架化,找机会实现它,来达到技术高逼格。如果真的想自己去研究算法,说实话,我没那天赋,资金和时间。
未来我不会花费太多的时间去搞机器学习的东西,吃饭优先吧。
2020年TODO:
Flutter
框架iOS底层
知识汇编
算法
和数据结构
css
和js
不能说不懂,能看懂,但是开发起来有些费劲,所以我给他的承诺就是我接口也就能改改Bug,如果指着我开发很困难。我能简单的看懂Html、css、js,所以我花费几天时间把Vue看一下
Vue是渐进式框架,只需要花费1到2天的时间就可以开发了,我只要能达到修bug的能力就可以。
了解生命周期
,引用流程
,声明式编程
,v-if/v-for语法
.
Python Data Analysis Library
pandas是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。Pandas 纳入 了大量库和一些标准的数据模型,提供了高效地操作大型结构化数据集所需的工具。
数据结构是计算机存储、组织数据的方式。 通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。
Series可以理解为一个一维的数组,只是index名称可以自己改动。类似于定长的有序字典,有Index和 value。
1 | import pandas as pd |
访问Series中的数据:
1 | # 使用索引检索元素 |
pandas日期处理
1 | # pandas识别的日期字符串格式 |
Series.dt提供了很多日期相关操作,如下:
1 | Series.dt.yearThe year of the datetime. |
通过指定周期和频率,使用date.range()
函数就可以创建日期序列。 默认情况下,范围的频率是天。
1 | import pandas as pd |
bdate_range()
用来表示商业日期范围,不同于date_range()
,它不包括星期六和星期天。
1 | import pandas as pd |
DataFrame是一个类似于表格的数据类型,可以理解为一个二维数组,索引有两个维度,可更改。DataFrame具有以下特点:
1 | import pandas as pd |
列访问
DataFrame的单列数据为一个Series。根据DataFrame的定义可以 知晓DataFrame是一个带有标签的二维数组,每个标签相当每一列的列名。
1 | import pandas as pd |
列添加
DataFrame添加一列的方法非常简单,只需要新建一个列索引。并对该索引下的数据进行赋值操作即可。
1 | import pandas as pd |
列删除
删除某列数据需要用到pandas提供的方法pop,pop方法的用法如下:
1 | import pandas as pd |
行访问
如果只是需要访问DataFrame某几行数据的实现方式则采用数组的选取方式,使用 “:” 即可:
1 | import pandas as pd |
loc方法是针对DataFrame索引名称的切片方法。loc方法使用方法如下:
1 | import pandas as pd |
iloc和loc区别是iloc接收的必须是行索引和列索引的位置。iloc方法的使用方法如下:
1 | import pandas as pd |
行添加
1 | import pandas as pd |
行删除
使用索引标签从DataFrame中删除或删除行。 如果标签重复,则会删除多行。
1 | import pandas as pd |
修改DataFrame中的数据
更改DataFrame中的数据,原理是将这部分数据提取出来,重新赋值为新的数据。
1 | import pandas as pd |
DataFrame常用属性
编号 | 属性或方法 | 描述 |
---|---|---|
1 | axes | 返回 行/列 标签(index)列表。 |
2 | dtype | 返回对象的数据类型(dtype )。 |
3 | empty | 如果系列为空,则返回True 。 |
4 | ndim | 返回底层数据的维数,默认定义:1 。 |
5 | size | 返回基础数据中的元素数。 |
6 | values | 将系列作为ndarray 返回。 |
7 | head() | 返回前n 行。 |
8 | tail() | 返回最后n 行。 |
实例代码:
1 | import pandas as pd |
Jupyter Notebook(此前被称为 IPython notebook)是一个交互式笔记本,支持运行 40 多种编程语言。使用浏览器作为界面,向后台的IPython服务器发送请求,并显示结果。 Jupyter Notebook 的本质是一个 Web 应用程序,便于创建和共享文学化程序文档,支持实时代码,数学方程,可视化和 markdown。
IPython 是一个 python 的交互式 shell,比默认的python shell 好用得多,支持变量自动补全,自动缩进,支持 bash shell 命令,内置了许多很有用的功能和函数。
安装ipython
windows: 前提是有numpy,matplotlib pandas
采用pip安装 pip install ipython
OS X: AppStore下载安装苹果开发工具Xcode。
使用easy_install或pip安装IPython,或者从源文件安装。
安装Jupyter notebook
1 | pip3 install jupyter |
数值型数据的描述性统计主要包括了计算数值型数据的完整情况、最小值、均值、中位 数、最大值、四分位数、极差、标准差、方差、协方差等。在NumPy库中一些常用的统计学函数也可用于对数据框进行描述性统计。
1 | np.min最小值 |
实例:
1 | import pandas as pd |
pandas提供了统计相关函数:
1 | count() | 非空观测数量 |
---|---|---|
2 | sum() | 所有值之和 |
3 | mean() | 所有值的平均值 |
4 | median() | 所有值的中位数 |
5 | std() | 值的标准偏差 |
6 | min() | 所有值中的最小值 |
7 | max() | 所有值中的最大值 |
8 | abs() | 绝对值 |
9 | prod() | 数组元素的乘积 |
10 | cumsum() | 累计总和 |
11 | cumprod() | 累计乘积 |
pandas还提供了一个方法叫作describe,能够一次性得出数据框所有数值型特征的非空值数目、均值、标准差等。
1 | import pandas as pd |
Pandas有两种排序方式,它们分别是按标签与按实际值排序。
1 | import pandas as pd |
按行标签排序
使用sort_index()
方法,通过传递axis
参数和排序顺序,可以对DataFrame
进行排序。 默认情况下,按照升序对行标签进行排序。
1 | import pandas as pd |
按列标签排序
1 | import numpy as np |
按某列值排序
像索引排序一样,sort_values()
是按值排序的方法。它接受一个by
参数,它将使用要与其排序值的DataFrame
的列名称。
1 | import pandas as pd |
在许多情况下,我们将数据分成多个集合,并在每个子集上应用一些函数。在应用函数中,可以执行以下操作 :
1 | import pandas as pd |
1 | # 按照年份Year字段分组 |
groupby返回可迭代对象,可以使用for循环遍历:
1 | grouped = df.groupby('Year') |
1 | grouped = df.groupby('Year') |
聚合函数为每个组返回聚合值。当创建了分组(group by)对象,就可以对每个分组数据执行求和、求标准差等操作。
1 | # 聚合每一年的平均的分 |
Pandas具有功能全面的高性能内存中连接操作,与SQL等关系数据库非常相似。
Pandas提供了一个单独的merge()
函数,作为DataFrame对象之间所有标准数据库连接操作的入口。
合并两个DataFrame:
1 | import pandas as pd |
使用“how”参数合并DataFrame:
1 | # 合并两个DataFrame (左连接) |
其他合并方法同数据库相同:
合并方法 | SQL等效 | 描述 |
---|---|---|
left | LEFT OUTER JOIN | 使用左侧对象的键 |
right | RIGHT OUTER JOIN | 使用右侧对象的键 |
outer | FULL OUTER JOIN | 使用键的联合 |
inner | INNER JOIN | 使用键的交集 |
试验:
1 | # 合并两个DataFrame (左连接) |
有如下数据:
1 | import pandas as pd |
透视表
透视表(pivot table)是各种电子表格程序和其他数据分析软件中一种常见的数据汇总工具。它根据一个或多个键对数据进行分组聚合,并根据每个分组进行数据汇总。
1 | # 以class_id与gender做分组汇总数据,默认聚合统计所有列 |
交叉表
交叉表(cross-tabulation, 简称crosstab)是一种用于计算分组频率的特殊透视表:
1 | # 按照class_id分组,针对不同的gender,统计数量 |
基本绘图:绘图
1 | import pandas as pd |
plot方法允许除默认线图之外的少数绘图样式。 这些方法可以作为plot()
的kind
关键字参数。这些包括 :
bar
或barh
为条形hist
为直方图scatter
为散点图条形图
1 | df = pd.DataFrame(np.random.rand(10,4),columns=['a','b','c','d']) |
直方图
1 | df = pd.DataFrame() |
散点图
1 | df = pd.DataFrame(np.random.rand(50, 4), columns=['a', 'b', 'c', 'd']) |
饼状图
1 | df = pd.DataFrame(3 * np.random.rand(4), index=['a', 'b', 'c', 'd'], columns=['x']) |
读取与存储csv:
1 | # filepath 文件路径。该字符串可以是一个URL。有效的URL方案包括http,ftp和file |
1 | DataFrame.to_csv(excel_writer=None, sheetname=None, header=True, index=True, index_label=None, mode=’w’, encoding=None) |
读取与存储excel:
1 | # io 表示文件路径。 |
1 | DataFrame.to_excel(excel_writer=None, sheetname=None, header=True, index=True, index_label=None, mode=’w’, encoding=None) |
读取与存储JSON:
1 | # 通过json模块转换为字典,再转换为DataFrame |
需求如下:
读取数据,从用户表读取用户信息、同样方法,导入电影评分表、电影数据表。
合并数据表
对数据初步描述分析
查看每一部电影不同性别的平均评分并计算分歧差值,之后排序
算出每部电影平均得分并对其进行排序
查看评分次数多的电影并进行排序
过滤掉评分条目数不足250条的电影
评分最高的十部电影
查看不同年龄的分布情况并且采用直方图进行可视化
在原数据中标记出用户位于的年龄分组
可视化显示movies_ratings中不同类型电影的频数
早期的深度学习受到了神经科学的启发,它们之间有着非常密切的联系。深度学习方法能够具备提取抽象特征的能力,也可以看作是从生物神经网络中获得了灵感。
传统机器学习过程
深度学习过程(以图像数据为例)
传统机器学习算法需要在样本数据输入模型前经历一个人工特征提取的步骤,之后通过算法更新模型的权重参数。经过这样的步骤后,当在有一批符合样本特征的数据输入到模型中时,模型就能得到一个可以接受的预测结果。
而深度学习算法不需要在样本数据输入模型前经历一个人工特征提取的步骤,将样本数据输入到算法模型中后,模型会从样本中提取基本的特征(图像的像素)。之后,随着模型的逐步深入,从这些基本特征中组合出了更高层的特征,比如线条,简单形状(如汽车轮毂边缘)等。此时的特征还是抽象的,我们无法形象这些特征组合起来会得到什么,简单形状可以被进一步组合,在模型越深入的地方,这些简单的形状也逐步地转化成更加复杂的特征(特征开始具体化,比如看起来更像一个轮毂而不是车身),这就使得不同类别的图像更加可分。这时,将这些提取到的特征再经历类似的机器学习算法中的更新模型权重参数等步骤,也就可以得到一个令人满意的结果。
生物学神经元简单介绍
1904年生物学家就已经知晓了神经元的组成结构。
每个神经元都是一个信息处理单元,且具有多输入但输出特性。
神经元的输入可分为兴奋性输入和抑制性输入两种类型。
神经元阈值特性,当细胞体膜内外电位差(由突触输入信号总和)超过阈值时产生脉冲,神经细胞进入兴奋状态。
信息在突触结构间的传递存在延迟,神经元的输入与输出之间也具有一定的延时。
突触是由突触前膜,突触间隙和突触后膜三部分构成,一个神经元的轴突末梢经过多次分支,最后每一小支的末端膨大呈杯状或球状,叫做突触小体。这些突触小体可以与多个神经元的细胞体或树突相接触而形成突触(一个神经元可以与多个突触小体进行连接)。
化学突触指的是突触前膜借助化学信号(即递质)将信息转送到突触后细胞;而电突触则借助电信号。化学突触和电突触都又相应地被分为兴奋性突触和抑制性突触。使下一个神经元产生兴奋效应的为兴奋性突触,使下一个神经元禅师抑制效应的为抑制性突触。因此看来,突触的主要作用是在神经元细胞传递信息。
接下来我们仿照神经元模型建立一个人工神经网络,简称神经网络或连接模型。这是一种模仿动物神经网络行为特征,进行分布式信息处理的数学算法模型,本质就是按照生物神经圆的结构和工作原理构造出来一个抽象和简化了的数学模型。
这个模型不必模拟生物神经元的所有属性和行为,但要足以模拟它执行计算的过程。处理简单易表达的目的,我们忽略了不太相关的复杂因素。我们的人工神经网络模型是一个包含输入,输出与计算功能的模型。输入可以类比为神经元的树突,而输出可以类比为神经元的轴突,计算则可以类比为细胞核。
下图是一个典型的神经元模型:包含有2个输入,1个阈值,1个输出,以及2个计算功能。这些线称为“连接”。每个上有一个“权值”。
对于某一个神经元,它可能同时接收了多个输入信号(输入1,2),神经元之间考形成突触的方式构成神经网络,但各个突触的结构的性质与连接强度不尽相同,具体表示时相同的输入可能对不同的神经元有不同的影响。引入权重值的目的就是为了模拟突触的这种表现,其征途代表了生物神经元中突出的兴奋或抑制,其大小则表示突触间的不同连接强度。
b 表示一个阈值。
考虑到神经元的累加性,我们对全部的输入信号进行累加整合,相当于生物神经元中膜电位的变化总量,其值可以用下述公式表示:
生物神经元的激活与否取决于输入信号与某一阈值电平的比较。在神经元模型中也是类似的,只有当其输入总和超过阈值b是才会被激活,否则神经元不会被激活,当处于激活后,可以计算出y值。
之后会在进行一个非线性变换,也就是经过非线性激活函数,计算出该节点的输出(激活值)a = g(y)
其中g(y) 为非线性函数。在深度学习中,常用的激活函数主要有,sigmid, tanh, ReLu。
比如此时我们选用sigmoid函数,该函数是将取值为负无穷到正无穷的数映射到(0,1)之间。Sigmoid函数的公式及图形如下。
若神经元的激活a为正值,称改神经元处于激活状态或兴奋状态,若a为负值,则称神经元处于抑制状态。
简单神经网络可分为三层,分别是输入层、输出层、以及中间的隐藏层
输入层有3个输入单元,隐藏层有4个单元,输出层有2个单元。
设计一个神经网络时,输入层与输出层的节点数往往是固定的,中间层则可以自由指定;
神经网络结构图中的拓扑与箭头代表着预测过程时数据的流向,跟训练时的数据流有一定的区别;
结构图里的关键不是圆圈(代表“神经元”),而是连接线(代表“神经元”之间的连接)。每个连接线对应一个不同的权重(其值称为权值),这是需要训练得到的。
整理输入与输出
我们有一组样本数据。每个样本有三个输入特征与一个输出结果。我们需要做的就是通过三个输入特征值预测输出。
模型搭建与训练
依据设计好的神经网络结构,为每一层的输入分配权重,完成神经网络正向结构搭建。基于正向传播计算样本预测输出。根据已知训练样本,设计损失函数,基于反向传播不断迭代更新每一层的权重参数使得损失函数值向最低点快速收敛。
预测
使用训练好的一组权重,对未知输出的输入进行结果预测。
正向传播推导过程如下:
$$
layer_0 = X \
$$
根据第一层权重计算第一层结果:
$$
layer_1 = sigmoid(layer_0 \times W_1)
$$
根据第二层权重计算当前样本的预测输出:
$$
layer_2(out) = sigmoid(layer_1 \times W_2)) = y’
$$
根据预测结果与实际结果的误差设计损失函数,对损失函数求偏导,随着迭代次数的不断增加。从而从后向前更新权重的过程称为反向传播。
1 | import numpy as np |
1 | import numpy as np |
1、准备数据集,提取特征,作为输入喂给神经网络( Neural Network NN)
2、搭建 NN 结构,从输入到输出(先搭建计算图,再用会话执行)
3、大量特征数据喂给 NN ,迭代优化 NN 参数
4、使用训练好的模型预测和分类
变量初始化:在 sess.run 函数中用 tf.global_variables_initializer() 汇总所有待优化变量。
1 | init_op = tf.global_variables_initializer() |
计算图节点运算:在sess.run函数中写入待运算的节点
1 | sess.run(y) |
用 tf.placeholder占位,在 sess.run 函数中用函数中用 feed_dict喂数据
1 |
|
反向传播 :训练模型参数 ,在所有参数上用梯度下降,使神经网络模型在训练数据上的损失函数最小。
损失函数的计算有很多方法。
用tensorflow 函数表示为loss_mse = tf.reduce_mean(tf.square(y_ - y))
反向传播训练方法: 以减小 loss 值为优化目标 ,有梯度下降 、 adam优化器等优化方法。
这两种优化方法用tensorflow 的函数可以表示为:
1 | train_step=tf.train.GradientDescentOptimizer(learning_rate).minimize(loss) |
tf.train.GradientDescentOptimizer 使用随机梯度下降算法,使参数沿着
梯度的反方向,即总损失减小的方向移动,实现更新参数。
其中,𝐽(𝜃)为损失函数, 𝜃为参数, 𝛼为学习率。
tf.train.AdamOptimizer() 是利用自适应学习率的优化算法, Adam 算法和随机梯度下降算法不同。随机梯度下降算法保持单一的学习率更新所有的参数,学习率在训练过程中并不会改变。而 Adam 算法通过计算梯度的一阶矩估计和二阶矩估计而为不同的参数设计独立的自适应性学习率。
学习率 learning_rate: 决定每次参数更新的幅度。
优化器中都需要一个叫做学习率的参数,使用时如果学习率选择过大会导致待优化的参数在最小值附近波动不收敛的情况,如果学习率选择过小,会出现收敛速度慢的情况。 我们可以选个比较小的值填入, 比如 0.01 、 0.001。
假设有两个分布p(1, 0, 0)与 q(0.8, 0.1, 0.1),则它们在给定样本集上的交叉熵定义如下:
$$
CE(p,q)=−\sum_{}p(x)logq(x)
$$
用Tensorflow 函数表示
1 | ce=-tf.reduce_sum(p * tf.log(tf.clip_by_value(q, 1e-12, 1.0))) |
(1e-12 是为了防止log0出现)
两个神经网络模型解决二分类问题中,已知标准答案为p = (1, 0),第一个神经网络模型预测结果为q1=(0.6, 0.4),第二个神经网络模型预测结果为q2=(0.8, 0.2),判断哪个神经网络模型预测的结果更接近标准答案。
根据交叉熵的计算公式得:
1 | H1((1,0),(0.6,0.4)) = -(1*log0.6 + 0*log0.4) ≈≈ -(-0.222 + 0) = 0.222 |
由于0.222>0.097,所以预测结果y2与标准答案与标准答案y_更接近,y2预测更准确。
总结:
交叉熵刻画了两个概率分布之间的距离, 它是分类问题中使用比较广的一种损失函数。
交叉熵越大,两个概率分布距离越远, 两个概率分布越相异 ;
交叉熵越小,两个概率分布距离越近 ,两个概率分布越相似 。
导入模块,生成模拟数据集;
import
常量定义
生成数据集
前向传播:定义输入、参数和输出
x= y_ =
w1= w2=
a= y=
反向传播:定义损失函数、反向传播方法
loss=
train_step=
生成会话,训练 STEPS 轮
1 | with tf.session() as sess |
1 | #coding utf-8 |
由神经网络的实现 结果,我们可以看出,总共 训练 3000 轮。 每轮从 X 的数据集和 Y 的标签中抽取相对应的从 start 开始到 end 结束个特征值 和 标签 喂入神经网络。 用 sess.run 求出 loss, 每 500 轮打印一次 loss 值 。经过 3000 轮后 我们打印出 最终训练好的 参数 w1 、 w2 。
在计算机中,对于图像存储是通过矩阵来存储的。照片分为黑白和彩色。在图像里我们相应的有灰度图和彩色图。
对于灰度图像,由于只有明暗的区别,因此只需要一个数字就可以表示出不同的灰度。通常用0表示最暗的黑色,255表示最亮的白色,介于0和255之间的整数则表示不同明暗程度的灰色。
对于彩色图像,我们用(R,G,B)三个数字来表示一个颜色,他们表示用红(R)、绿(G)、蓝(B)三种基本颜色叠加后的颜色。对于每种基本颜色,我们用0到255之间的整数表示这个颜色分量的明暗程度。
三个数字中对应的某种基本颜色的数字越大,表示该基本颜色的比例越大,例如(255,0,0)表示纯红色,(0,255,0)表示纯绿色,(135,206,255)表示天蓝色。
一张彩色图片我们可以用一个由整数组成的立方体阵列来表示。我们称这样的立方体排列的数字阵列为三阶张量(tensor)。这个三阶张量的长度与宽度就是图片的分辨率,高度为3.对于数字图像而言,三阶张量的高度也成为通道(channel)数,因此我们说彩色图像有3个通道。矩阵可以看成是高度为1的三阶张量。
在深度学习出现之前,图像特征的设计一直是计算机视觉领域中一个重要的研究课题,在这个领域发展初期,人们手工设计了各种图像特征,这些特征可以描述图像的颜色、边缘、纹理等性质,结合机器学习技术,能解决物体识别和物体检测等实际问题。
既然图像在计算机中可以表示成三阶张量,那么从图像中提取特征便是对这个三阶张量进行运算的过程。其中非常重要的一种运算就是卷积。
卷积是两个变量在某范围内相乘后求和的结果。
卷积运算的物理意义:一个函数(如:单位响应)在另一个函数(如:输入信号)上的加权叠加
有两个离散信号
待卷积信号 X=[1,2,3,0,1,0],
卷积核 H=[1,2,1]
卷积运算 Y = X * H
valid
自始至终卷积核都在“信号内”
最后得到的结果的长度会小于卷积信号的长度
same
卷积核的中心刚好是从待卷积信号的第一个元素“划”到最后一个元素卷积结果的长度和待卷积信号长度一样
full
从卷积核的最后一个元素开始,直到第一个元素到与待卷积信号第一个元素对齐卷积结果的长度是n+m-1
图像数据是5x5的二维矩阵,使用一个3x3的卷积核,从左到右从上到下滑动。滑动的过程称为stride,一个卷积层有两个stride,分别从上到下,从左到右,步长一般设定为1或2。
卷积运算在图像处理中应用十分广泛,许多图像特征提取方法都会用到卷积。以灰度图为例,,我们知道在计算机中,一个灰度图像被表示为一个整数矩阵,如果我们用一个形状较小的矩阵和这个图像矩阵做卷积运算,就可以得到一个新的矩阵,这个新的矩阵可以看作是一副新的图像,换句话说,通过卷积运算,我们可以将原图像变换为一副新的图像。这幅新图像比原图像更清楚地表示了某些性质,我们就可以把它看做原图像的一个特征。
这里用到的小矩阵就称为卷积核(convolution kernel),通常,图像矩阵中的元素都是介于0到255的整数,但卷积核中的元素可以是任意实数。
通过卷积,我们可以从图像中提取边缘特征,在没有边缘的比较平坦的区域,图像的像素值的变化较小,而横向边缘上下两侧的像素值 差异明显,竖向边缘左右两侧的像素也会有较大差别。
如上图,我们用1、0、-1 组成的卷积核与原图像进行卷积运算,可以从图像中提取出竖向边缘。
如上图,我们用三行1,0,-1组成的卷积核,从图中提取出了横向边缘。
事实上,这两个卷积核分别计算了原图像上每个3*3区域内左右像素或者上下像素的差值(为了将运算结果以图像的形式显示出来,我们对运算结果去了绝对值),通过这样的运算,我们就可以从图像上提取不同的边缘特征。
Alex Net 神经网络
上图为Alex Net 神经网络的主体部分,主体部分有5个卷积层和3个全连接层组成
5个卷积层位于网络的最前端,依次对图像进行变换以提取特征;
每个卷积层之后都有一个ReLU非线性激活层完成非线性变换;
第一、二、五个卷积层之后连接有最大池化 层,用以降低特征图的分辨率。
经过5个卷积层以及相连的非线性激活层与池化层之后,特征图被转换为4096维特征向量,在经过两次全连接层和ReLU层变换之后,成为最终的特征向量,在经过一个全连接层和一个softmax归一化指数层之后,就得到了对图片所属类型的预测。
神经网络中的卷积层就是用卷积运算对原始图像或者上一层的特征进行变换的层。在前边的学习中,我们学习了边缘特征的提取,知道一种特定的卷积核可以对图像进行一种特定的变换,从而提取出某种特定的特征,如横向边缘或者竖向边缘。
在一个卷积层中,为了从图像中提取多种形式的特征,我们通常使用多个卷积核对输入的图像进行不同的卷积操作。一个卷积核可以得到一个通道为1的三阶张量,,多个卷积核就可以得到多个通道为1的三阶张量结果。我们把这些结果作为不同的通道组合起来,就可以得到一个新的三阶张量,这个三阶张量的通道数就等于我们使用的卷积核的个数。由于每一个通道都是从原图像中提取的一种特征,我们也将这个三阶张量称为特征图(feature map)。这个特征图就是卷积层的最终输出。
特征图与彩色图像都是三阶张量,也都有若干个通道。因此卷积层不仅可以作用于图像,也可以作用于其他输出的特征图。通常,一个深度神经网络的第一个卷积层会以图像作为输入,而之后的卷积层会以前面的特征图为输入。
通常我们需要在每个卷积层和全连接层后面都连接一个非线性激活层(non-linear activation layer)。为什么呢?其实不管是卷积运算还是全连接层中的运算,他们都是自变量的一次函数,即所谓的线性函数(linear function)。线性函数有一个性质:若干线性计算的符合仍然是线性的。换句话说,如果我们只是将卷积层和全连接层直接堆叠起来,,那么它们对输入图片产生的效果就可以被一个全连接层替代。这样一来,虽然我们堆叠了很多层,但对每一层的变换效果实际上被合并到了一起。而如果我们在每次线性运算后,再进行一次非线性运算,那么每次变换的效果就可以保留。非线性激活层的形式与很多种,它们的基本形式是先选定某种非线性函数,然后对输入特征图或者特征向量的每一个元素应用这种非线性函数,得到输出。
常见的非线性函数有:
l 逻辑函数(logistic function)sigmoid
l 双曲正切函数(hyperbolic tangent function)
l 修正线性函数(rectified linear function)
前两者sigmoid/tanh比较常见于全连接层,后者ReLU常见于卷积层。
激活函数是用来加入非线性因素的,使得神经网络可以任意逼近任何非线性函数,提高经网络对模型的表达能力,解决线性模型所不能解决的问题,这样神经网络就可以应用到众多的非线性模型中。
以ReLU层为例,对于输入的特征向量或特征图,他会将其中小于零的元素变成零,而其他元素的值保持不变,就得到了输出。
因为ReLU的计算非常简单,所以它的计算速度往往比其他非线性激活层快很多,价值其在实际应用中的效果也很好,因此在深度神经网络中被广泛地使用。
在计算卷积时,我们会用卷积核滑过图像或者特征图的每一个像素。如果图像或者特征图的分辨率很多,那么卷积的计算量就会很大。为了解决 这个问题,我们通常在几个卷积层之后插入池化层(pooling layer),已降低特征图的分辨率。
池化层的基本操作步骤如下。首先,我们将特征图按通道分开,得到若干个矩阵。对于每个矩阵,我们将其切割成若干大小相等的正方形小块。如下图,我们将一个44的矩阵分割成4个正方形区块,每个区块的大小为22.接下来,我们对每一个区块取最大值或者平均值,并将结果组成一个新的矩阵。最后,我们将所有通道的结果矩阵按原顺序堆叠起来形成一个三阶张量,这个三阶张量就是池化层的输出。对于每一个区块取最大值的池化层,我们称之为最大池化层(max pooling),而取平均值的池化层成为平均池化层(average pooling layer)。
在经过池化后,特征图的长和宽都会减小到原来的1/2,特征图中的元素数目减小到原来的1/4。通常我们会在卷积层之后增加池化层。这样,在经过若干卷积、池化层的组合之后,在不考虑通道数的情况下,特征图的分辨率就会远小于输入图像的分辨率,大大减小了对计算量和参数数量的需求。
全连接层在整个卷积神经网络中起到“分类器”的作用,即通过卷积、激活函数、池化等深度网络后,再经过全连接层对结果进行识别分类。:
由于神经网络是属于监督学习,在模型训练时,根据训练样本对模型进行训练,从而得到全连接层的权重(如预测字母X的所有连接的权重)
最后计算出来字母X的识别值为0.92,字母O的识别值为0.51,则结果判定为X
“卷积神经网络”(CNN)结构,如下图所示:
softmax实现多分类业务
mnist 数据集 :包含 7 万张 黑底白字手写数字 图片, 其中 55000 张为训练集,5000 张为验证集, 1 0000 张 为测试集 。每张图片大小为 28*28 像素,图片中 纯 黑色像素 值为 0, 纯 白色像素值为 1 。数据集的标签是长度为 10 的一维数组,数组中每个元素索引号表示对应数字出现的概率 。
tf.cast(x,dtype) 函数表示将参数 x 转换为指定数据类型 。
tf.reduce_mean( x,axis 函数表示求取矩阵或张量指定维度的平均值。
tf argmax(x,axis) 函数表示 返回 指定维度 axis 下,参数 x 中 最大值索引号 。
os.path.join 函数表示 把 参数 字符串按照路径命名规则拼接。
字符串 split( 函数表示 按照指定 拆分符 对字符串拆分 返回拆分列表 。
1 | from tensorflow.examples.tutorials.mnist import input_data |
模型上线:
MyModel.py
y = model.predict(x)
假设模型用于预测股票价格,如果已经得到y,如何交给调用者?
设计web服务:
url: http://172.10.0.13:8000/moviereco/{ID}
method: get
params: none
return: {status:200, data:[movie1id, movie2id, …….], msg:’no msg’}
]]>2005年发布, 采用Python语言编写的开源web框架
早期的时候Django主做新闻和内容管理的
一个重量级的 Python Web框架,Django 配备了常用的大部分组件
Django的用途
Django的版本
Django的官网
django-docs-1.11-en.zip
django-docs-1.11-en/index.html
1 | import django |
$ sudo pip3 install django
安装django的最新版本$ sudo pip3 install django[==版本]
安装django的指定版本$ sudo pip3 install django==1.11.8
$ tar -xvf Django-1.11.8.tar.gz
$ cd Django-1.11.8
$ sudo python3 setup.py install
pip3 download -d /home/weimz/django_packs django==1.11.8
$ django-admin startproject 项目名称
如:
运行
1 | cd mysite1 |
示例:
1 | django-admin startproject mysite1 |
项目目录结构解析:
manage.py
包含项目管理的子命令, 如:python3 manage.py runserver
启动服务python3 manage.py startapp
创建应用python3 manage.py migrate
数据库迁移...
__init__.py
wsgi.py
urls.py
settings.py
settings.py
文件介绍
BASE_DIR
DEBUG
调试模式
(用于开发中)生产环境中
(不启用调试)ALLOWED_HOSTS
127.0.0.1
, localhost
, ‘[::1]’ 能访问本项目python3 manage.py runserver 0.0.0.0:5000
# 指定网络设备所有主机都可以通过5000端口访问(需加ALLOWED_HOSTS = ['*']
) INSTALLED_APPS
MIDDLEWARE
TEMPLATES
DATABASES
LANGUAGE_CODE
"en-us"
"zh-Hans"
TIME_ZONE
"UTC"
"Asia/Shanghai"
ROOT_URLCONF
ROOT_URLCONF = 'mysite1.urls'
注: 此模块可以通过
from django.conf import settings
导入和使用
url 即统一资源定位符 Uniform Resource Locator
作用:
说明:
URL的一般语法格式为:
1 | protocol :// hostname[:port] / path [?query][#fragment] |
如:
1 | http://tts.tmooc.cn/video/showVideo?menuId=657421&version=AID201908#subject |
说明:
protocol(协议)
HTTP://
HTTPS://
file:///
hostname(主机名)
port(端口号)
path(路由地址)
query(查询)
fragment(信息片断)
注: [] 代表其中的内容可省略
1 | def xxx_view(request[, 其它参数...]): |
views.py
1 | # file : <项目名>/views.py |
ROOT_URLCONF
指定了主路由配置列表urlpatterns的文件位置1 | # file : <项目名>/urls.py |
urlpatterns 是一个路由-视图函数映射关的列表,此列表的映射关系由url函数来确定
from django.conf.urls import url
每个正则表达式前面的r表示
'\'
不转义的原始字符串
提示: 主面路由的正则是
r'^$'
在视图函数内,可以用正则表达式分组 ()
提取参数后用函数位置传参传递给视图函数
一个分组表示一个参数,多个参数需要使用多个分组,并且使用个/隔开
练习:
定义一个路由的格式为:
从路由中提取数据,做相应的操作后返回给浏览器
如:
1 | 输入: 127.0.0.1:8000/100/add/200 |
(?P<name>pattern)
提取参数后用函数位置传参传递给视图函数1 | # file : <项目名>/urls.py |
+
号添加一个自己的配置序号 | 方法 | 描述 |
---|---|---|
1 | GET | 请求指定的页面信息,并返回实体主体。 |
2 | HEAD | 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头 |
3 | POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 |
4 | PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 |
5 | DELETE | 请求服务器删除指定的页面。 |
6 | CONNECT | HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 |
7 | OPTIONS | 允许客户端查看服务器的性能。 |
8 | TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含HTTP状态码的信息头用以响应浏览器的请求。
HTTP状态码的英文为HTTP Status Code。
下面是常见的HTTP状态码:
HTTP状态码分类
HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为5种类型:
分类 | 分类描述 |
---|---|
1** | 信息,服务器收到请求,需要请求者继续执行操作 |
2** | 成功,操作被成功接收并处理 |
3** | 重定向,需要进一步的操作以完成请求 |
4** | 客户端错误,请求包含语法错误或无法完成请求 |
5** | 服务器错误,服务器在处理请求的过程中发生了错误 |
Django中的响应对象HttpResponse:
HttpResponse(content=响应体, content_type=响应体数据类型, status=状态码)
'text/html'
(默认的,html文件)'text/plain'
(纯文本)'text/css'
(css文件)'text/javascript'
(js文件)'multipart/form-data'
(文件提交)'application/json'
(json传输)'application/xml'
(xml文件)注: 关键字MIME(Multipurpose Internet Mail Extensions)是指多用途互联网邮件扩展类型。
HttpResponse 子类
类型 | 作用 | 状态码 |
---|---|---|
HttpResponseRedirect | 重定响 | 301 |
HttpResponseNotModified | 未修改 | 304 |
HttpResponseBadRequest | 错误请求 | 400 |
HttpResponseNotFound | 没有对应的资源 | 404 |
HttpResponseForbidden | 请求被禁止 | 403 |
HttpResponseServerError | 服务器错误 | 500 |
GET请求方式中可以通过查询字符串(Query String)将数据传递给服务器
URL 格式: xxx?参数名1=值1&参数名2=值2...
http://127.0.0.1:8000/page1?a=100&b=200
服务器端接收参数
1 | if request.method == 'GET': |
1 | request.GET['参数名'] |
<a href="地址?参数=值&参数=值">
1 | <form method='get' action="/user/login"> |
一般查询字符串的大小会受到浏览器的的限制(不建议超过2048字节)
PyCharm社区版调试Django程序配置
客户端通过表单等POST请求将数据传递给服务器端,如:
1 | <form method='post' action="/user/login"> |
服务器端接收参数
1 | if request.method == 'POST': |
使用post方式接收客户端数据
1 | request.POST['参数名'] |
取消csrf验证,否则Django将会拒绝客户端发来的POST请求
1 | MIDDLEWARE = [ |
1 | <input name='xxx'> |
1 | <form action="/page1" method="POST"> |
客户端通过表单等POST请求将数据传递给服务器端,如:
1 | <form method='post' action="/user/login"> |
服务器端接收参数
1 | if request.method == 'POST': |
使用post方式接收客户端数据
1 | request.POST['参数名'] |
取消csrf验证,否则Django将会拒绝客户端发来的POST请求
1 | MIDDLEWARE = [ |
1 | <input name='xxx'> |
1 | <form action="/page1" method="POST"> |
MVC 设计模式
MTV 模式
MTV 代表 Model-Template-View(模型-模板-视图) 模式。这种模式用于应用程序的分层开发
创建步骤
创建应用的子命令
Django应用的结构组成
migrations
文件夹__init__.py
admin.py
apps.py
models.py
tests.py
views.py
配置安装应用
1 | # file : settings.py |
1 | INSTALLED_APPS = [ |
模块
app命字/url模块名.py
文件件里必须有urlpatterns 列表
使用前需要使用from django.conf.urls import include
导入此函数
安装 pymysql包
$ sudo pip3 install pymysql
$ sudo pip3 install mysqlclient
创建 和 配置数据库
创建数据库
create database 数据库名 default charset utf8 collate utf8_general_ci;
1 | create database mywebdb default charset utf8 collate utf8_general_ci; |
数据库的配置
1 | # file: settings.py |
1 | DATABASES = { |
关于数据为的SETTING设置
ENGINE
1 | 'django.db.backends.mysql' |
NAME
'NAME': 'mywebdb'
USER
'USER':'root'
PASSWORD
'PASSWORD':'123456'
HOST
'HOST':'127.0.0.1'
PORT
'PORT':'3306'
添加 mysql 支持
$ sudo pip install pymysql
1 | import pymysql |
1 | python3 manage.py startapp bookstore |
1 | # file : bookstore/models.py |
1 | # file : setting.py |
python3 manage.py makemigrations
python3 manage.py migrate
bookstore/migrations/0001_initial.py
并进行迁移 1 | python3 manage.py makemigrations |
编写模型类Models
django.db.models.Model
1 | from django.db import models |
模型类名是数据表名的一部分,建议类名首字母大写
字段名又是当前类的类属性名,此名称将作为数据表的字段名
字段类型用来映射到数据表中的字段的类型
字段选项为这些字段提供附加的参数信息
字段类型
BooleanField()
CharField()
DateField()
DateTimeField()
DecimalField()
数据库类型:decimal(x,y)
编程语言中:使用小数表示该列的值
在数据库中:使用小数
参数:
示例:
1 | money=models.DecimalField( |
FloatField()
EmailField()
IntegerField()
URLField()
ImageField()
1 | image=models.ImageField( |
TextField()
字段选项FIELD_OPTIONS
1 | # 创建一个属性,表示用户名称,长度30个字符,必须是唯一的,不能为空,添加索引 |
当执行 $ python3 manage.py makemigrations
出现如下迁移错误时的处理方法
1 | $ python3 manage.py makemigrations |
1 | $ python3 manage.py makemigrations |
1 | class Book(models.Model): |
1 | class Book(models.Model): |
default=XXX
的缺省值(推荐使用)数据库的迁移文件混乱的解决办法
__init__.py
除外)1 | class MyModel(models.Model): |
1 | obj = MyModel(属性=值,属性=值) |
Django Shell
它能够在交互模式用项目工程的代码执行相应的操作1 | python3 manage.py shell |
方法 | 说明 |
---|---|
all() | 查询全部记录,返回QuerySet查询对象 |
get() | 查询符合条件的单一记录 |
filter() | 查询符合条件的多条记录 |
exclude() | 查询符合条件之外的全部记录 |
… |
all()方法
1 | from bookstore import models |
在模型类中定义 def __str__(self):
方法可以将自定义默认的字符串
1 | class Book(models.Model): |
查询返回指定列(字典表示)
1 | from bookstore import models |
查询返回指定列(元组表示)
元组
1 | from bookstore import models |
排序查询
1 | from bookstore import models |
根据条件查询多条记录
1 | MyModel.objects.filter(属性1=值1, 属性2=值2) |
Books.objects.filter(price=20, pub="清华大学出版社")
返回定价为20 且
出版社为”清华大学出版社”的全部图书1 | # 查询书中出版社为"清华大学出版社"的图书 |
1 | # 查询作者中年龄大于30 |
__exact
: 等值匹配 1 | Author.objects.filter(id__exact=1) |
__contains
: 包含指定值 1 | Author.objects.filter(name__contains='w') |
__startswith
: 以 XXX 开始__endswith
: 以 XXX 开始__gt
: 大于指定值 1 | Author.objects.filer(age__gt=50) |
__gte
: 大于等于__lt
: 小于__lte
: 小于等于__in
: 查找数据是否在指定范围内1 | Author.objects.filter(country__in=['中国','日本','韩国']) |
__range
: 查找数据是否在指定的区间范围内 1 | # 查找年龄在某一区间内的所有作者 |
示例
1 | MyModel.objects.filter(id__gt=4) |
练习:
Author.objects.filter(age__gte=85)
Author.objects.filter(name__startswith='王')
Author.objects.filter(email__contains='wc')
条件
的 全部的数据集清华大学出版社,定价大于50
以外的全部图书1 | books = models.Book.objects.exclude(pub="清华大学出版社", price__gt=50) |
1 | from bookstore import models |
1 | # 将 id大于3的所有图书价格定为0元 |
1 | try: |
1 | # 删除全部作者中,年龄大于65的全部信息 |
不带分组聚合
django.db.models
from django.db.models import *
1 | # 得到所有书的平均价格 |
分组聚合
分组聚合是指通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合。
语法:
用法步骤:
1 | pub_set = models.Book.objects.values('pub') |
1 | pub_count_set = pub_set.annotate(myCount=Count('pub')) |
示例:
1 | def test_annotate(request): |
作用:
用法
from django.db.models import F
语法:
1 | from django.db.models import F |
说明:
1 | models.Book.objects.all().update(market_price=F('market_price')+10) |
1 | from django.db.models import F |
|
、 逻辑非 ~
等操作时可以借助于 Q对象进行操作1 | models.Book.objects.filter(Q(price__lt=20)|Q(pub="清华大学出版社")) |
from django.db.models import Q
1 | from django.db.models import Q |
1 | from django.db.models import Q |
MyModel.objects.raw(sql语句)
MyModel.objects.raw('sql语句')
1 | books = models.Book.objects.raw('select * from bookstore_book') |
使用django中的游标cursor对数据库进行 增删改操作
在Django中可以使用 如UPDATE,DELETE等SQL语句对数据库进行操作。
在Django中使用上述非查询语句必须使用游标进行操作
使用步骤:
from django.db import connection
1 | from django.db import connection |
1 | # 用SQL语句将id 为 10的 书的出版社改为 "XXX出版社" |
$ python3 manage.py createsuperuser
1 | python3 manage.py createsuperuser |
/admin
后台管理界中显示和管理,需要将自己的类注册到后台管理界面admin.site.register(自定义模型类)
方法进行注册1 | from . import models |
1 | from django.contrib import admin |
1 | # file: bookstore/admin.py |
XXXX object
类型的记录,不便于阅读和判断def __str__(self):
方法解决显示问题,如:1 | class Book(models.Model): |
作用:
说明:
django.contrib.admin
里的 ModelAdmin
类模型管理器的使用方法:
<应用app>/admin.py
里定义模型管理器类 1 | class XXXX_Manager(admin.ModelAdmin): |
1 | from django.contrib import admin |
1 | # file : bookstore/admin.py |
模型管理器类ModelAdmin中实现的高级管理功能
1 | title = models.CharField( |
1 | class Book(models.Model): |
1 | class A(model.Model): |
1 | # file : xxxxxxxx/models.py |
1 | from . import models |
1 | # 通过 wife 找 author |
实例对象.引用类名(小写)
,如作家的反向引用为作家对象.wife
1 | # 通过 author.wife 关联属性 找 wife,如果没有对应的wife刚触发异常 |
用法语法
1 | class A(model.Model): |
外键类ForeignKey
1 | ForeignKey(to, on_delete, **options) |
**options
可以是常用的字段选项如:示例
有二个出版社对应五本书的情况.
清华大学出版社
有书
北京大学出版社
有书
1 | # file: one2many/models.py |
创建一对多的对象
1 | # file: xxxxx/views.py |
查询:
1 | # 通过一本书找到对应的出版社 |
1 | # 通过出版社查询对应的书 |
数据查询
1 | 通过 publisher 属性查询即可 |
1 | Django会在Publisher中增加一个属性来表示对对应的Book们的查询引用 |
1 | 属性 = models.ManyToManyField(MyModel) |
1 | class Author(models.Model): |
1 | book.authors.all() -> 获取 book 对应的所有的author的信息 |
1 | author.book_set.all() |
1 | class Author(models.Model): |
1 | from django.http import HttpResponse |
1 | mysql> select * from many2many_author; |
cookies是保存在客户端浏览器上的存储空间,通常用来记录浏览器端自己的信息和当前连接的确认信息
cookies 在浏览器上是以键-值对的形式进行存储的,键和值都是以ASCII字符串的形存储(不能是中文字符串)
cookies 的内部的数据会在每次访问此网址时都会携带到服务器端,如果cookies过大会降低响应速度
在Django 服务器端来设置 设置浏览器的COOKIE 必须通过 HttpResponse 对象来完成
HttpResponse 关于COOKIE的方法
添加、修改COOKIE
删除COOKIE
Django中的cookies
1 | from django.http import HttpResponse |
1 | resp = HttpResponse() |
1 | from django.shortcuts import render |
1 | value = request.COOKIES.get('cookies名', '没有值!') |
Application
>> Storage
>> Cookies
查看和操作浏览器端所有的 Cookies 值cookies 示例
1 | # 为浏览器添加键为 my_var1,值为123,过期时间为1个小时的cookie |
1 | # 为浏览器添加键为 my_var1,修改值为456,过期时间为2个小时的cookie |
1 | # 删除浏览器键为 my_var1的cookie |
1 | # 获取浏览器中 my_var变量对应的值 |
什么是session
session又名会话控制,是在服务器上开辟一段空间用于保留浏览器和服务器交互时的重要数据
session的起源
实现方式
Django启用Session
1 | INSTALLED_APPS = [ |
1 | MIDDLEWARE = [ |
session的基本操作:
request.session['KEY'] = VALUE
VALUE = request.session['KEY']
VALUE = request.session.get('KEY', 缺省值)
del request.session['KEY']
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2
import django.conf.global_settings
注: 当使用session时需要迁移数据库,否则会出现错误
1 | $ python3 manage.py makemigrations |
什么是session
session又名会话控制,是在服务器上开辟一段空间用于保留浏览器和服务器交互时的重要数据
session的起源
实现方式
Django启用Session
1 | INSTALLED_APPS = [ |
1 | MIDDLEWARE = [ |
session的基本操作:
request.session['KEY'] = VALUE
VALUE = request.session['KEY']
VALUE = request.session.get('KEY', 缺省值)
del request.session['KEY']
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2
import django.conf.global_settings
注: 当使用session时需要迁移数据库,否则会出现错误
1 | $ python3 manage.py makemigrations |
当用户登陆时,可以在session添加一个键 ‘user’ 绑定一个当前登陆用户的信息,如果在 'user' in request.session
成立,即当前用户为登陆状态,可以从request.session['user']
获限登陆信息。否则为没有登陆状态
登陆逻辑处理
1 | # 在session内保存当前用的名称和id |
退出登陆的逻辑处理
1 | if 'user' in request.session: |
判断用户是否登陆:
1 | if 'user' in request.session: |
练习:
实现用户登陆、退出功能
说明:
要求 :
python3 manage.py startapp user
python3 manage.py startapp index
模型类
1 | class User(models.Model): |
登陆设计规范(在user应用中写代码)
路由正则 | 视图函数 | 模板位置 | 说明 |
---|---|---|---|
/user/login | def login_view(request): | templates/user/login.html | 用户登陆 |
/user/logout | def logout_view(request | 无 | 退出用户登陆 |
主页设计规范(在index应用中写代码)
路由正则 | 视图函数 | 模板位置 | 说明 |
---|---|---|---|
/ | def index_view(request): | templates/index/index.html | 主页 |
中间件是 Django 请求/响应处理的钩子框架。它是一个轻量级的、低级的“插件”系统,用于全局改变 Django 的输入或输出。
每个中间件组件负责做一些特定的功能。例如,Django 包含一个中间件组件 AuthenticationMiddleware,它使用会话将用户与请求关联起来。
他的文档解释了中间件是如何工作的,如何激活中间件,以及如何编写自己的中间件。Django 具有一些内置的中间件,你可以直接使用。它们被记录在 built-in middleware reference 中。
中间件类:
django.utils.deprecation.MiddlewareMixin
类def process_request(self, request):
执行视图之前被调用,在每个请求上调用,返回None或HttpResponse对象 def process_view(self, request, callback, callback_args, callback_kwargs):
调用视图之前被调用,在每个请求上调用,返回None或HttpResponse对象def process_response(self, request, response):
所有响应返回浏览器之前被调用,在每个请求上调用,返回HttpResponse对象def process_exception(self, request, exception):
当处理过程中抛出异常时调用,返回一个HttpResponse对象def process_template_response(self, request, response):
在视图刚好执行完毕之后被调用,在每个请求上调用,返回实现了render方法的响应对象编写中间件类:
1 | #file : middleware/mymiddleware.py |
1 | # file : settings.py |
跨站请求伪造攻击
说明:
作用:
解决方案:
django.middleware.csrf.CsrfViewMiddleware
的中间件1 | 需要在表单中增加一个标签 |
Form表单类示意:
1 | class MySearch(forms.Form): |
1 | <label for="id_input_text">请输入内容:</label> |
使用 forms 模块的步骤
from django import forms
1 | class Form表单类名(forms.Form): |
1 | form1 = FormName() |
1 | {{ form1.as_p }} |
forms.Form 示例:
1 | from django import forms |
字段参数
在模板中解析form对象
1 | form = XXXForm() |
1 | {% for field in form %} |
将 form 中的每个属性(控件/文本)都使用p标记包裹起来再显示
1 | 将 form 中的每个属性(控件/文本)都使用li标记包裹起来再显示 |
1 | 将 form 中的每个属性(控件/文本)都使用tr标记包裹起来再显示 |
widget名称 | 对应和type类值 |
---|---|
TextInput | type=’text’ |
PasswordInput | type=’password’ |
NumberInput | type=”number” |
EmailInput | type=”email” |
URLInput | type=”url” |
HiddenInput | type=”hidden” |
CheckboxInput | type=”checkbox” |
CheckboxSelectMultiple | type=”checkbox” |
RadioSelect | type=”radio” |
Textarea | textarea标记 |
Select | select标记 |
SelectMultiple | select multiple 标记 |
小部件的使用
1 | 属性 = forms.CharField() #无预选值使用 |
1 | upwd = forms.CharField( |
django form 提供表单和字段验证
当在创建有不同的多个表单需要提交的网站时,用表单验证比较方便验证的封装
当调用form.is_valid() 返回True表示当前表单合法,当返回False说明表单验证出现问题
验证步骤:
验证方法:
文档参见https://docs.djangoproject.com/en/1.11/ref/forms/validation/
验证示例
1 | from django import forms |
django.core.paginator
模块中。对象的构造方法
Paginator属性
Paginator方法
Paginator异常exception
创建对象
Paginator对象的page()方法返回Page对象,不需要手动构造
Page对象属性
Page对象方法
说明:
参考文档https://docs.djangoproject.com/en/1.11/topics/pagination/
1 | from django.core.paginator import Paginator |
1 | <html> |
<form>
中文件上传时必须有带有enctype="multipart/form-data"
时才会包含文件内容数据。<input type="file" name="xxx">
标签上传文件xxx
对应request.FILES['xxx']
对应的内存缓冲文件流对象。可通能过request.FILES['xxx']
返回的对象获取上传文件数据file=request.FILES['xxx']
file 绑定文件流对象,可以通过文件流对象的如下信息获取文件数据上传文件的表单书写方式
1 | <!-- file: index/templates/index/upload.html --> |
在setting.py 中设置一个变量MEDIA_ROOT 用来记录上传文件的位置
1 | # file : settings.py |
在当前项目文件夹下创建 static/files
文件夹
1 | mkdir -p static/files |
添加路由及对应的处理函数
1 | # file urls.py |
上传文件的视图处理函数
1 | # file views.py |
Django带有一个用户认证系统。 它处理用户账号、组、权限以及基于cookie的用户会话。
作用:
文档参见
User模型类
from django.contrib.auth.models import User
默认user的基本属性有:
属性名 | 类型 | 是否必选 |
---|---|---|
username | 用户名 | 是 |
password | 密码 | 是 |
邮箱 | 可选 | |
first_name | 名 | |
last_name | 姓 | |
is_superuser | 是否是管理员帐号(/admin) | |
is_staff | 是否可以访问admin管理界面 | |
is_active | 是否是活跃用户,默认True。一般不删除用户,而是将用户的is_active设为False。 | |
last_login | 上一次的登录时间 | |
date_joined | 用户创建的时间 |
数据库表现形式
1 | mysql> use myauth; |
1 | from django.contrib.auth import models |
1 | from django.contrib.auth import models |
1 | from django.contrib.auth import models |
1 | from django.contrib.auth import models |
1 | from django.contrib.auth import models |
python3 manage.py runserver
方法启动服务器$ sudo apt install python3
$ pip3 freeze > package_list.txt
$ pip3 install -r package_list.txt
使用 python manage.py runserver
通常只在开发和测试环境中使用。
当开发结束后,完善的项目代码需要在一个高效稳定的环境中运行,这时可以使用uWSGI
uWSGI是WSGI的一种,它可以让Django、Flask等开发的web站点运行其中.
安装uWSGI
1 | sudo pip3 install uwsgi |
1 | pip3 download uwsgi |
uwsgi-2.0.18.tar.gz
1 | tar -xzvf uwsgi-2.0.18.tar.gz |
配置uWSGI
项目文件夹/uwsgi.ini
1 | [uwsgi] |
uWSGI的运行管理
1 | cd 项目文件夹 |
1 | cd 项目文件夹 |
测试:
Nginx是轻量级的高性能Web服务器,提供了诸如HTTP代理和反向代理、负载均衡、缓存等一系列重要特性,在实践之中使用广泛。
C语言编写,执行效率高
nginx 作用
原理:
客户端请求nginx,再由nginx 请求 uwsgi, 运行django下的python代码
ubuntu 下 nginx 安装
$ sudo apt install nginx
nginx 配置
1 | # 在server节点下添加新的location项,指向uwsgi的ip与端口。 |
nginx服务控制
1 | sudo /etc/init.d/nginx start|stop|restart|status |
通过 start,stop,restart,status 可能实现nginx服务的启动、停止、重启、查扑克状态等操作
修改uWSGI配置
项目文件夹/uwsgi.ini
下的Http通信方式改为socket通信方式,如:1 | [uwsgi] |
1 | sudo uwsgi --stop uwsgi.pid |
1 |
|
修改配置文件后需要重新启动 nginx 服务
1 | from django.http import Http404 |
1 | docker run -d -p 4000:4000 --name hexo \ |
1 | docker exec -it hexo bash |
1 | npm install hexo-cli -g |
如果初始化博客
1 | mkdir hexo |
hexo init 自动把资源文件下载好;会等待一段时间
如果已经有博客
1 | cd Blog |
1 | nohup hexo s & |
1 | yum install -y nodejs |
]]>安装时间会很久,闹心等待。
卧槽,无情。安装失败了
移除旧版本
1.查询安装过的包
1 | yum list installed | grep docker |
2.删除安装的软件包
1 | yum -y remove docker-engine.x86_64 |
3.删除镜像/容器等
1 | rm -rf /var/lib/docker |
安装一些必要的系统工具:
1 | sudo yum install -y yum-utils device-mapper-persistent-data lvm2 |
添加软件源信息:
1 | sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo |
更新 yum 缓存
1 | sudo yum makecache fast |
安装 Docker-ce:
1 | #安装 Docker |
启动 Docker 后台服务:
1 | sudo systemctl start docker |
测试运行 hello-world
1 | docker run hello-world |
出现hello world
就证明安装正常了
安装nginx
前,我们现在本地建立几个文件,用于存放nginx
的配置文件等
1 | # 切换到服务器根目录 |
上面的 dockerData
可以换成自己喜欢的名字
1 | dockerData/nginx 用于存放 docker 下 nginx 自定义文件 |
启动 nginx
1 | docker run --name nginx -p 80:80 -d --rm nginx |
如果你没有备案,可以将上面的80:80
换成8081:80
,因为这个东西一会儿也要删掉,所以加上--rm
参数,命令执行玩后通过 docker ps
查看 nginx
是否在运行,在运行的情况下访问你的域名加端口号查看是否正常安装,80
直接省略。如下表示访问成功
导出配置文件
1 | docker cp nginx:/etc/nginx/nginx.conf /dockerData/nginx/conf/nginx.conf 导出配置文件 nginx.conf |
执行 docker stop nginx
,会自动删除现在的 nginx
容器,然后执行如下命令重新启动一个 nginx
容器
1 | docker run -d -p 80:80 --name nginx \ |
1 | -v /dockerData/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \ 挂载配置文件nginx.conf |
访问你的域名,你会发现报错了
这时我们可以前往 /dockerData/nginx/logs
下查看日志文件
1 | 2019/08/05 14:57:54 [error] 6#6: *3 directory index of "/usr/share/nginx/html/" is forbidden, client: 121.32.33.217, server: localhost, request: "GET / HTTP/1.1", host: "www.jinjianh.com" |
因为 /usr/share/nginx/html/
被挂载到了服务器上面的/dockerData/nginx/www目录下,原来的欢迎页面在 dockerData/nginx/www
是没有的,所有就报错了,这里我们随便建一个。
1 | # 打开项目文件 |
再次访问我们的域名就可以看到我们刚刚写的h1标签内容
]]>表示颜色时,有两种形式,一种为索引色(Index Color),一种为直接色(Direct Color)
图片格式中一般分为静态图和动态图
JPG:是支持 JPEG( 一种有损压缩方法)标准中最常用的图片格式。采用点阵图。常见的是使用 24 位的颜色深度的直接色(不支持透明)。
PNG:是支持无损压缩的图片格式。采用点阵图。PNG 有 5 种颜色选项:索引色、灰度、灰度透明、真彩色(24 位直接色)、真彩色透明(32 位直接色)。
WebP:是同时支持有损压缩和无所压缩的的图片格式。采用点阵图。支持 32 位直接色。移动端支持情况如下:
系统 | 原生 | WebView | 浏览器 |
---|---|---|---|
iOS | 第三方库支持 | 不支持 | 支持 |
Android | 4.3后支持完整功能 | 支持 | 支持 |
系统 | 原生 | WebView | 浏览器 |
---|---|---|---|
iOS | 支持 | 支持 | 支持 |
Android | 第三方库支持 | 不支持 | 不支持 |
系统 | 原生 | WebView | 浏览器 |
---|---|---|---|
iOS | 第三方库支持 | 不支持 | 不支持 |
Android | 第三方库支持 | 不支持 | 不支持 |
而由于一般项目需要兼容三端(iOS、Android、Web 的关系),最简单就是支持 JPG、PNG、GIF 这三种通用的格式。所以本文暂不讨论其余图片格式的压缩。
根据我的了解,画了一下 iOS&Android 图片处理架构。iOS 这边,也是可以直接调用底层一点的框架的。
本文 iOS 端处理图片主要用 ImageIO 框架,使用的原因主要是静态图动态图 API 调用保持一致,且不会因为 UIImage 转换时会丢失一部分数据的信息。
ImageIO 主要提供了图片编解码功能,封装了一套 C 语言接口。在 Swift 中不需要对 C 对象进行内存管理,会比 Objective-C 中使用方便不少,但 api 结果返回都是 Optional(实际上非空),需要用 guard/if
,或者 !进行转换。
1. 创建 CGImageSourceCGImageSource
相当于 ImageIO 数据来源的抽象类。通用的使用方式 CGImageSourceCreateWithDataProvider:
需要提供一个 DataProvider,可以指定文件、URL、Data 等输入。也有通过传入 CFData 来进行创建的便捷方法 CGImageSourceCreateWithData:
。方法的第二个参数 options 传入一个字典进行配置。根据 Apple 在 WWDC 2018 上的 Image and Graphics Best Practices 上的例子,当不需要解码仅需要创建 CGImageSource
的时候,应该将 kCGImageSourceShouldCache
设为 false。
2. 解码得到 CGImage
用 CGImageSourceCreateImageAtIndex:
或者 CGImageSourceCreateThumbnailAtIndex:
来获取生成的 CGImage
,这里参数的 Index 就是第几帧图片,静态图传入 0 即可。
1. 创建 CGImageDestinationCGImageDestination
相当于ImageIO
数据输出的抽象类。通用的使用方式 CGImageDestinationCreateWithDataConsumer:
需要提供一个 DataConsumer
,可以置顶 URL、Data 等输入。也有通过传入 CFData 来进行创建的便捷方法 CGImageDestinationCreateWithData:
,输出会写入到传入的Data
中。方法还需要提供图片类型,图片帧数。
2. 添加 CGImage
添加 CGImage
使用CGImageDestinationAddImage:
方法,动图的话,按顺序多次调用就行了。
而且还有一个特别的 CGImageDestinationAddImageFromSource:
方法,添加的其实是一个 CGImageSource
,有什么用呢,通过 options
参数,达到改变图像设置的作用。比如改变 JPG 的压缩参数,用上这个功能后,就不需要转换成更顶层的对象(比如 UIImage
),减少了转换时的编解码的损耗,达到性能更优的目的。
3. 进行编码
调用 CGImageDestinationFinalize:
,表示开始编码,完成后会返回一个Bool
值,并将数据写入 CGImageDestination
提供的 DataConsumer 中。
位图占用的空间大小,其实就是像素数量 x 单像素占用空间 x 帧数。所以减小图片空间大小,其实就从这三个方向下手。其中单像素占用空间,在直接色的情况下,主要和色彩深度相关。在实际项目中,改变色彩深度会导致图片颜色和原图没有保持完全一致,笔者并不建议对色彩深度进行更改。而像素数量就是平时非常常用的图片分辨率缩放。除此之外,JPG 格式还有特有的通过指定压缩系数来进行有损压缩。
JPG:压缩系数 + 分辨率缩放 + 色彩深度降低
PNG: 分辨率缩放 + 降低色彩深度
GIF:减少帧数 + 每帧分辨率缩放 + 减小调色盘
减少图片占用内存空间
图片的质量压缩 降低图片质量(大小)。
原理
通过算法扣掉(同化)了 图片中的一些某个点附近相近的像素,达到降低质量 减少 文件大小的目的。
他其实只能 实现对 file 的影响,对加载这个图片出来的bitmap 内存是无法节省的 ,还是那么大。 因为 bitmap 在内存中的大小是按照 像素 计算的 ,也就是width * height ,对于质量压缩,并不会改变图片的真实的像素(像素大小不会变)。
就是按照一定的倍数对图片减少单位尺寸的像素值。
原理
通过减少单位尺寸的像素值,真正意义上的降低像素值。
App分享卡片到微信,微信点击打开卡片连接,点击【按钮】直接唤起App;
微信内置浏览器已禁止使用外部url scheme跳转至其他app。
官方没有给具体的链接,但是就不能跳转。
引导打开浏览器,iOS在Safari中打开,Android在浏览器中打开
基于
universal link
和scheme
技术相对简单,虽说达到了跳转目的,但过程过于繁琐
集成第三方程序(无需花时间去维护)
使用
DeepLink
技术,突破微信跳转问题
技术门槛高,但第三方帮我们解决了。
—国内⾸首家企业级深度链接服务提供商,致⼒于帮助APP解决 ⽤用户增⻓(拉新、拉活、留留存、转化等)和流量变现等问题。
唤醒指定页面 | 场景还原 | 沉默用户唤醒 |
---|---|---|
<video src=”https://www.linkedme.cc/img/LInkPage.mp4"controls="controls" width=180px> | <video src=”https://www.linkedme.cc/img/li.mp4"controls="controls" width=180px> | <video src=”https://www.linkedme.cc/img/vancl.mp4"controls="controls" width=180px> |
唤醒指定页面
今年极光收购了魔窗,产品名称是极光魔链。可以快速集成,但是稳定性待测试。
iOS
微信:可以完美唤起App以及指定页面App;(打开App的时候显示的是第三方应用)
Android
微信:当App在后台时可以直接唤起App(Android Q除外,它跟杀死唤起机制一样);如果App杀死之后,需要浏览器唤起。
小程序可以打开App(类似Bilibili)
官方文档
通过自己调研开发DeepLink(参考一点资讯)
]]>超级复杂,需要移动端,Web,服务器;三端联调。预计3人两周到三周工作量。阅读英文文档,需要懂架构的人。
实际测试,也并没有100%全部跳转成功
该脚本是需要所有信息更新完成才能触发
1 | # 请求 |
按照摘要描述的,这本不是一个事故,为什么叫事故呢?
由于此版本是强更版本,移动端,Web,后台,全部需要统一上线;最后其他端都上线了,就iOS没有更新,导致线上宕机25h20m,并且收到了很多差评。
时间 | 操作 |
---|---|
09:00 | 发布停服更新公告 |
10:00 | 停服,导数据,代码合并Master上线 |
10:10 | Web合并Master上线 |
11:00 | 释放Android包同时配置强更 |
11:10 | iOS发布版本同时配置强更 |
14:00 | 开服 |
按照这个流程,iOS这边只需要静悄悄的等待上线就好了,按照正常的时间,iOS这边是半个小时到2个小时,这个时间肯定是够够的。但是,就怕这个但是,我们等了25h20m。
时间 | 操作状态 | 备注 |
---|---|---|
2019-09-22 22:39 | 2.1.0准备提交 | |
2019-09-23 13:19 | 提交审核 | |
2019-09-24 04:56 | 正在审核 | |
2019-09-24 05:55 | 等待开发人员发布 | |
2019-09-25 11:10 | 发布此版本 | |
2019-09-25 16:43 | 更改价格及发布地区 | 刷新状态 |
2019-09-25 18:15 | 给Apple发邮件 | |
2019-09-26 10:00 | 下架应用 | 刷新状态 |
2019-09-26 10:00 | 发布版本 | 刷新状态 |
2019-09-26 10:36 | Apple回复邮件 | 可供销售后,最多等待24小时 |
2019-09-26 11:03 | 2.1.1准备提交 | 只为尝试提交让其刷新状态,并没有用 |
2019-09-26 11:13 | Call Apple | 回复说我们重新上架,还要等最多24小时 |
2019-09-26 12:14 | 商店封面图更新 | |
2019-09-26 12:16 | 商店可以刷到最新包 | |
2019-09-26 12:30 | 商店文案更新 | |
2019-09-26 12:38 | 发布上线通知 |
下面是Apple的邮件回复
优化上线流程,iOS和Android应该提前发布,然后再走后端上线流程。
]]>如果后台的流程后置的话,一定要保障其自动化部署和迁移脚本能够顺利进行。
1 | /// 判断是否设置了代理 |
脚本后加&
1 | python ***.py & |
此方法在ssh连接断开之后自动停止
如果你想临时让他后台执行的话,这种方法很适合你
1 | nohup python ***.py & |
如何把print
输入重定向到执行文件
1 | nohup python -u myscript.py params1 > nohup.out 2>&1 & |
python的输出有缓冲,导致nohup.out不能够马上看到输出。加上-u
参数得到解决
]]>这种方法就算退出登录也会执行
1 | import logging |
logging中可以选择很多消息级别,如debug、info、warning、error以及critical。通过赋予logger或者handler不同的级别,开发者就可以只输出错误信息到特定的记录文件,或者在调试时只记录调试信息。
filename:指定日志文件名;
filemode:和file函数意义相同,指定日志文件的打开模式,’w’或者’a’;
format:指定输出的格式和内容,format可以输出很多有用的信息,
参数:作用
%(levelno)s:打印日志级别的数值
%(levelname)s:打印日志级别的名称
%(pathname)s:打印当前执行程序的路径,其实就是sys.argv[0]
%(filename)s:打印当前执行程序名
%(funcName)s:打印日志的当前函数
%(lineno)d:打印日志的当前行号
%(asctime)s:打印日志的时间
%(thread)d:打印线程ID
%(threadName)s:打印线程名称
%(process)d:打印进程ID
%(message)s:打印日志信息
datefmt:指定时间格式,同time.strftime();
level:设置日志级别,默认为logging.WARNNING;
stream:指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略;
1 | import logging |
1 | import logging |
1 | import logging |
time
和calendar
模块可以用于格式化日期和时间。Python 的 time 模块下有很多函数可以转换常见日期格式。如函数time.time()
用于获取当前时间戳, 如下实例:
1 | import time |
时间戳单位最适于做日期运算。但是1970年之前的日期就无法以此表示了。太遥远的日期也不行,UNIX和Windows只支持到2038年。
1 | time.time() |
由于我们获取的时间是距离1970年的时间戳,我们要转换成我们想要的格式,比如2018-09-10 08:09:00;也有将日期时间转换成时间戳
1 | import time |
python中时间日期格式化符号
符号 | 备注 |
---|---|
%y | 两位数的年份表示(00-99) |
%Y | 四位数的年份表示(000-9999) |
%m | 月份(01-12) |
%d | 月内中的一天(0-31) |
%H | 24小时制小时数(0-23) |
%I | 12小时制小时数(01-12) |
%Mv | 分钟数(00=59) |
%S | 秒(00-59) |
%a | 本地简化星期名称 |
%A | 本地完整星期名称 |
%b | 本地简化的月份名称 |
%B | 本地完整的月份名称 |
%c | 本地相应的日期表示和时间表示 |
%j | 年内的一天(001-366) |
%p | 本地A.M.或P.M.的等价符 |
%U | 一年中的星期数(00-53)星期天为星期的开始 |
%w | 星期(0-6),星期天为星期的开始 |
%W | 一年中的星期数(00-53)星期一为星期的开始 |
%x | 本地相应的日期表示 |
%X | 本地相应的时间表示 |
%Z | 当前时区的名称 |
%% | %号本身 |
由于CPU调度问题,我们使用time.time()
方法不是特别准确,Python为我们提供了更加准确的方法
1 | ime.perf_counter() # 返回系统运行时间 |
类别 | Objective-c | Swift |
---|---|---|
网络请求库 | AFNetworkingYTKNetwork | AlamofireMoya |
图片异步加载 | SDWebImage | Kingfisher(喵神) |
刷新控件 | MJRefresh(MJ) | |
布局约束 | Masonry | SnapKit |
JSON 解析 | YYModel(郭耀源)(最高效)MJExtention(MJ) | KakaJSON(MJ)HandyJson(Alibaba)SwiftyJSON |
基础库 | YYKit(郭耀源) | |
播放器 | ZFPlayer(任子峰) | BMPlayer(模仿ZF) |
1 | 1、导入模块 |
1 | # 单行写入(writerow([])) |
函数
及其上下文
的OC对象
如果需要交换UIControl的didMoveToSuperview方法,用于捕获控件的点击事件,需要的注意点是什么?
讲一下OC的消息机制
objc_msgSend
函数调用,给receiver
方法调用者发送一个消息@selector方法名
objc_msgSend
底层有3大阶段消息转发机制流程
forwardTargetForSelector
返回target对象methodSignatureForSelector
返回方法签名;如果返回签名不为空,进行第3步forwardInvocation:
参数传入一个方法调用对象,可以自定义调用机制 什么是Runtime?平时项目中有用过吗?
*
讲讲 RunLoop,项目中有用到吗?
RunLoop的基本作用
Runloop内部实现逻辑?
Runloop和线程的关系?
RunLoop
保存在一个全局的Dictionary里,线程作为key,RunLoop
作为value;{thread: Runloop}RunLoop
会在线程结束时销毁RunLoop
已经自动获取(创建),子线程默认没有开启RunLoop
Timer 与 Runloop 的关系?
程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?
runloop 是怎么响应用户操作的, 具体流程是什么样的?
说说runLoop的几种状态
1 | /* Run Loop Observer Activities */ |
runloop的mode作用是什么?
你理解的多线程?
iOS的多线程方案有哪几种?你更倾向于哪一种?
pthread
NSThread
GCD
NSOperation
更倾向于GCD;但是也要看场景
GCD 的队列类型
同步队列``并发队列
说一下 OperationQueue 和 GCD 的区别,以及各自的优势
Operation Queue
是基于GCD
封装的抽象类,目的是为了提高灵活度,以满足多线程操作频繁、灵活度要求高的复杂场景。
线程安全的处理手段有哪些?
加锁
,GCD同步队列
,信号量
等
OC你了解的锁有哪些?在你回答基础上进行二次提问;自旋和互斥对比?使用以上锁需要注意哪些?
路由地址
以下代码是在主线程执行的,会不会产生死锁?
1 | @implementation ViewController |
答:会产生死锁,因为viewDidLoad
方法就是在主线程(主队列)中进行的,而line-9
仍然想让代码块 立即 在主线程(主队列)中同步执行。
问下面代码的打印结果是什么?
1 | @implementation ViewController |
答:打印结果是:1, 3。
原因:performSelector:withObject:afterDelay:
的本质是往Runloop中添加定时器,子线程默认没有启动Runloop
。解决方法:在line-8
后插入
1 | //[[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSDefaultRunLoopMode]; |
请问下面代码的打印结果是什么?
1 | - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { |
答:只会打印1。因为在调用test的时候,线程已经销毁了。会报错target thread exited while waiting for the perform
使用CADisplayLink、NSTimer有什么注意点?
CADisplayLink
、NSTimer
会对target
产生强引用,如果target
又对它们产生强引用,那么就会引发循环引用。解决方案:1:使用Block处理。2.使用代理对象NSProxy
介绍下内存的几大区域
讲一下你对 iOS 内存管理的理解
ARC 都帮我们做了什么?
答:LLVM+Runtime相互协作;ARC利用LLVM编译器,自动帮我们生成release,retain,autorelease代码。像弱引用会在运行时做操作。
weak指针的实现原理
答:将弱引用存到哈希表里面,对象将要销毁的时候,取出弱引用表,将指向当前对象的弱指针置为nil。
autorelease对象在什么时机会被调用release
方法里有局部对象, 出了方法后会立即释放吗
1 | dispatch_queue_t queue = dispatch_get_global_queue(0, 0); |
1 | dispatch_queue_t queue = dispatch_get_global_queue(0, 0); |
Tagged Pointer
,涉及不到对象的内存管理,而上面一段代码因为多线程操作一个对象,有可能会造成对象的过度释放,导致崩溃。名称 | 描述 | 下载地址 |
---|---|---|
Fiddler | 抓包工具-配合谷歌插件Proxy-SwitchyOmega | 博主 |
Finder证书信任 | 抓包失败,可能是证书没有被信任 | 博主 |
1 | pip install tqdm |
tqdm是非常通用的,并且可以以多种方式使用。
1 | import tqdm |
1 | with tqdm(total=100) as pbar: |
即文本的高级匹配模式,提供搜索,替代等功能。其本质是一系列由特殊符号组成的字串,这个字串即正则表达式。
由普通字符和特殊符号组成字符串,通过描述字符的重复和位置等行为,达到匹配某一类字符串的目的
1 | re.findall(pattern, string) |
功能: 使用正则表达式匹配目标字符串内容
参数
元字符 : a
b
c
匹配规则: 每个字符匹配对应的字符
1 | re.findall("hello","hello world") |
1 | re.findall("你好","你好北京") |
元字符: |
匹配规则: 匹配 | 两边任意一个正则表达式
1 | re.findall("ab|cd","abcdefghialkjasbab") |
元字符: .
匹配规则: 匹配除换行外的任意字符
1 | f.o --> foo fao f@o f o |
1 | re.findall("f.o","foo is not fao") |
元字符 : ^
匹配规则:匹配目标字符串的开头位置
1 | re.findall("^Tom","Tom is a boy") |
元字符 : $
匹配规则 : 匹配字符串的结束位置
1 | re.findall("Tom$","hi Tom") |
元字符 : *
匹配规则: 匹配前面的字符出现0次或多次
1 | fo* --> fooooooooo f fo |
1 | re.findall("fo*","fadsfafoooafo") |
元字符 : +
匹配规则: 匹配前面的字符出现1次或多次
1 | fo+ --> fo fooooo |
1 | re.findall("fo+","fadsfafoooafo") |
元字符 : ?
匹配规则: 匹配前面的字符出现0次或1次
1 | fo? --> f fo |
1 | re.findall("fo?","fasdffoafooooo") |
元字符 : {n}
匹配规则: 匹配指定的重复次数
1 | fo{3} --> fooo |
1 | re.findall("fo{2}","fasdffoafooooo") |
元字符 : {m,n}
匹配规则 : 匹配前面的正则表达式 m–n次
1 | fo{2,4} --> foo fooo foooo |
1 | re.findall("fo{2,4}","fasdfofoooafooooo") |
元字符: [字符集]
匹配规则 : 匹配任意一个字符集中的字符
1 | [abc123] a b c 1 2 3 |
1 | re.findall("^[A-Z][a-z]*","Boy") |
元字符 : [^...]
匹配规则 :字符集取非,除列出的字符之外任意一个字符
1 | [^abc] --> 除a b c之外任意字符 |
1 | re.findall("[^ ]+","a little boy") |
元字符 : \d
\D
匹配规则:
\d
匹配任意数字字符 [0-9]
\D
匹配任意非数字字符 [^0-9]
1 | re.findall("1\d{10}","18888886666") |
元字符 : \w
\W
匹配规则:
\w
普通字符 [_0-9a-zA-Z]
也能匹配普通汉字\W
非普通字符1 | re.findall("\w+","hello#nihao%asdf@adsgdfg!df&") |
1 | re.findall("\W+","hello#nihao%asdf@adsgdfg!df&") |
元字符 :
\s
匹配任意空字符 [ \r\t\n\v\f]
\S
匹配任意非空字符1 | re.findall("\w+\s+\w+","hello world") |
1 | re.findall("\S+","hello this is tom") |
空格也属于空字符
元字符 : \A
\Z
匹配规则:
\A
匹配字符串开头位置 ^
\Z
匹配字符串结尾位置 $
绝对匹配 : 正则表达式要完全匹配目标字符串内容
在正则表达式开始和结束位置加上^
$
(或者\A
\Z
)。这样正则表达式必须匹配整个目标字符串才会有结果
1 | re.findall("\A\d+\Z","123445") |
元字符 : \b
\B
匹配规则 :
\b
匹配单词边界位置 普通字符和非普通字符交界认为是单词边界\B
匹配非单词边界位置1 | re.findall(r"num\b","num#asdf#") |
1 | re.findall(r"num\b","numasdf#") |
匹配单个字符 : a
.
\d
\D
\w
\W
\s
\S
[...]
[^...]
匹配重复 : *
+
?
{n}
{m,n}
匹配位置 : ^
$
\A
\Z
\b
\B
其他 : |
()
\
正则表达式转义
正则中的特殊符号:
.
*
+
?
^
$
[]
{}
()
|
\
正则表达式如果匹配特殊字符需要加 \
表达转义
1 | 正则 目标字符串 |
1 | pattern string |
raw字串 : 原始字符串对内容不解释转义,就表达内容原 本意义
贪婪模式 : 正则表达式的重复匹配总是尽可能多的向后匹配更多内容
*
+
?
{m,n}
非贪婪(懒惰模式) : 尽可能少的匹配内容
贪婪 —> 非贪婪 *?
+?
??
{m,n}?
1 | re.findall(r"ab+?","abbbbbbbb") |
1 | re.findall(r"ab??","abbbbbbbb") |
可以使用()
为正则表达式建立子组,子组可以看做是正则表达式内部操作的一个整体
子组是在正则表达式整体匹配到内容的前提下才会发挥作用,它不影响正则表达式整体去匹配目标内容这一原则
1 | re.search(r"(ab)+\d+","ababab1234").group() |
1 | re.search(r"\w+@\w+\.(com|cn)","abc@123.com").group() |
1 | re.search(r"(https|http|ftp)://\S+","https://www.baidu.com").group(1) |
search 只能匹配一个结果
1 | 格式 : (?P<name>pattern) |
1 | e.g. |
作用 : 可以通过组名更方便获取某组内容
用来生成正则表达式对象
regex = compile(pattern,flags=0)
re.findall(pattern,string,flags)
regex.findall(string,pos,endpos)
正则表达式所匹配的内容,做字符串的切割
re.split(pattern,string,flags = 0)
使用一个字符串去替换目标字符串被正则表达式匹配到的内容
re.sub(pattern,replaceStr,string,max,flags)
使用一个字符串去替换目标字符串被正则表达式匹配到的内容
re.subn(pattern,replaceStr,string,max,flags)
sub 和 subn区别
- sub返回替换后的字符串
- subn返回替换了几处
相比findall返回一个迭代对象
re.finditer(pattern,string,flags)
相当于在最前面写一个^
,最后面写一个$
, 只匹配到一处,返回一个match对象
fullmatch(pattern,string,flags)
相当于只匹配到开头,返回一个match对象
match(pattern,string,flags)
search(pattern,string,flags)
compile对象属性:
flags : 标志位
pattern : 正则表达式
groups: 有多少子组
groupindex : 捕获组形成组名和序列号的字典
组名为键,第几组为值
1 | pos 匹配目标字符串的开始位置 |
1 | span() 匹配内容的开始位置 |
1 | group() |
1 | groupdict() 获取捕获组名作为键,对应内容作为值的字典 |
re.compile re.findall re.search re.match
re.finditer re.fullmatch re.sub re.subn re.split
上面这些方法都有flags参数
作用: 辅助正则表达式,丰富匹配结果
1 | I == IGNORECASE 匹配时忽略字母的大小写 |
使用多个标志位使用按位或连接
1 | e.g. |
聚合操作
对文档的筛选结果进行整理统计
db.collection.aggregate()
功能 : 完成聚合操作
参数 : 聚合条件 —》 聚合操作符
聚合操作符
$group 分组聚合 需要配合具体的分组统计选项
$sum : 求和e.g.db.class0.aggregate({$group:{_id:'$gender',num:{$sum:1}}})db.class0.aggregate({$group:{_id:'$gender',num:{$sum:'$age'}}})$avg : 求平均数e.g. db.class0.aggregate({$group:{_id:'$gender',avg:{$avg:'$age'}}})$max 求最大值e.g.db.class0.aggregate({$group:{_id:'$gender',max:{$max:'$age'}}})$min 求最小值e.g.db.class0.aggregate({$group:{_id:'$gender',min:{$min:'$age'}}})
$project
修改文档的显示效果
e.g. project值得用法和find函数field格式一致
db.class0.aggregate({$project:{_id:0,name:1,age:1}})
db.class0.aggregate({$project:{_id:0,Name:’$name’,Age:’$age’}})
$match
数据筛选
$match值得用法同query一致
e.g. 过滤年龄大于18岁的数据文档
db.class0.aggregate({$match:{age:{$gt:18}}})
$limit
筛选前几条文档
e.g. 筛选前三条数据文档
db.class0.aggregate({$limit:3})
$skip
跳过几条文档显示
e.g. 跳过前三条文档
db.class0.aggregate({$skip:3})
$sort
将数据排序
e.g. 按照年龄排序
db.class0.aggregate({$sort:{age:1}})
聚合管道
聚合管道指的是将上一个聚合的操作结果给下一个聚合继续操作
db.collection.aggregate([{聚合},{},{}…])
e.g. match –> project –> sort
db.class0.aggregate([{$match:{gender:’m’}},{$project:{_id:0}},{$sort:{age:1}}])
e.g. group —> match 找到重名学生
db.class0.aggregate([{$group:{_id:’$name’,num:{$sum:1}}},{$match:{num:{$gt:1}}}])
指建立指定键值及所在文档存储位置的对照清单,使用索引可以方便我们进行快速查找,减少遍历次数提高查找效率
ensureIndex()
功能 : 创建索引
参数 : 索引域和索引选项
e.g. 根据name域创建索引
db.class0.ensureIndex({name:1})
查看集合中索引
db.collection.getIndexes()
自定义索引名称
db.collection.ensureIndex({},{name:’myIndex’})
e.g. 对age域创建索引命名ageIndex
db.class0.ensureIndex({age:1},{name:’ageIndex’})
删除索引
db.collection.dropIndex(“index”)
功能:删除索引
参数: 要删除的索引名称或者键值对
e.g.
db.class0.dropIndex({name:1})
db.class0.dropIndex(‘ageIndex’)
db.collection.dropIndexes()
功能:删除所有索引
索引类型
复合索引
根据多个域创建一个索引
e.g.
db.class0.ensureIndex({name:1,age:-1},{name:’name_age’})
数组索引 ,子文档索引
如果对某个域的值为数组或者子文档的域创建索引,那么通过数组或者子文档中某一项进行查找也是索引查找
e.g. 如果对score创建了索引那么该查找就是索引查找
db.class1.find({‘score.1’:88})
唯一索引
创建索引的域要求值不能够重复
e.g. 对name创建唯一索引db.class0.ensureIndex({name:1},{unique:true})
稀疏索引(间隙索引)
只针对有指定索引域的文档创建索引,没有该域的文档不会插入到索引表
e.g. 只对有age域的文档创建索引
db.class0.ensureIndex({age:1},{sparse:true})
索引约束
综上 : 数据量较小时不适合创建索引,当数据库进行频繁的修改操作而不是查找操作时也不适合创建索引。针对一个集合并不是创建索引越多越好。
]]>时间数据类型
mongo中存储时间大多为 ISODate
存储当前时间方法
指定时间
ISODate()
功能 : 生成mongo标准时间类型数据
参数 : 如果不传参默认为当前时间
传参表示指定时间
“2018-01-01 12:12:12”
“20180101 12:12:12”
“20180101”
e.g.
db.class2.insert({book:’Python崩溃’,date:ISODate(“2018-07-01 11:15:56”)})
时间戳
valueOf()
获取某个时间的时间戳
e.g. 获取当前标准时间时间戳
db.class2.insert({book:’Python涅槃’,date:ISODate().valueOf()})
Null 类型
值 null
Object (内部文档)
文档内部某个域的值还是一个文档数据则这个文档就是内部文档类型数据
通常使用外部文档域名 . 引用内部文档域名的方式使用内部文档
e.g.
db.class3.find({‘book.title’:’狂人日记’})
e.g.
db.class3.update({‘book.title’:’围城’},{$set:{‘book.price’:48.8}})
通过数组下标直接操作某一项
e.g. 通过数组下标引用第一项进行查找
db.class1.find({‘score.0’:{$gt:90}},{_id:0})
e.g.
db.class1.update({name:’小刚’},{$set:{‘score.1’:80}})
1 | db.collection.find(query,field) |
1 | query: 以键值对方式传递参数,如果是空{}表示查找所 有内容 |
_id
除非设置为0 否则均会查找_id
域其他域不能有的设置1有的设置01 | e.g. 查找结果只有name域 |
1 | findOne(query,field) |
1 | e.g. 查找集合中性别为女的第一个文档 |
操作符 : 使用$
符号注明的一个特殊字符串,表达一定的含义,比如 $lt
表示小于
1 | e.g. 查找年龄等于18 |
1 | e.g. 查找年龄小于18的 |
在mongodb中字符串可以比较大小
1 | e.g. 年龄小于等于18 |
1 | e.g. 查找年龄大于16 且 小于19 |
在mongodb中所有的{} [] 中都可以写多个条件。但根据 参数的不同表达的意思不一样
1 | e.g. 大于等于19 |
1 | e.g. 性别不等于‘m’的 |
使用ne查找也会找到该域不存在的文档
1 | e.g. 找到年龄为 [10,20,30] |
1 | e.g. 找到年龄不是 17 18 19 的 |
1 | e.g. |
1 | e.g. 年龄小于18并且 性别为男 |
区别: 第一种不能对相同的域进行操作
1 | e.g. 年龄小于16或者年龄大于18 |
1 | e.g. 查找年龄不小于18岁的 |
e.g. 性别不是m且年龄不小于18
db.class0.find({$nor:[{sex:’m’},{age:{$lt:18}}]},{_id:0})
1 |
|
(年龄大于17 并且 为男生) 或者 姓名叫 Abby
db.class0.find({$or:[{age:{$gt:17},sex:’m’},{name:’Abby’}]},{_id:0})
(年龄不大于18 或者为 女性) 并且 姓名 大于Lucy
e.g.
db.class0.find({$or:[{age:{$not:{$gt:18}}},{sex:’w’}],name:{$gt:’Lucy’}},{_id:0})
1 |
|
e.g. 只要score数组中包含小于60的元素即可查询过滤
db.class1.find({score:{$lt:60}},{_id:0})
1 |
|
e.g. 查找同时包含49 67的文档
db.class1.find({score:{$all:[49,67]}},{_id:0})
1 |
|
e.g. 查找score中包含两个元素的文档
db.class1.find({score:{$size:2}},{_id:0})
1 |
|
e.g. 显示数组中前两项
db.class2.find({},{_id:0,score:{$slice:2}})
e.g. 跳过第一项显示后面两项
db.class2.find({},{_id:0,score:{$slice:[1,2]}})
1 |
|
e.g. : 查找不存在sex域的文档
db.class1.find({sex:{$exists:false}},{_id:0})
1 |
|
e.g. 找出年龄为单数的文档
db.class1.find({age:{$mod:[2,1]}},{_id:0})
1 | #### $type |
e.g. 查找name域值类型为2的文档
db.class1.find({name:{$type:2}},{_id:0})
1 |
|
db.collection.distinct(filed)
1 | * 功能: 查看某个域的值范围 |
e.g. 获取某个域的值,去重
db.class0.distinct(‘age’)
1 |
|
e.g.
db.class0.find().pretty()
1 |
|
e.g. 显示查询结果前三条
db.class0.find({},{_id:0}).limit(3)
1 |
|
e.g. : 跳过前5条文档,显示后面的查询结果
db.class0.find({},{_id:0}).skip(5)
1 |
|
e.g. 统计性别为w的文档个数
db.class0.find({sex:’w’},{_id:0}).count()
1 |
|
e.g. 查找结果按照降序排序
db.class0.find({},{_id:0}).sort({age:-1})
1 |
|
e.g. 按照年龄升序排序,年龄相同时按照姓名降序
db.class0.find({},{_id:0}).sort({age:1,name:-1})
1 | 函数的连续调用 |
e.g.
db.class0.find({},{_id:0}).sort({age:1}).limit(3)
练习:1. 创建数据 名称 grade use grade 2. 创建集合 名称 class 3. 集合中插入若干(5-8条即可)文档 文档格式{name:'zhangsan',age:10,sex:'m',hobby:['a','b']} 年龄范围 6-15 爱好选择:draw sing dance basketball football pingpong computer 每个同学选择2-5项 db.class.insert({name:'zhangsan',age:10,sex:'m',hobby:['draw','sing']})4. 查找练习 查看班级所有学生信息 find() 查看班级中年龄为8岁的学生信息 find({age:8}) 查看班级中年龄大于10岁学生信息 find({age:{$gt:10}}) 查看班级中年龄在8-11岁之间的学生信息 find({age:{$gte:8,$lte:11}}) 查看班级中年龄10岁且为男生的学生信息 find({age:10,sex:'m'}) 查看班级中小于7岁或者大于14岁的学生 find({$or:[{age:{$lt:7}},{age:{$gt:14}}]}) 查看班级中年龄为8岁或者11岁的学生 find({age:{$in:[8,11]}}) 找到有2项兴趣爱好的学生 find({hobby:{$size:2}}) 找到兴趣中 有draw的学生 find({hobby:'draw'}) 找到既喜欢画画又喜欢跳舞的学生 find({hobby:{$all:['draw','dance']}}) 统计兴趣有4项的学生人数 find({hobby:{$size:4}}).count() 找出本班年龄第二大的学生 find().sort({age:-1}).skip(1).limit(1) 查看本班学生兴趣爱好涵盖哪些方面 db.class.distinct('hobby') 找到年龄最大的三个学生 find().sort({age:-1}).limit(3) 删除所有年龄大于16或者小于7岁的学生除非他的爱好有三项以上 remove({$or:[{age:{$gt:16}},{age:{$lt:7}}],{hobby:{$size:2}}})
]]>mongodb中数据的组织形式
mongodb文档 : 是以键值对的形成组成的一组数据。类似python中字典描述数据的方式
即文档的域,表达了一个键值对的含义
utf-8
格式字符串
\0
重复
即文档存储的数据。
bson支持的数据类型值
类型 | 值 |
---|---|
整型 | 整数 1 2 3 |
布尔类型 | true false |
浮点型 | 小数 |
Array | 数组 |
Date | 时间日期 |
Timestamp | 时间戳 |
String | 字符串 |
Symbol | 特殊字符串 |
Binary data | 二进制子串 |
Null | null 空值 |
Object | 内部文档(对象) |
code | js代码 |
regex | 正则子串 |
ObjectId | 自动生成ID标记 |
1 | "_id" : ObjectId("5ba07671b17d2b40342f7c5c") |
当mongodb插入文档时如果不指定_id
域则自动生成_id域。值如果不自己指定即会自动生成一个ObjectId值
24位16进制 使用ObjectId经过算法处理保证其唯一性
5ba07671 b17d2b 4034 2f7c5c
8位文档创建时间 6位 机器ID 4位进程id 6位计数器
文档中键值对是有序的
文档中键值对严格区分大小写
集合中的文档不一定有相同的域
集合中文档各自比较独立,相互并不影响
1 | db.collection.insert() |
1 | e.g. |
1 | db.class0.insert({_id:1,name:'Jame',age:16,sex:'m'}) |
参数用中括号里面放入多个文档
1 | db.class0.insert([{},{},...]) |
跟insert插入的区别就是,如果_id重复,save会覆盖,insert会报错
如果正常插入与insert用法相同
1 | db.collection.save() |
1 | e.g. |
如果插入数据是有_id域,且_id域值存在时则会修改原有文档,如果该值不存在则正常插入
1 | db.class0.save({_id:2,name:'Mary',age:20,sex:'w'}) |
1 | db.class0 ===> db.getCollection('class0') |
1 | remove(query,justOne) |
功能 : 删除文档
参数 :
1 | e.g. 删除所有不存在sex域的文档 |
1 | e.g. 删除第一条性别为w的文档 |
1 | e.g. 删除class1中所有文档 |
1 | updateOne(query,update,upsert,multi) |
功能 : 修改文档
参数 :
如果设置为true 则如果query没有筛选到匹配文档则根据query和update内容插入新的文档
1 | e.g. 将Tom的年龄修改为18 |
1 | e.g. 如果有name=Jame的文档则修改,如果没有则根据 query update插入新的文档 |
1 | e.g. 修改所有年龄小于17的为18 |
1 | updateMany(query,update,upsert) |
1 | db.findOneAndUpdate() |
1 | e.g. 找到名字为jpy的人,更改它的名字是lilei |
1 | db.findOneAndReplace() |
1 | e.g. 找到名字是eveb的这个人,并且把替换文档 |
修改一个值或者也可以增加一个域
1 | e.g. 修改姓名ebal的年龄为49岁 |
1 | e.g. 修改姓名ebal的年龄为49岁, 性别为女,并且新增一个喜好的域 |
删除一个域
1 | e.g. 删除名字Jpy的age域(字段) |
给一个域重命名
1 | e.g. 当前集合中所有的sex域更名为gender |
加法修改器
1 | e.g. 将所有人的年龄都加上1岁 |
乘法修改器
1 | e.g. 将所有人的年龄都成上3 |
指定一个值的下限
小于指定的值则修改该值,大于不变
1 | e.g. 将所有人的年龄小于18的都修改为18 |
指定一个值的上限
大于指定的值则修改该值,小于不变
1 | e.g. 将所有人的年龄大于18的都修改为18 |
如果使用update插入了文档,则将该修改器内容作为插入文档的一部分
1 | e.g. 如果插入了新文档则setOnInsert内容也会作为新文档一部分 |
向数组中添加一项
1 | e.g. 给小红 score数组中添加一项91 |
向数组中添加多项
1 | e.g. |
从数组中删除一项
1 | e.g. 从数组中删除一项 |
从数组中删除多项
1 | e.g. |
对多个值逐个进行操作
1 | e.g. 分别插入99 10 |
指定插入位置
1 | e.g. 将67 插入到数组1号位置 |
数组排序
1 | e.g. 将说有score域的数组降序排序 |
弹出一项 1表示弹出最后一项 -1弹出第一项
1 | e.g. 删除小明score中第一项 |
向数组中添加一项 但是不能添加重复的内容
1 | e.g. 如果数组中没有81 则添加81 |
1 | db.createCollection(collection_name) |
当向一个集合中插入数据的时候, 如果集合不存在则自动创建
1 | db.collection_name.insert(...) |
1 | show collections |
UTF-8
字符\0
system.
开头,因为这是系统保留集合前缀 重名
1 | db.collection.drop() |
1 | db.collection.renameCollection("new_name") |
1 | use databaseName |
use实际为选择使用哪个数据库,当数据库不存在时会自 动创建
use 后并不会立即创建出数据库,而是需要等到插入数 据时数据库才会创建
1 | show dbs |
系统数据库说明
admin : 存储用户信息
local : 存储本地数据
config : 存储分片信息
utf-8
字符 (mongo默认支持utf-8)空格 . / \ '\0'
字符64
字节重名
db : mongodb的全局量,代表当前正在使用的数据库
如果不选择使用任何数据库db代表test,直接插入数据就会建立test数据库
1 | db.dropDatabase() |
删除db所代表的数据库
备份和恢复数据的命令跟mongo都是shell命令,并不是Mongo Shell命令
1 | mongodump -h host -d dbname -o bak |
1 | mongorestore -h dbhost:port -d dbname path |
1 | mongostat |
常用的检测的字段含义
字段 | 含义 |
---|---|
insert | 每秒增加的次数 |
query | 每秒查的次数 |
update | 每秒改的次数 |
delete | 每秒删的次数 |
flushes | 每秒和磁盘交互次数 |
vsize | 虚拟内存 |
res | 物理内存 |
time | 时间 |
1 | mongotop |
常用的检测的字段含义
字段 | 含义 |
---|---|
ns | 数据表 |
total | 总时间 |
read | 读时间 |
write | 写时间 |
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
1.键值型数据库 Redis
2.文档型数据库 MongoDB
3.列存储数据库 HBase
4.图形数据库
标签 : 非关系型数据库 文档型数据库
最像关系型的非关系型数据库
特点 :
要求:
1 | sudo apt-get install mongodb |
默认安装位置 : /var/lib/mongodb
配置文件位置 : /etc/mongodb.conf
命令集: /usr/bin /usr/local/bin
mysql | mongodb | 含义 |
---|---|---|
database | database | 数据库 |
table | collection | 表/集合 |
column | field | 字段/域 |
row | document | 记录/文档 |
index | index | 索引 |
1 | mongod --dbpath 目录 |
1 | mongod --port 8080 |
1 | mongo shell : 用来操作mongodb数据库的界面,在这里可 以使用mongo语句操作数据库内容 |
组织结构 : 键值对 –> 文档 –>集合 –> 数据库
在MongoDB中如何表示下面的数据表
1 | ------------------------- |
1 | { |
经过多年的等待和期待,iOS13 终于支持本机Dark Mode
! 用户可以选择启用系统范围的深色外观,所有正式应用程序都将支持该外观。
在应用中启用Dark Mode
支持就像使用新的iOS13 SDK一样容易! 使用最新的SDK时,iOS将自动更新系统控件,例如开关,表格视图和按钮。如我们所见,Apple还使开发人员可以轻松地添加Dark Mode
支持。
但是,iOS不会自动切换图像或文本颜色,因此您可能会在Dark Mode
下注意到很多与应用有关的问题。
尽管使用新的iOS13 SDK进行构建会自动更新系统控件,但Apple强烈建议所有开发人员检查并更新其应用程序,以确保在启用Dark Mode
时所有文本和图像都能正确显示。
幸运的是,由于苹果在过去几年中对资产目录进行了许多改进,因此大多数iOS应用程序应该能够采用Dark Mode
,而无需进行重大代码更改。
iOS13现在在UIColor
中包括新的系统颜色,例如label
颜色。 通过使用iOS13中可用的新系统颜色,您的应用程序可以自动支持Dark Mode
和高对比度模式。
1 | label.color = UIColor.secondaryLabel |
尽管强烈建议您使用系统颜色来自动适应界面更改并确保各个应用程序之间的一致性,但是您可能希望在自定义颜色上支持深色模式。
使用iOS11的新资产目录颜色,可以通过添加自定义颜色的深色版本来轻松支持深色模式。
要添加资产目录颜色的深色版本,只需在目录中选择颜色,然后在“属性”检查器中将Appearances
切换为Any
,Dark
。 然后,添加颜色的深色外观版本。
就是这样-启用深色模式时,iOS会自动切换到资产目录颜色的深色版本,而您无需进行其他工作!
如果工程未使用资产目录颜色?
资产目录颜色使支持Dark Mode
更加容易! 请参阅Antoine van der Lee撰写的博客文章,详细了解如何使用资产目录颜色,如果您不希望切换,请参阅下面的以编程方式检测Dark Mode。
虽然大多数图像在Dark Mode
下看起来都不错,尤其是当您使用模板图像时,如果您将其放置在工作中以自动切换颜色(请参见上文的“适应颜色”),该图像会自动更改色调,但您可能希望Dark Mode
下看某些图像具有不同的颜色。
与资产目录颜色一样,启用Dark Mode
时自动切换资产目录中的图像很简单。 要添加任何图像的深色版本,只需在目录中选择该图像,然后在“属性”检查器中将Appearances
切换为Any
,Dark
。 然后,只需添加图像的深色外观版本。
在某些情况下,您需要以代码来检测外观变化并相应地更改用户界面。
⚠️警告:响应外观更改时,请确保尽快更新您的界面。 不要执行与外观更改无关的任务,因为这可能会导致延迟,尤其是当用户从Control Center切换外观时。
1 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { |
通过在视图控制器上重写traitCollectionDidChange,检测外观变化很简单,只需访问视图控制器的traitCollection.userInterfaceStyle
。
但是要记住,我们也要针对其他情况更改(例如设备旋转)调用traitCollectionDidChange
。 您可以使用此新方法检查当前外观是否不同:
1 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { |
如果要从任何地方访问当前特征集,也可以使用UITraitCollection.current
。
系统会自动在与iOS13或更高版本的SDK链接的任何应用中选择采用浅色和深色外观。 如果您需要更多时间来处理应用程序的Dark Mode
支持,或者希望将应用保持单一样式,则可以通过在应用的Info.plist
文件中添加UIUserInterfaceStyle
键(值为Light
或Dark
)来控制。 设置此键会导致系统忽略用户的偏好,并始终将特定外观应用于您的应用。
⚠️Note️注意:强烈建议支持深色模式。 在改进应用程序的
Dark Mode
支持时,请使用UIUserInterfaceStyle键暂时停用。
在iOS13中,您现在可以在特定视图或视图控制器上重写User Interface Style
。例如,您可能只希望某个视图控制器处于Dark Mode
,而应用程序的其余部分处于Light
模式。
要覆盖用户界面样式,只需在顶视图或视图控制器中覆盖此变量,它将向下传播到子视图:
1 | // Inside a UIViewController |
Edit Scheme -> Run -> Arguments -> Arguments Passed On Launch
加入
1 | -UITraitCollectionChangeLoggingEnabled YES |
在文章中,我们探讨了如何使您的应用在iOS13的Dark Mode
中看起来更漂亮,以及如何调整图像,颜色和自定义用户界面元素以匹配新样式。
我迫不及待想看到肯定会在秋天出现的所有出色的Dark Mode
应用程序!
本文所有信息全部摘自WWDC
今年整体上问题不大,没有出现编译报错问题。
用 Xcode10 编译的 App 在 iOS 13 上使用甚至几乎完美
用 Xcode11 编译的 App 再在 iOS 13 上跑,就有些问题了
iOS 13 多了一个新的枚举类型 UIModalPresentationAutomatic
,且是modalPresentationStyle
的默认值。
UIModalPresentationAutomatic
实际是表现是在 =iOS 13的设备上被映射成UIModalPresentationPageSheet
。
我这边的设计师表示,新样式不错,可以不用改😆。
不过, PageSheet
与 FullScreen
对比 有个需要注意的地方,控制器的生命周期有点区别:
以 控制器A
、控制器B
举例:
控制器A
present 控制器B
控制器A
不会调用 viewWillDisappear
以及 viewDidDisappear
控制器B
dismiss 时控制器A
不会调用 viewWillAppear
以及 viewDidAppear
那么如果有些业务逻辑会在控制器A
的生命周期里做的话,就需要考虑其他方式实现,或者改回UIModalPresentationFullScreen
如果需要改成原本全屏的样式,可以处理Controller:
modalPresentationStyle
值modalPresentationStyle
值modalPresentationStyle
的get方法看上面gif,用户是可以通过手势下拉关闭被present出来的控制器的,那如果我需要禁止他下来要怎么实现呢?
可以参考disabling_pulling_down_a_sheet的Demo
设置presentationController.delegate
代理对象,实现UIAdaptivePresentationControllerDelegate
协议方法
1 | @interface XXViewController () <UIAdaptivePresentationControllerDelegate> |
如下方式,直接给textfield.leftView
赋值一个UILabel
对象,他的宽高会被 sizeToFit
,而不是创建时的值。
1 | // left view label |
如所看到,实际leftview的width为59,height为19:
通过监听leftView
的frame
变化,发现是layoutSubview
之后变化的。
最终还是给UILabel多套了一个UIView来解决
1 | // label |
打开有UISearchBar
的页面发现Crash了,看到控制台输出提示:
1 | // 获取_searchField |
看起来是禁止访问私有属性了。
用 Xcode 10 编译的 App 在 iOS 13 上能正常使用,那么就是 Xcode 11 做了限制访问私有属性的一些处理了。
偶然发现 iOS 13 中增加了UISearchTextField
类,且暴露了searchTextField
。
1 | // UISearchTextField.h |
但是仅在 iOS 13 以上系统支持,还是暂时用遍历view的方式去做了😂
点击导航栏返回的时候Crash了,控制台输出提示:
1 | Теrmіnаtіng арр due to uncaught exception' NSInternalInconsistencyException' , |
因为我们工程里,基本上所有的Controller
是继承基类BaseViewController
并实现- (BOOL)naviBack:
方法,用于实现在用户点击返回和侧滑返回时,一些不能返回的特殊处理。
其根本原理是通过实现UINavgationBar
的代理方法- (BOOL)navigationBar:shouldPopItem:
来做的控制:
1 | - (BOOL)navigationBar:(UINavigationBar *)navigationBar |
但是我实现的时候有Return YES
啊!想了想,试着注释了[self popViewControllerAnimated:YES]
,发现没有崩溃了。
但是在iOS 12上,会发现控制器没有回到上一层,如图,只有navbar回到上一层了:
好吧,那只能判断一下版本解决这个问题了,修改方式:
1 | - (BOOL)navigationBar:(UINavigationBar *)navigationBar |
WWDC 19 直播的时候看到夜间模式,老实说挺开心的,直到我用 Xcode 11 开始做适配,妈耶!x N
注意:使用 Xcode 10 编译的 App 依然是日间模式,不会产生效果!!!
初步扫了一下出现的问题如下图,大致情况是:没有设置背景色的系统控件会被设置成黑色,部分控件是tintColor没设置的话也会被改。
由于Assets里的Color配置是iOS 12
以上才能使用的,所以如果没有做全局主题色设计且需要支持iOS 12以下设备,改起来会比较恶心。
对此现象,找设计师沟通。设计师表示,暂时没有精力做夜间模式规划。
设计师问:能否强制只日间模式?
答:能。配置方式有两种,单页面配置
和 全局配置
。
UIViewControler
对象的overrideUserInterfaceStyle
属性设置成UIUserInterfaceStyleLight
或者UIUserInterfaceStyleDark
以强制是某个页面显示为浅/深色模式
Info.plist
的中,增加/修改UIUserInterfaceStyle
为UIUserInterfaceStyleLight
或UIUserInterfaceStyleDark
textField 更改holder颜色崩溃
1 | [textField setValue:HexColor(0x999999) forKeyPath:@"_placeholderLabel.textColor"]; |
解决方式
1 | NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:@"请输入占位文字" attributes:@{NSForegroundColorAttributeName:HexColor(0x999999), NSFontAttributeName:textField.font}]; |
1 | print("Hello World") |
不用编写main函数,Swift将全局范围内的首句可执行代码作为程序入口
一句代码尾部可以省略分号(;),多句代码写到同一行时必须用分号(;)隔开
用var
定义变量,let
定义常量,编译器能自动推断出变量\常量的类型
1 | let a = 10 |
Playground可以快速预览代码效果,是学习语法的好帮手
Command + Shift + Enter
:运行整个Playground
Shift + Enter
:运行截止到某一行代码
1 | // 单行注释 |
1 | let age1 = 10 |
1 | let age: Int |
1 | let age |
1 | func 🐂🍺() { |
整数类型:Int8、Int16、Int32、Int64、UInt8、UInt16、UInt32、UInt64
在32bit平台,Int等价于Int32,Int等价于Int64
整数的最值:UInt8.max、Int16.min
一般情况下,都是直接使用Int即可
浮点类型:Float,32位,精度只有6位;Double,64位,精度至少15位
1 | let letFloat: Float = 30.0 |
布尔
1 | let bool = true // 取反是false |
字符串
1 | let string = "飞翔" |
字符(可存储ASCII字符、Unicode字符)
1 | let character: Character = "🐶" |
整数
1 | let intDecimal = 15 //十进制 |
整数和浮点数可以添加额外的零或者添加下划线来增强可读性
p100_0000
、1_000_000.000_000_1
、000123.456
浮点数
1 | let doubleDecimal = 15.0 //十进制, 等价于1.25e2,0.0125等价于1.25e-2 |
数组
1 | let array = [1, 3, 5, 7, 9] |
字典
1 | let dictionary = ["age": 18, "height": 168, "weight": 120] |
整数转换
1 | let int1: UInt16 = 2_000 |
整数、浮点数转换
1 | let int = 3 |
字面量相加
1 | // 字面量可以直接相加,欣慰数字字面量本身没有明确类型 |
1 | let http404Error = (404, "Not Found") |
1 | let age = 4 |
1 | // 错误代码 |
1 | var num = 5 |
1 | var num = -1 |
1 | let names = ["Anna", "Alex", "Brian", "Jack"] |
1 | let names = ["Anna", "Alex", "Brian", "Jack"] |
1 | let names = ["Anna", "Alex", "Brian", "Jack"] |
1 | // i默认是let, 如果有需要可以声明为var |
1 | for _ i in 1...3 { |
1 | for i in 1..<5 { |
1 | let names = ["Anna", "Alex", "Brian", "Jack"] |
单侧区间
让区间朝着一个方向尽可能的远
1 | let names = ["Anna", "Alex", "Brian", "Jack"] |
1 | for name in names[...2] { |
1 | for name in names[..<2] { |
1 | let range = ...5 |
1 | let range1: ClosedRange<Int> = 1...3 |
字符、字符串也能使用区间运算符,但默认不能用在for-in中
1 | let stringRange1 = "cc"..."ff" // ClosedRange<String> |
1 | // \0到~囊括了所有可能要用到的ASCII字符 |
1 | let hours = 11 |
1 | var number = 1 |
1 | var number = 1 |
1 | var number = 1 |
1 | // 错误示范,编译器会报错;应该加上default |
1 | var number = 1 |
1 | enum Answer { case right, wrong } |
1 | let string = "Jack" |
1 | let character: Character = "a" |
1 | switch string { |
1 | let count = 62 |
1 | let point = (1, 1) |
1 | let point = (2, 0) |
1 | let point = (1, -1) |
1 | // 将所有正数加起来 |
1 | outer: for i in 1...4 { |
1 | func pi() -> Double { |
1 | func sayHello() -> Void { |
1 | func sayHello() -> () { |
1 | func sayHello() { |
1 | func sum(v1: Int, v2: Int) -> Int { |
1 | func calculate(v1: Int, v2: Int) -> (sum: Int, difference: Int, average: Int) { |
1 | /// 求和【概述】 |
可以修改参数标签
1 | func goToWork(at time: String) { |
可以使用下划线_ 省略参数标签
1 | func sum(_ v1: Int, _ v2: Int) -> Int { |
参数可以有默认值
1 | func check(name: String = "nobody", age: Int, job: String = "none") { |
C++的默认参数值有个限制:必须从右往左设置。由于Swift拥有参数标签,因此并没有此类限制
但是在省略参数标签时,需要特别注意,避免出错
1 | // 这里的middle不可以省略参数标签 |
1 | func sum(_ numbers: Int...) -> Int { |
一个函数最多只能有1个可变参数
紧跟在可变参数后面的参数不能省略参数标签
1 | // 参数string不能省略标签 |
1 | /// - Parameters: |
1 | print(1, 2, 3, 4, 5) // 1 2 3 4 5 |
1 | print("My name is Jake.", terminator: "") |
可以用inout定义一个输入输出参数:可以在函数内部修改外部实参的值
1 | func swapValues(_ v1: inout Int, _ v2: inout Int) { |
1 | func swapValues(_ v1: inout Int, _ v2: inout Int) { |
可变参数不能标记为inout
inout参数不能有默认值
inout参数只能传入可以被多次赋值的
inout参数的本质是地址传递(引用传递)
1 | func sum(v1: Int, v2: Int) -> Int { |
1 | func sum(v1: Int, v2: Int, v3: Int) -> Int { |
1 | func sum(v1: Double, v2: Int) -> Double { |
1 | func sum(_ v1: Int, _ v2: Int) -> Int { |
1 | func sum(a: Int, b: Int) -> Int { |
1 | sum(v1: 10, v2: 20) // 30 |
返回值类型与函数重载无关
默认参数值和函数重载一起使用产生二义性时,编译器并不会报错(在C++中会报错)
1 | func sum(v1: Int, v2: Int) -> Int { |
可变参数、省略参数标签、函数重载一起使用产生二义性时,编译器有可能会报错
1 | func sum(v1: Int, v2: Int) -> Int { |
如果开启了编译器优化(Release模式默认会开启优化),编译器会自动将某些函数变成内联函数 p将函数调用展开成函数体
哪些函数不会被自动内联?
@inline
1 | // 永远不会被内联(即使开启了编译器优化) |
1 | // 开启编译器优化后,即使代码很长,也会被内联(递归调用函数、动态派发的函数除外) |
在Release模式下,编译器已经开启优化,会自动决定哪些函数需要内联,因此没必要使用@inline
每一个函数都是有类型的,函数类型由形式参数类型、返回值类型组成
1 | func test() { } // () -> Void 或者 () -> () |
1 | func sum(a: Int, b: Int) -> Int { |
1 | // 定义变量 |
函数类型作为函数参数
1 | func sum(v1: Int, v2: Int) -> Int { |
函数类型作为函数返回值
1 | func next(_ input: Int) -> Int { |
返回值是函数类型的函数,叫做高阶函数(Higher-Order Function)
typealias用来给类型起别名
1 | typealias Byte = Int8 |
1 | typealias Date = (year: Int, month: Int, day: Int) func test(_ date: Date) { |
按照Swift标准库的定义,Void就是空元组()
1 | public typealias Void = () |
将一个工程分解为各个组件,然后按照某种方式任意组织成为一一个拥有完整业务逻辑的工程。
基础组件
基础配置(宏,常量),分类, 网络(AFNetworking, SDWebImage二次封装)、工具类(日期时间的处理,文件处理,设备处理)
功能组件
控件(弹幕,轮播器,选项卡);功能(断点续传,音频处理)
业务组件
业务线一,业务线二。
多人协作的项目工程中,独立的代码模块划分的重要性是毋庸置疑的。而CocoaPods是一个iOS的包管理第三方工具(类似的概念),它可以方便的帮助我们管理代码模块。
一般情况下,下面这条命令就能安装最新的cocoapods到本地。
1 | sudo gem install cocoapods |
然而有时候也会由于以下几个问题导致安装失败:
具体问题还得再去发动Google大法找一找如何解决。
1 | gem update --system # 这里请翻墙一下 |
1 | gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ |
由于MacOS系统自带了一个Ruby环境,但是usr/bin的访问权限可能会导致一些问题,最好还是使用Homebrew安装一个ruby。
使用brew install ruby装好Ruby后记得给Shell配一下环境变量如下:
1 | export PATH="/usr/local/lib/ruby/gems/2.5.0/bin:/usr/local/opt/ruby/bin:/usr/local/bin:$PATH" |
CocoaPods的工作主要是通过ProjectName.xcworkspace来组织的,在打开ProjectName.xcworkspace文件后,发现Xcode会多出一个Pods工程。
1.创建一个本地Demo工程
1 | pod lib create RCDownloader |
2.填入询问的问题,填完之后会自动打开一个Demo工程
1 | What platform do you want to use?? [ iOS / macOS ] |
3.找到Pod工程
下面有一个Development Pods
文件夹下面的工程名
下面的ReplaceMe.m
文件
4.右键ReplaceMe.m
文件,Show in Finder
到Finder文件夹,把我们封装的.h和.m文件
替换掉ReplaceMe.m
5.将替换进来的文件,拖拽到工程刚刚ReplaceMe.m
所在的文件夹,然后全选Target
6.主工程
下面有一个Example for <classname>
文件夹,这个文件夹是用来调试的类库
7.在我们需要引入该Pod库的工程的Podfile
文件中加入pod 'RCDownloader', :path => '可以是绝对路径/相对路径'
8.执行pod install
注意:
如果我们的Pod有需要依赖的Framework,那么就打开组建工程的spec文件,里面有配置framework的地方。
1.打开码云(他家私有库不要钱,github私有库要钱)
2.创建一个项目:名称是RCSpecs
,私有的Objc
3.添加本地索引库
1 | pod repo add RCSpecs https://gitee.com/****/RCSpecs.git |
4.查看本地索引库列表
1 | pod repo |
5.将本地Pod库关联本地索引库
1.在码云上再创建一个项目名称是RCDownloader
私有的
2.终端切换到RCDownloader工程根目录
3.提交代码
1 | git add . |
4.修改本地spec文件
1 | s.homepage = 'https://gitee.com/htyh_manito_rencheng11/RCDownloader' |
5.打 tag 0.1.0(跟s.version保持一致)
1 | git tag 0.1.0 |
6.将修改的spec文件上传到远程仓库
1 | git commit -m "修改spec" |
7.验证spec文件(警告可以忽略)
1 | pod spec lint |
8.上传RCDownloader.spec到远程索引库
1 | pod repo push RCSpecs RCDownloader.podspec --allow-warnings |
9.Podfile文件中增加source
1 | source 'https://gitee.com/htyh_manito_rencheng11/RCSpecs.git' |
10.pod install引入
1 | # 这么写可以每次获取最新版本 |
xxx
。CocoaPods
目录缓存, 找到 ~/Library/Caches/CocoaPods/Pods/Release
, 删除此目录。Xcode -> Preferences -> Locations -> Derived Data
点击下方的右箭头跳转到相应目, 删除此目录即可。pod repo update
。pod install
。名称 | 描述 | 下载地址 |
---|---|---|
sip | 颜色提取器 | 博主 |
Apple Configurator 2 | AppStore中ipa包获取工具 | AppStore |
Assets提取工具 | 提取ipa包中的图片 | 博主 |
Prepo | AppIcon 生成工具 | AppStore |
Asset Catalog Creator Pro | AppIcon/LaunchImage生成工具 | AppStore |
HandShaker | 安卓手机管理器 | AppStore |
名称 | 描述 | 下载地址 |
---|---|---|
FileZilla | ftp、sftp连接客户端 | 博主 |
Microsoft Remote Desktop | windows server连接工具 | 博主 |
parallels client windows | server 连接工具 | AppStore |
ShellCraft | ssh连接管理器 | 博主 |
SocketTool | socket调试工具 | 博主 |
Sequel Pro | MySQL连接工具 | 官方 博主 |
Medis | Redis连接工具 | AppStore 博主 |
名称 | 描述 | 下载地址 |
---|---|---|
MWeb | Markdown编辑工具 | 博主 |
OmniGraffle | 画图 | 博主 |
CleanMyMac X | 空间清理工具 | 官方 博主 |
zoom.us | 屏幕共享软件 | 官方 博主 |
VirtualBox | 虚拟机 | 官方 |
VLC | 流媒体播放软件 | 博主 |
Shadowsocks | VPN | 博主 |
ClashX | VPN | 博主 |
Realm Browser | Realm数据库查看 | AppStore |
OBS | 音视频推流工具 | 官方 |
Charles | 抓包工具 | 官方 破解工具 |
java-抓包 | 抓包工具 | 博主 |
jmeter | 性能测试工具 | 博主 |
AirServer | 镜像服务 | 博主 |
Xmind2020 | 思维导图 | 博主 |
jetbrains2020.2激活 | 激活工具 | 博主 |
设备 | 尺寸 | 逻辑分辨率 | Scale | 设备分辨率 | PPI |
---|---|---|---|---|---|
3GS | 3.5 | 320x480 | @1x | 320x480 | 163 |
4s | 3.5 | 320x480 | @2x | 640x960 | 326 |
5/5c/5s/SE | 4 | 320x568 | @2x | 640x1136 | 326 |
6/6s/7/8/SE2 | 4.7 | 375x667 | @2x | 750x1334 | 326 |
6/6s/7/8 Plus | 5.5 | 414x736 | @3x | 1242x2208 | 401 |
X/Xs/11 Pro | 5.8 | 375x812 | @3x | 1125x2436 | 458 |
Xs Max/11 Pro Max | 6.5 | 414x896 | @3x | 1242×2688 | 458 |
XR/11 | 6.1 | 414x896 | @2x | 828×1792 | 326 |
12 Mini | 5.4 | 375x812 | @3x | 1080×2340 | 476 |
12/12 Pro | 6.1 | 390x844 | @3x | 1170×2532 | 460 |
12 Pro Max | 6.7 | 428x926 | @3x | 1284×2778 | 458 |
1 | npm install --save hexo-filter-sequence |
站点配置文件 _config.yml 中增加如下配置:
1 | sequence: |
源码修改后才能正常使用,进入插件目录作如下修改:
1 | // index.js |
新建文章,增加如下内容:
1 | Alice->Bob: Hello Bob, how are you? |
效果如下:
Alice->Bob: Hello Bob, how are you?
Note right of Bob: Bob thinks
Bob–>Alice: I am good thanks!
在实际开发过程中,我们经常遇到定时器NSTimer不准确的情况。比如
1. 滑动视图的时候,NSTimer干脆就不走了;2. 两台设备同样倒计时300,有的设备走得快,有的设备走得慢,如果是抢购商品,这个不准确就很扯皮了;
解决方案:
1. 可以通过切换runloop的模式来处理2. 更换计时方案
NSTimer受runloop的影响,由于runloop需要处理很多任务,导致NSTimer的精度降低
简述:GCD 比 NSTimer 更准的定时器
优点:精度很高,系统自动触发,系统级别
注意:dispatch_source_t在后台可以走,但是在一段时间(30s或3min)后,程序也会被挂起,这个时候dispatch_source_t还是不准,所以我们要给程序开一个后台任务,让程序在后台能夺走一会儿
1 | // 宏定义 @weakify是YYKit提供的 |
1 | @interface AppDelegate : UIResponder <UIApplicationDelegate> |
1 | @interface AppDelegate () |
socket网络通信
1 | 用于网络计算机通信的一种接口 |
1 | 创建(socket) |
1 | 创建(socket) |
1 | 面向连接、可靠传输、数据无差错的收发序列 |
1 | 面向无连接、不可靠传输,数据可能丢失、错误、重发 |
创建
1 | socket.socket(socket_family = AF_INET, socket_type = SOCK_STREAM, proto = 0) |
绑定:绑定IP地址和端口
1 | socket.bind(address) |
监听:使服务器可以接收连接请求
1 | socket.listen(backlog) |
接受连接:
1 | connfd,addr = sockfd.accept() |
接收数据:
1 | data = connfd.recv(buffersize) |
发送数据:
1 | n = connfd.send(data) |
关闭套接字:
1 | sockfd.close() |
请求连接(只能在客户端调用)
1 | sockfd.connect(addr) |
1 | import socket |
1 | import socket |
1 | import socket |
1 | import socket |
1 | 局域网、城域网、广域网 |
1 | 客户端:请求服务的一方 |
客户端 | 服务器 |
---|---|
如何找到对方查阅地址(DNS) | 如何让对方联系自己公布自己的网络地址 |
如何联系对方主动敲门(发起网络连接) | 如何让对方联系自己开门迎客(监听、接收连接) |
如何正确传输数据通信协议 | 如何正确传输数据通信协议 |
如何让对方理解自己的意思应用协议 | 如何让对方理解自己的意思应用协议 |
如何结束对话关闭连接 | 如何结束对话释放资源 |
1 | 生活中的协议:汽车转向灯、交通信号灯 |
OSI/ISO七层模型 –> 网络通信标准化流程
ISO(国际标准化组织)
OSI(开放系统互联模型)
1 | 应用层 : 提供用户服务,具体功能有程序体现 |
1 | 建立了统一的网络工作流程 |
1 | 高内聚: 模块功能尽可能单一,不要相互掺杂 |
1 | 应用层 传输层 网络层 物理链路层 |
将应用层,表示层,会话层统一为应用层,便于开发实践
1 | 应用层 传输层 网络层 链路层 物理层 |
1 | 发送端由应用层逐层根据协议添加首部信息,最终在物理层实现发送 |
1 | 封装:每个层次接收上层传递的数据,加入本层的控制信息,传递给下一层 |
1 | 网络节点分配一个IP地址(全世界独一无二的) |
网络主机: 在网络中标识一台主机的标志
1 | 本地使用 : 'localhost' |
ifconfig : 查看本地网络信息 (Unix, Linux)
ipconfig : 查看本地网络信息 (Windows)
IP地址 : 在网络上查找一台主机的网络位置
1 | IPv4 : 点分十进制 192.168.1.2 每部分取值:0--255 |
域名 : 网络服务器的别名
1 | 方便记忆,表达一定的含义 |
端口号 port
1 | 端口号是网络地址的一部分,用于区分主机上不同的网络应用 |
网络字节序 : 网络上数据传输的排列方式
基于TCP协议的数据传输
提供可靠的数据传输,可靠性指的是传输。过程中无丢失,无失序,无差错,无重复
在通信前需要建立通信连接,通信结束需要断开连接。
1 | 1. 客户端向服务端发起连接请求 |
1 | 1. 主动方发送报文提出断开连接 |
对数据传输有准确性的要求,传输文件较大;
需要确保传输可靠性。比如 : 网页获取,文件下载,邮件收发
基于UDP协议的传输
不保证传输的可靠性,数据传输不需要提前 建立连接
网络情况较差,对传输可靠性要求不高。比如:网络视频,群聊,广播
]]>1、语法 :alter table 表名 执行动作;
2、添加字段(add)
alter table 表名 add 字段名 数据类型;
alter table 表名 add 字段名 数据类型 first;
alter table 表名 add 字段名 数据类型 after 字段名;
3、删除字段(drop)
alter table 表名 drop 字段名;
4、修改数据类型(modify)
alter table 表名 modify 字段名 新数据类型;
5、表重命名(rename)
alter table 表名 rename 新表名;
6、练习
1、创建库 studb2
2、在库中创建表 t1 ,字段有3个:name、age、phnumber
use studb2;
create table t1(
name char(20),
age tinyint unsigned,
phnumber char(11)
);
3、查看表结构
desc t1;
4、在表中第一列添加一个 id 字段
alter table t1 add id int first;
5、把 phnumber 的数据类型改为 bigint
alter table t1 modify phnumber bigint;
6、在表中最后一列添加一个字段 address
alter table t1 add address varchar(50);
7、删除表中的 age 字段
alter table t1 drop age;
8、查看表结构
desc t1;
5、表记录管理
1、删除表记录
1、delete from 表名 where 条件;
2、注意
delete语句后如果不加where条件,所有记录全部清空
2、更新表记录
1、update 表名 set 字段1=值1,字段2=值2,… where 条件;
2、注意
必须加where条件
3、练习(表hero)
1、查找所有蜀国人的信息
select * from hero where country=”蜀国”;
2、查找所有女英雄的姓名、性别和国家
select name,sex,country from hero
where sex=”女”;
3、把id为2的记录改为典韦,性别男,国家魏国
update hero set name=”典韦”,sex=”男”,country=”魏国” where id=2;
4、删除所有蜀国英雄
delete from hero where country=”蜀国”;
5、把貂蝉的国籍改为魏国
update hero set country=”魏国”
where name=”貂蝉”;
6、删除所有表记录
delete from hero;
4、运算符操作
1、数值比较/字符比较
1、数值比较 := != > >= < <=
2、字符比较 := !=
3、练习
1、查找攻击力高于150的英雄的名字和攻击值
select name,gongji from sanguo where gongji>150;
2、将赵云的攻击力设置为360,防御力设置为68
update sanguo set gongji=360,fangyu=68
where name=”赵云”;
2、逻辑比较
1、and (两个或多个条件同时成立)
2、or (任意一个条件成立即可)
3、练习
1、找出攻击值高于200的蜀国英雄的名字、攻击力
select name as n,gongji as g from sanguo
where gongji>200 and country=”蜀国”;
2、将吴国英雄中攻击值为110的英雄的攻击值改为100,防御力改为60
update sanguo set gongji=100,fangyu=60
where country=”吴国” and gongji=110;
3、查找蜀国和魏国的英雄信息
select * from sanguo
where country=”蜀国” or country=”魏国”;
3、范围内比较
1、between 值1 and 值2
2、where 字段名 in(值1,值2,…)
3、where 字段名 not in(值1,值2,…)
4、练习
1、查找攻击值100-200的蜀国英雄信息
select * from sanguo
where gongji between 100 and 200 and
country=”蜀国”;
2、找到蜀国和吴国以外的国家的女英雄信息
select * from sanguo
where country not in(“蜀国”,”吴国”)
and sex=”女”;
3、找到id为1、3或5的蜀国英雄 和 貂蝉的信息
select * from sanguo
where
(id in(1,3,5) and country=”蜀国”) or name=”貂蝉”;
4、匹配空、非空
1、空 :where name is null
2、非空:where name is not null
3、示例
1、姓名为NULL值的蜀国女英雄信息
select * from sanguo
where
name is null and country=”蜀国” and sex=”女”;
2、姓名为 “” 的英雄信息
select * from sanguo where name=””;
4、注意
1、NULL :空值,只能用 is 或者 is not 去匹配
2、”” :空字符串,用 = 或者 != 去匹配
5、模糊比较
1、where 字段名 like 表达式
2、表达式
1、_ : 匹配单个字符
2、% : 匹配0到多个字符
3、示例
select name from sanguo where name like “%“;
select name from sanguo where name like “%”;
## NULL不会被统计,只能用is、is not去匹配
select name from sanguo where name like “___”;
select name from sanguo where name like “赵%”;
5、SQL查询
1、总结
3、select …聚合函数 from 表名
1、where …
2、group by …
4、having …
5、order by …
6、limit …;
2、order by
1、给查询结果进行排序
2、… order by 字段名 ASC/DESC
3、升序:ASC(默认)
降序:DESC
4、示例
1、将英雄按防御值从高到低排序
2、将蜀国英雄按攻击值从高到低排序 3、将魏蜀两国英雄中名字为三个字的按防御值升序排列 select * from sanguo where country in("蜀国","魏国") and name like "___" order by fangyu ASC;select * from sanguo where (country="魏国" or country="蜀国") and name like "___" order by fangyu;
3、limit (永远放在SQL语句的最后写)
1、作用 :限制显示查询记录的个数
2、用法
1、limit n -> 显示 n 条记录
2、limit m,n
m 表示 从第m+1条记录开始显示,显示 n 条
limit 2,3 : 第 3、4、5 三条记录
3、示例
1、在蜀国英雄中,查找防御值倒数第二名至倒数第四名的英雄的记录
select * from sanguo
where country=”蜀国”
order by fangyu asc
limit 1,3;
2、在蜀国英雄中,查找攻击值前3名且名字不为 NULL 的英雄的姓名、攻击值和国家
select name,gongji,country from sanguo
where
country=”蜀国” and name is not NULL
order by gongji DESC
limit 3;
4、分页
每页显示5条记录,显示第4页的内容
第1页 :limit 0,5 # 1 2 3 4 5 第2页 :limit (2-1)*5,5 # 6 7 8 9 10 第3页 :limit (3-1)*5,5 # 11 12 13 14 15 第4页 :limit (4-1)*5,5 # 16 17 18 19 20 每页显示n条记录,显示第m页 :limit (m-1)*n,n
4、聚合函数
1、分类
avg(字段名) : 求该字段平均值
sum(字段名) : 求和
max(字段名) : 最大值
min(字段名) : 最小值
count(字段名) : 统计该字段记录的个数
2、示例
1、攻击力最强值是多少
select max(gongji) from MOSHOU.sanguo;
2、统计id 、name 两个字段分别有几条记录
select count(id),count(name) from sanguo;
## 空值 NULL 不会被统计,””会被统计
3、计算蜀国英雄的总攻击力 select sum(gongji) from MOSHOU.sanguowhere country="蜀国"; 4、统计蜀国英雄中攻击值大于200的英雄的数量 select count(*) from MOSHOU.sanguowhere gongji>200 and country="蜀国";
]]>1 | 存储数据的仓库 |
1 | 金融机构、游戏网站、购物网站、论坛网站 ... ... |
1 | MySQL、Oracle、SQL_Server、DB2、MongoDB、MariaDB |
1 | 1. 开源软件:MySQL、Mariadb、MongoDB |
1 | 1. 不跨平台 :SQL_Server |
1 | 1. 商业软件 :政府部门、金融机构 |
1
2
3
1. 数据是以行和列(表格)的形式去存储的
2. 表中的每一行叫一条记录,每一列叫一个字段
3. 表和表之间的逻辑关联叫关系
**示例**1
2
3
4
5
6
7
8
9
10
11
关系型数据库存储
表1、学生信息表
姓名 年龄 班级
星矢 25 三班
水冰月 25 六班
表2、班级信息表
班级 班主任
三班 大空翼
六班 松人
1
2
3
4
非关系型数据库中存储
{"姓名":"水冰月","年龄":25,"班级":"六班"}
{"姓名":"星矢","年龄":25,"班级":"三班","班主任":"大空翼"}
1 | 可以在Unix、Linux、Windows上运行数据库服务 |
1 | python、java、php ... ... |
1 | 1. 数据库软件 |
更新apt-get
1 | sudo apt-get update |
把本地已安装软件与刚下载的软件列表进行对比,如果发现已安装软件版本低,则更新
1 | sudo apt-get upgrade |
修复依赖关系
1 | sudo apt-get -f install |
访问源列表中的每个网址,读取软件列表,保存到本地
/var/lib/apt/lists/
服务端启动
1 | 查看MySQL状态 |
客户端连接
1 | mysql -h主机地址 -u用户名 -p密码 |
2、本地连接可省略 -h 选项
1 | mysql -uroot -p123456 |
1 | 1、每条命令必须以 ; 结尾 |
1 | 1、查看已有库 |
1 | 1、数字、字母、下划线,但不能使用纯数字 |
1 | 1、创建表(指定字符集) |
1、所有的数据都是以文件的形式存放在数据库目录下
2、数据库目录 :/var/lib/mysql
1 | 1、insert into 表名 values(值1),(值2),...; |
1 | 1、select * from 表名 [where 条件]; |
方法(通过更改MySQL配置文件实现)
步骤
1 | 1、获取root权限 |
mac上
1 | 配置文件路径 |
1 | 余数 字节 |
1 | create table t5( |
1 | create table t7( |
1 | 1、在表中插入3条记录 |
1 | select * from 表名 |
示例
1 | 1、查询1天以内的记录 |
标量(scalar)
一个标量表示一个单独的数,它不同于线性代数中研究的其他大部分对象(通常是多个数的数组)。我们用斜体表示标量。标量通常被赋予小写的变量名称。
向量(vector)
一个向量表示一组有序排列的数。通过次序中的索引,我们可以确定每个单独的数。通常我们赋予向量粗体的小写变量名称,比如xx。向量中的元素可以通过带脚标的斜体表示。向量$X$的第一个元素是$X_1$,第二个元素是$X_2$,以此类推。我们也会注明存储在向量中的元素的类型(实数、虚数等)。
矩阵(matrix)
矩阵是具有相同特征和纬度的对象的集合,表现为一张二维数据表。其意义是一个对象表示为矩阵中的一行,一个特征表示为矩阵中的一列,每个特征都有数值型的取值。通常会赋予矩阵粗体的大写变量名称,比如$A$。
张量(tensor)
在某些情况下,我们会讨论坐标超过两维的数组。一般地,一个数组中的元素分布在若干维坐标的规则网格中,我们将其称之为张量。使用 $A$ 来表示张量“A”。张量$A$中坐标为$(i,j,k)$的元素记作$A_{(i,j,k)}$。
四者之间关系
标量是0阶张量,向量是一阶张量。举例:
标量就是知道棍子的长度,但是你不会知道棍子指向哪儿。
向量就是不但知道棍子的长度,还知道棍子指向前面还是后面。
张量就是不但知道棍子的长度,也知道棍子指向前面还是后面,还能知道这棍子又向上/下和左/右偏转了多少。
若使用爱因斯坦求和约定(Einstein summation convention),矩阵$A$, $B$相乘得到矩阵$C$可以用下式表示:
$$ a_{ik}*b_{kj}=c_{ij} \tag{1.3-1} $$
其中,$a_{ik}$, $b_{kj}$, $c_{ij}$分别表示矩阵$A, B, C$的元素,$k$出现两次,是一个哑变量(Dummy Variables)表示对该参数进行遍历求和。
而矩阵和向量相乘可以看成是矩阵相乘的一个特殊情况,例如:矩阵$B$是一个$n \times 1$的矩阵。
向量的范数(norm)
定义一个向量为:$\vec{a}=[-5, 6, 8, -10]$。任意一组向量设为$\vec{x}=(x_1,x_2,…,x_N)$。其不同范数求解如下:
$$
\Vert\vec{x}\Vert_1=\sum_{i=1}^N\vert{x_i}\vert
$$
$$
\Vert\vec{x}\Vert_2=\sqrt{\sum_{i=1}^N{\vert{x_i}\vert}^2}
$$
$$
\Vert\vec{x}\Vert_{-\infty}=\min{|{x_i}|}
$$
$$
\Vert\vec{x}\Vert_{+\infty}=\max{|{x_i}|}
$$
$$
L_p=\Vert\vec{x}\Vert_p=\sqrt[p]{\sum_{i=1}^{N}|{x_i}|^p}
$$
矩阵的范数
定义一个矩阵$A=[-1, 2, -3; 4, -6, 6]$。 任意矩阵定义为:$A_{m\times n}$,其元素为 $a_{ij}$。
矩阵的范数定义为
$$
\Vert{A}\Vert_p :=\sup_{x\neq 0}\frac{\Vert{Ax}\Vert_p}{\Vert{x}\Vert_p}
$$
当向量取不同范数时, 相应得到了不同的矩阵范数。
矩阵的1范数(列范数):矩阵的每一列上的元
素绝对值先求和,再从中取个最大的,(列和最大),上述矩阵$A$的1范数先得到$[5,8,9]$,再取最大的最终结果就是:9。
$$
\Vert A\Vert_1=\max_{1\le j\le n}\sum_{i=1}^m|{a_{ij}}|
$$
矩阵的2范数:矩阵$A^TA$的最大特征值开平方根,上述矩阵$A$的2范数得到的最终结果是:10.0623。
$$
\Vert A\Vert_2=\sqrt{\lambda_{max}(A^T A)}
$$
其中, $\lambda_{max}(A^T A)$ 为 $A^T A$ 的特征值绝对值的最大值。
矩阵的无穷范数(行范数):矩阵的每一行上的元素绝对值先求和,再从中取个最大的,(行和最大),上述矩阵$A$的行范数先得到$[6;16]$,再取最大的最终结果就是:16。
$$
\Vert A\Vert_{\infty}=\max_{1\le i \le m}\sum_{j=1}^n |{a_{ij}}|
$$
矩阵的核范数:矩阵的奇异值(将矩阵svd分解)之和,这个范数可以用来低秩表示(因为最小化核范数,相当于最小化矩阵的秩——低秩),上述矩阵A最终结果就是:10.9287。
矩阵的L0范数:矩阵的非0元素的个数,通常用它来表示稀疏,L0范数越小0元素越多,也就越稀疏,上述矩阵$A$最终结果就是:6。
矩阵的L1范数:矩阵中的每个元素绝对值之和,它是L0范数的最优凸近似,因此它也可以表示稀疏,上述矩阵$A$最终结果就是:22。
矩阵的F范数:矩阵的各个元素平方之和再开平方根,它通常也叫做矩阵的L2范数,它的优点在于它是一个凸函数,可以求导求解,易于计算,上述矩阵A最终结果就是:10.0995。
$$
\Vert A\Vert_F=\sqrt{(\sum_{i=1}^m\sum_{j=1}^n{| a_{ij}|}^2)}
$$
$$
\Vert A\Vert_p=\sqrt[p]{(\sum_{i=1}^m\sum_{j=1}^n{| a_{ij}|}^p)}
$$
判定一个矩阵是否为正定,通常有以下几个方面:
导数定义:
导数(derivative)代表了在自变量变化趋于无穷小的时候,函数值的变化与自变量的变化的比值。几何意义是这个点的切线。物理意义是该时刻的(瞬时)变化率。
注意:在一元函数中,只有一个自变量变动,也就是说只存在一个方向的变化率,这也就是为什么一元函数没有偏导数的原因。在物理学中有平均速度和瞬时速度之说。平均速度有
$$
v=\frac{s}{t}
$$
其中$v$表示平均速度,$s$表示路程,$t$表示时间。这个公式可以改写为
$$
\bar{v}=\frac{\Delta s}{\Delta t}=\frac{s(t_0+\Delta t)-s(t_0)}{\Delta t}
$$
其中$\Delta s$表示两点之间的距离,而$\Delta t$表示走过这段距离需要花费的时间。当$\Delta t$趋向于0($\Delta t \to 0$)时,也就是时间变得很短时,平均速度也就变成了在$t_0$时刻的瞬时速度,表示成如下形式:
$$
v(t_0)=\lim_{\Delta t \to 0}{\bar{v}}=\lim_{\Delta t \to 0}{\frac{\Delta s}{\Delta t}}=\lim_{\Delta t \to 0}{\frac{s(t_0+\Delta t)-s(t_0)}{\Delta t}}
$$
实际上,上式表示的是路程$s$关于时间$t$的函数在$t=t_0$处的导数。一般的,这样定义导数:如果平均变化率的极限存在,即有
$$
\lim_{\Delta x \to 0}{\frac{\Delta y}{\Delta x}}=\lim_{\Delta x \to 0}{\frac{f(x_0+\Delta x)-f(x_0)}{\Delta x}}
$$
则称此极限为函数 $y=f(x)$ 在点 $x_0$ 处的导数。记作 $f’(x_0)$ 或 $y’\vert_{x=x_0}$ 或 $\frac{dy}{dx}\vert_{x=x_0}$ 或 $\frac{df(x)}{dx}\vert_{x=x_0}$。
通俗地说,导数就是曲线在某一点切线的斜率。
偏导数:
既然谈到偏导数(partial derivative),那就至少涉及到两个自变量。以两个自变量为例,$z=f(x,y)$,从导数到偏导数,也就是从曲线来到了曲面。曲线上的一点,其切线只有一条。但是曲面上的一点,切线有无数条。而偏导数就是指多元函数沿着坐标轴的变化率。
注意:直观地说,偏导数也就是函数在某一点上沿坐标轴正方向的的变化率。
设函数$z=f(x,y)$在点$(x_0,y_0)$的领域内有定义,当$y=y_0$时,$z$可以看作关于$x$的一元函数$f(x,y_0)$,若该一元函数在$x=x_0$处可导,即有
$$
\lim_{\Delta x \to 0}{\frac{f(x_0+\Delta x,y_0)-f(x_0,y_0)}{\Delta x}}=A
$$
函数的极限$A$存在。那么称$A$为函数$z=f(x,y)$在点$(x_0,y_0)$处关于自变量$x$的偏导数,记作$f_x(x_0,y_0)$或$\frac{\partial z}{\partial x}\vert_{y=y_0}^{x=x_0}$或$\frac{\partial f}{\partial x}\vert_{y=y_0}^{x=x_0}$或$z_x\vert_{y=y_0}^{x=x_0}$。
偏导数在求解时可以将另外一个变量看做常数,利用普通的求导方式求解,比如$z=3x^2+xy$关于$x$的偏导数就为$z_x=6x+y$,这个时候$y$相当于$x$的系数。
某点$(x_0,y_0)$处的偏导数的几何意义为曲面$z=f(x,y)$与面$x=x_0$或面$y=y_0$交线在$y=y_0$或$x=x_0$处切线的斜率。
导数和偏导没有本质区别,如果极限存在,都是当自变量的变化量趋于0时,函数值的变化量与自变量变化量比值的极限。
- 一元函数,一个$y$对应一个$x$,导数只有一个。
- 二元函数,一个$z$对应一个$x$和一个$y$,有两个导数:一个是$z$对$x$的导数,一个是$z$对$y$的导数,称之为偏导。
- 求偏导时要注意,对一个变量求导,则视另一个变量为常数,只对改变量求导,从而将偏导的求解转化成了一元函数的求导。
特征值分解可以得到特征值(eigenvalues)与特征向量(eigenvectors);
特征值表示的是这个特征到底有多重要,而特征向量表示这个特征是什么。
如果说一个向量$\vec{v}$是方阵$A$的特征向量,将一定可以表示成下面的形式:
$$
A\nu = \lambda \nu
$$
$\lambda$为特征向量$\vec{v}$对应的特征值。特征值分解是将一个矩阵分解为如下形式:
$$
A=Q\sum Q^{-1}
$$
其中,$Q$是这个矩阵$A$的特征向量组成的矩阵,$\sum$是一个对角矩阵,每一个对角线元素就是一个特征值,里面的特征值是由大到小排列的,这些特征值所对应的特征向量就是描述这个矩阵变化方向(从主要的变化到次要的变化排列)。也就是说矩阵$A$的信息可以由其特征值和特征向量表示。
那么奇异值和特征值是怎么对应起来的呢?我们将一个矩阵$A$的转置乘以$A$,并对$A^TA$求特征值,则有下面的形式:
$$
(A^TA)V = \lambda V
$$
这里$V$就是上面的右奇异向量,另外还有:
$$
\sigma_i = \sqrt{\lambda_i}, u_i=\frac{1}{\sigma_i}A\mu_i
$$
这里的$\sigma$就是奇异值,$u$就是上面说的左奇异向量。【证明那个哥们也没给】
奇异值$\sigma$跟特征值类似,在矩阵$\sum$中也是从大到小排列,而且$\sigma$的减少特别的快,在很多情况下,前10%甚至1%的奇异值的和就占了全部的奇异值之和的99%以上了。也就是说,我们也可以用前$r$($r$远小于$m、n$)个的奇异值来近似描述矩阵,即部分奇异值分解:
$$
A_{m\times n}\approx U_{m \times r}\sum_{r\times r}V_{r \times n}^T
$$
右边的三个矩阵相乘的结果将会是一个接近于$A$的矩阵,在这儿,$r$越接近于$n$,则相乘的结果越接近于$A$。
事件的概率是衡量该事件发生的可能性的量度。虽然在一次随机试验中某个事件的发生是带有偶然性的,但那些可在相同条件下大量重复的随机试验却往往呈现出明显的数量规律。
机器学习除了处理不确定量,也需处理随机量。不确定性和随机性可能来自多个方面,使用概率论来量化不确定性。
概率论在机器学习中扮演着一个核心角色,因为机器学习算法的设计通常依赖于对数据的概率假设。
例如在机器学习(Andrew Ng)的课中,会有一个朴素贝叶斯假设就是条件独立的一个例子。该学习算法对内容做出假设,用来分辨电子邮件是否为垃圾邮件。假设无论邮件是否为垃圾邮件,单词x出现在邮件中的概率条件独立于单词y。很明显这个假设不是不失一般性的,因为某些单词几乎总是同时出现。然而,最终结果是,这个简单的假设对结果的影响并不大,且无论如何都可以让我们快速判别垃圾邮件。
随机变量(random variable)
表示随机现象(在一定条件下,并不总是出现相同结果的现象称为随机现象)中各种结果的实值函数(一切可能的样本点)。例如某一时间内公共汽车站等车乘客人数,电话交换台在一定时间内收到的呼叫次数等,都是随机变量的实例。
随机变量与模糊变量的不确定性的本质差别在于,后者的测定结果仍具有不确定性,即模糊性。
变量与随机变量的区别:
当变量的取值的概率不是1时,变量就变成了随机变量;当随机变量取值的概率为1时,随机变量就变成了变量。
比如:
当变量$x$值为100的概率为1的话,那么$x=100$就是确定了的,不会再有变化,除非有进一步运算.
当变量$x$的值为100的概率不为1,比如为50的概率是0.5,为100的概率是0.5,那么这个变量就是会随不同条件而变化的,是随机变量,取到50或者100的概率都是0.5,即50%。
一个随机变量仅仅表示一个可能取得的状态,还必须给定与之相伴的概率分布来制定每个状态的可能性。用来描述随机变量或一簇随机变量的每一个可能的状态的可能性大小的方法,就是 概率分布(probability distribution).
随机变量可以分为离散型随机变量和连续型随机变量。
相应的描述其概率分布的函数是
概率质量函数(Probability Mass Function, PMF):描述离散型随机变量的概率分布,通常用大写字母 $P$表示。
概率密度函数(Probability Density Function, PDF):描述连续型随机变量的概率分布,通常用小写字母$p$表示。
PMF 将随机变量能够取得的每个状态映射到随机变量取得该状态的概率。
PMF 可以同时作用于多个随机变量,即联合概率分布(joint probability distribution) $P(X=x,Y=y)$*表示 $X=x$和$Y=y$同时发生的概率,也可以简写成 $P(x,y)$.
如果一个函数$P$是随机变量 $X$ 的 PMF, 那么它必须满足如下三个条件
如果一个函数$p$是x的PDF,那么它必须满足如下几个条件
注:PDF$p(x)$并没有直接对特定的状态给出概率,给出的是密度,相对的,它给出了落在面积为 $δx$的无线小的区域内的概率为$ p(x)δx$. 由此,我们无法求得具体某个状态的概率,我们可以求得的是 某个状态 $x$ 落在 某个区间$[a,b]$内的概率为$ \int_{a}^{b}p(x)dx$.
条件概率公式如下:
$$
P(A|B) = P(A\cap B) / P(B)
$$
说明:在同一个样本空间$\Omega$中的事件或者子集$A$与$B$,如果随机从$\Omega$中选出的一个元素属于$B$,那么下一个随机选择的元素属于$A$ 的概率就定义为在$B$的前提下$A$的条件概率。条件概率文氏图示意如图1.1所示。
图1.1 条件概率文氏图示意
根据文氏图,可以很清楚地看到在事件B发生的情况下,事件A发生的概率就是$P(A\bigcap B)$除以$P(B)$。
举例:一对夫妻有两个小孩,已知其中一个是女孩,则另一个是女孩子的概率是多少?(面试、笔试都碰到过)
穷举法:已知其中一个是女孩,那么样本空间为男女,女女,女男,则另外一个仍然是女生的概率就是1/3。
条件概率法:$P(女|女)=P(女女)/P(女)$,夫妻有两个小孩,那么它的样本空间为女女,男女,女男,男男,则$P(女女)$为1/4,$P(女)= 1-P(男男)=3/4$,所以最后$1/3$。
这里大家可能会误解,男女和女男是同一种情况,但实际上类似姐弟和兄妹是不同情况。
区别:
联合概率:联合概率指类似于$P(X=a,Y=b)$这样,包含多个条件,且所有条件同时成立的概率。联合概率是指在多元的概率分布中多个随机变量分别满足各自条件的概率。
边缘概率:边缘概率是某个事件发生的概率,而与其它事件无关。边缘概率指类似于$P(X=a)$,$P(Y=b)$这样,仅与单个随机变量有关的概率。
联系:
联合分布可求边缘分布,但若只知道边缘分布,无法求得联合分布。
由条件概率的定义,可直接得出下面的乘法公式:
乘法公式 设$A, B$是两个事件,并且$P(A) > 0$, 则有
$$
P(AB) = P(B|A)P(A)
$$
推广
$$
P(ABC)=P(C|AB)P(B|A)P(A)
$$
一般地,用归纳法可证:若$P(A_1A_2…A_n)>0$,则有
$$
P(A_1A_2…A_n)=P(A_n|A_1A_2…A_{n-1})P(A_{n-1}|A_1A_2…A_{n-2})…P(A_2|A_1)P(A_1)
=P(A_1)\prod_{i=2}^{n}P(A_i|A_1A_2…A_{i-1})
$$
任何多维随机变量联合概率分布,都可以分解成只有一个变量的条件概率相乘形式。
独立性
两个随机变量$x$和$y$,概率分布表示成两个因子乘积形式,一个因子只包含$x$,另一个因子只包含$y$,两个随机变量相互独立(independent)。
条件有时为不独立的事件之间带来独立,有时也会把本来独立的事件,因为此条件的存在,而失去独立性。
举例:$P(XY)=P(X)P(Y)$, 事件$X$和事件$Y$独立。此时给定$Z$,
$$
P(X,Y|Z) \not = P(X|Z)P(Y|Z)
$$
事件独立时,联合概率等于概率的乘积。这是一个非常好的数学性质,然而不幸的是,无条件的独立是十分稀少的,因为大部分情况下,事件之间都是互相影响的。
条件独立性
给定$Z$的情况下,$X$和$Y$条件独立,当且仅当
$$
X\bot Y|Z \iff P(X,Y|Z) = P(X|Z)P(Y|Z)
$$
$X$和$Y$的关系依赖于$Z$,而不是直接产生。
举例定义如下事件:
$X$:明天下雨;
$Y$:今天的地面是湿的;
$Z$:今天是否下雨;
$Z$事件的成立,对$X$和$Y$均有影响,然而,在$Z$事件成立的前提下,今天的地面情况对明天是否下雨没有影响。
Bernoulli分布是单个二值随机变量分布, 单参数$\phi$∈[0,1]控制,$\phi$给出随机变量等于1的概率. 主要性质有:
$$
\begin{align*}
P(x=1) &= \phi \
P(x=0) &= 1-\phi \
P(x=x) &= \phi^x(1-\phi)^{1-x} \
\end{align*}
$$
其期望和方差为:
$$
\begin{align*}
E_x[x] &= \phi \
Var_x(x) &= \phi{(1-\phi)}
\end{align*}
$$
Multinoulli分布也叫范畴分布, 是单个k值随机分布,经常用来表示对象分类的分布. 其中$k$是有限值.Multinoulli分布由向量$\vec{p}\in[0,1]^{k-1}$参数化,每个分量$p_i$表示第$i$个状态的概率, 且$p_k=1-1^Tp$.
适用范围: 伯努利分布适合对离散型随机变量建模.
高斯也叫正态分布(Normal Distribution), 概率度函数如下:
$$
N(x;\mu,\sigma^2) = \sqrt{\frac{1}{2\pi\sigma^2}}exp\left ( -\frac{1}{2\sigma^2}(x-\mu)^2 \right )
$$
其中, $\mu$和$\sigma$分别是均值和方差, 中心峰值x坐标由$\mu$给出, 峰的宽度受$\sigma$控制, 最大点在$x=\mu$处取得, 拐点为$x=\mu\pm\sigma$
正态分布中,±1$\sigma$、±2$\sigma$、±3$\sigma$下的概率分别是68.3%、95.5%、99.73%,这3个数最好记住。
此外, 令$\mu=0,\sigma=1$高斯分布即简化为标准正态分布:
$$
N(x;\mu,\sigma^2) = \sqrt{\frac{1}{2\pi}}exp\left ( -\frac{1}{2}x^2 \right )
$$
对概率密度函数高效求值:
$$
N(x;\mu,\beta^{-1})=\sqrt{\frac{\beta}{2\pi}}exp\left(-\frac{1}{2}\beta(x-\mu)^2\right)
$$
其中,$\beta=\frac{1}{\sigma^2}$通过参数$\beta∈(0,\infty)$来控制分布精度。
问: 何时采用正态分布?
答: 缺乏实数上分布的先验知识, 不知选择何种形式时, 默认选择正态分布总是不会错的, 理由如下:
正态分布的推广:
正态分布可以推广到$R^n$空间, 此时称为多位正态分布, 其参数是一个正定对称矩阵$\Sigma$:
$$
N(x;\vec\mu,\Sigma)=\sqrt{\frac{1}{(2\pi)^ndet(\Sigma)}}exp\left(-\frac{1}{2}(\vec{x}-\vec{\mu})^T\Sigma^{-1}(\vec{x}-\vec{\mu})\right)
$$
对多为正态分布概率密度高效求值:
$$
N(x;\vec{\mu},\vec\beta^{-1}) = \sqrt{det(\vec\beta)}{(2\pi)^n}exp\left(-\frac{1}{2}(\vec{x}-\vec\mu)^T\beta(\vec{x}-\vec\mu)\right)
$$
此处,$\vec\beta$是一个精度矩阵。
深度学习中, 指数分布用来描述在$x=0$点处取得边界点的分布, 指数分布定义如下:
$$
p(x;\lambda)=\lambda I_{x\geq 0}exp(-\lambda{x})
$$
指数分布用指示函数$I_{x\geq 0}$来使$x$取负值时的概率为零。
一个联系紧密的概率分布是 Laplace 分布(Laplace distribution),它允许我们在任意一点 $\mu$处设置概率质量的峰值
$$
Laplace(x;\mu;\gamma)=\frac{1}{2\gamma}exp\left(-\frac{|x-\mu|}{\gamma}\right)
$$
Dirac分布可保证概率分布中所有质量都集中在一个点上. Diract分布的狄拉克$\delta$函数(也称为单位脉冲函数)定义如下:
$$
p(x)=\delta(x-\mu), x\neq \mu
$$
$$
\int_{a}^{b}\delta(x-\mu)dx = 1, a < \mu < b
$$
Dirac 分布经常作为 经验分布(empirical distribution)的一个组成部分出现
$$
\hat{p}(\vec{x})=\frac{1}{m}\sum_{i=1}^{m}\delta(\vec{x}-{\vec{x}}^{(i)})
$$
, 其中, m个点$x^{1},…,x^{m}$是给定的数据集, 经验分布将概率密度$\frac{1}{m}$赋给了这些点.
当我们在训练集上训练模型时, 可以认为从这个训练集上得到的经验分布指明了采样来源.
适用范围: 狄拉克δ函数适合对连续型随机变量的经验分布.
在概率论和统计学中,数学期望(或均值,亦简称期望)是试验中每次可能结果的概率乘以其结果的总和。它反映随机变量平均取值的大小。
注意:
- 函数的期望大于等于期望的函数(Jensen不等式),即$E(f(x))\geqslant f(E(x))$
- 一般情况下,乘积的期望不等于期望的乘积。
- 如果$X$和$Y$相互独立,则$E(xy)=E(x)E(y)$。
概率论中方差用来度量随机变量和其数学期望(即均值)之间的偏离程度。方差是一种特殊的期望。定义为:
$$
Var(x) = E((x-E(x))^2)
$$
方差性质:
1)$Var(x) = E(x^2) -E(x)^2$
2)常数的方差为0;
3)方差不满足线性性质;
4)如果$X$和$Y$相互独立, $Var(ax+by)=a^2Var(x)+b^2Var(y)$
协方差是衡量两个变量线性相关性强度及变量尺度。 两个随机变量的协方差定义为:
$$
Cov(x,y)=E((x-E(x))(y-E(y)))
$$
方差是一种特殊的协方差。当$X=Y$时,$Cov(x,y)=Var(x)=Var(y)$。
协方差性质:
1)独立变量的协方差为0。
2)协方差计算公式:
$$
Cov(\sum_{i=1}^{m}{a_ix_i}, \sum_{j=1}^{m}{b_jy_j}) = \sum_{i=1}^{m} \sum_{j=1}^{m}{a_ib_jCov(x_iy_i)}
$$
3)特殊情况:
$$
Cov(a+bx, c+dy) = bdCov(x, y)
$$
相关系数是研究变量之间线性相关程度的量。两个随机变量的相关系数定义为:
$$
Corr(x,y) = \frac{Cov(x,y)}{\sqrt{Var(x)Var(y)}}
$$
]]>相关系数的性质:
1)有界性。相关系数的取值范围是 [-1,1],可以看成无量纲的协方差。
2)值越接近1,说明两个变量正相关性(线性)越强。越接近-1,说明负相关性越强,当为0时,表示两个变量没有相关性。
1 | import cv2 as cv |
从整个画面中定位人脸的位置和范围
]]>