realm使用笔记

准备换数据库realm
环境xcode8.1 swift oc 混编 官方没有太多这样的说明 ,就是用oc 的库导入一个swift文件然后调用吧

文档页面:
https://realm.io/docs/objc/latest/

####1 创建数据库文件,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/// 创建数据库文件
///
/// - Parameter dataName: dataName 数据库文件名称
func createRleam(dataName:String){
let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
if FileManager.default.fileExists(atPath: "\(path!)/realm") == false{
try! FileManager.default.createDirectory(atPath: "\(path!)/realm", withIntermediateDirectories: true, attributes: nil)
}
let filePath = path! + "/realm/\(dataName)"
let config = RLMRealmConfiguration.default()
config.fileURL = URL(string: filePath)
config.readOnly = false
let currentVersion:UInt64 = 1
config.schemaVersion = currentVersion
config.migrationBlock = { (migration,oldSchemaVersion) in
// 数据迁移的block
if oldSchemaVersion < currentVersion{
}
}
RLMRealmConfiguration.setDefault(config)
//获取父级路径, 更改文件保护模式
let folderPath = RLMRealm.default().configuration.fileURL?.deletingLastPathComponent().path
Getdevice.println("父级路径\(folderPath)")
if let folderPath = folderPath{
try? FileManager.default.setAttributes([FileAttributeKey.protectionKey:FileProtectionType.none], ofItemAtPath: folderPath)
}
}

####2 创建 一个数据模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class RLMHome: RLMObject {
override init() {
super.init()
}
override init(value: Any) {
super.init(value: value)
}
/// 索引值 一直用0 来创建
dynamic var index = ""
dynamic var homedata:Data?
//设置忽略属性,即不存到realm数据库中
override class func primaryKey()->String{
return "index"
}
//一般来说,属性为nil的话realm会抛出异常,但是如果实现了这个方法的话,就只有name为nil会抛出异常,也就是说现在cover属性可以为空了
override class func requiredProperties()->Array<String>{
return ["index"]
}
//设置索引,可以加快检索的速度
override class func indexedProperties()->Array<String>{
return ["index"]
}
//设置属性默认值
// override class func defaultPropertyValues()->[AnyHashable : Any]?{
// return ["index":"0","homedata":Data()]
// }
}

####3 数据的增 或者更新

1
2
3
4
5
6
7
8
9
10
let home = RLMHome()
home.index = "0"
let data = try? json.rawData()
home.homedata = data let realm = RLMRealm.default()
realm.beginWriteTransaction()
// 或者独立的增加add 就好有提示 add 的话 主键肯定不能相同,大家都懂得
// 这个是更新 根据主键的
RLMHome.createOrUpdate(in: realm,withValue: home)
try? realm.commitWriteTransaction()

####4 数据的查询

1
2
3
4
5
6
7
8
9
10
//查询所有 这个数据查询是没有分页的,因为懒加载,用得时候在读取,所以分页自己分楼
let homes = RLMHome.allObjects()
if homes.count>0 && (homes[0] as! RLMHome).homedata != nil{
let data = (homes[0] as! RLMHome).homedata
let json = JSON(data: data!)
if json["code"].intValue == CompleteCode{
complete(json)
return
}

查询遇到的坑, “id == 123” ,id为主键, 查询失败 得id == ‘123’ , 在另外一个地方非主键查询得不嫁单引号,

数据更新和结构问题

1
self.downDataCompleteArray = RLMDownloads.objects(where: "downStatus == 1") as! RLMResults<RLMDownloads>

结构 RLMResults<自定义>
类似数组但是不是数组, 如果用他来更新ui 或者说realm数据库更新ui 都得用他们的消息机制,接收到更改刷新ui
比如我的

1
2
3
4
5
self.RLMNotificationTokenReadun = self.downDataUnderwayArray.addNotificationBlock { [weak self] (results:RLMResults<RLMDownloads>?, change:RLMCollectionChange? , error) in
if change != nil{
self?.underwayTableView.reloadData()
}
}

deinit 的时候self.RLMNotificationTokenReadun.stop()一下

可以说方便了,但是和原有项目数据结构相差还是比较大得

遇到的小问题 1:
数据更新问题, 开始整体更新

1
2
3
4
5
6
7
8
9
10
11
12
home = RLMHome()
home.index = "0"
// home.adData = nil
home.homedata = data
DispatchQueue(label: "rleam").async {
let realm = RLMRealm.default()
realm.beginWriteTransaction()
//realm.addOrUpdate(home)
RLMHome.createOrUpdate(in: realm,withValue: home)
try? realm.commitWriteTransaction()
}
}

没问题 ,后来准备局部更新 , 发现这样方式失败 , 就算查询出来更改同样崩溃 后来发现api 上有个更新

let realm = RLMRealm.default()
                     try? realm.transaction{ () in
                         home.homedata = data
                     }

IOS自动打包研究

//自动打包sh https://github.com/jkpang/PPAutoPackageScript

注 最后问题解决使用手动导出的plist文件 并且 把
<key>compileBitcode</key> <false/>
为false 就能打包成功了

xcode 9 了 自动打包也出现问题了, 找到一个文章: 注 但是我这里打包还是出问题,但是离成功更近一步

From: http://www.jianshu.com/p/9bae9e433035

Xcode

是不是很开心终于升级Xcode9了。
是不是上传Fir发现错误内心崩溃了。
是不是在满大街查找解决方法。

自动上传脚本,保存到项目的目录下,使用sh *.sh -u 进行上传,其中的fir的token和项目的名称需要修改,其他的等报错再修改吧。详见如下:

#/bin/sh
#coding utf-8
#上传模块需要FIR.im CLI 
#安装gem install fir-cli
#token 获取 http://fir.im/user/info

#安静模式,不输出多余log
quiet=1

while getopts "huv" arg #选项后面的冒号表示该选项需要参数
do
    case $arg in
         t)
            echo "t's arg:$OPTARG" #参数存在$OPTARG中
            ;;
         u)
            upload=1
            ;;
         v)
            quiet=0
            ;;

         h)
            echo Commands:
            echo "    make -u        #build ipa and upload fir.im"
            ;;
         ?)  #当有不认识的选项的时候arg为?
        echo "unkonw argument"
    ;;
    esac
done

token="需要替换"  #token 获取 http://fir.im/user/info

echo '--------------start----------------'
echo '>> clean...'
proj=$(cd $(dirname ${0}) ; pwd -P)
xcodebuild clean 1>/dev/null
project=需要替换
product="$proj/build/$project.ipa"
rm $product

echo '>> build...'
if [[ $quiet == 1 ]]
then
    xcodebuild -workspace "$project.xcworkspace" -scheme "$project" archive -archivePath $proj/build/$project.xcarchive -configuration Ad-hoc -sdk iphoneos >/dev/null
else
    xcodebuild -workspace "$project.xcworkspace" -scheme "$project" archive -archivePath $proj/build/$project.xcarchive -configuration Ad-hoc -sdk iphoneos
fi

echo '>> create ipa...'

xcodebuild -exportArchive -archivePath $proj/build/$project.xcarchive -exportOptionsPlist exportOptions.plist -exportPath "$proj/build"

#copy dsym to xcarchives
echo '>> archive dsym...'
if [[ -d $proj/build/$project.xcarchive ]]
then
    filename=$(date "+%Y%m%d%H%M.%S")
    mkdir -p "$proj/build/archives"
    cp -r $proj/build/$project.xcarchive/ "$proj/build/archives/$filename.xcarchive"
    cp "$product" "$proj/build/archives/$filename.xcarchive"
fi

if [[ $upload == 1 ]] && [[ -f "$product" ]]
then
    fir l $token
    fir p "$product"
    clear
    fir i "$product"
else
    open "$proj/build"
fi

使用之前的Fir自动上传脚本,突然发现报错了,可在升级Xcode 9之前明明还是好的呢,So 只能想办法解决。先看下报错日志:

2017-09-20 14:22:07.140 xcodebuild[31386:364151] [MT] IDEDistribution: Step failed: <IDEDistributionSigningAssetsStep: 0x7f8cdcc95b90>: Error Domain=IDEDistributionSigningAssetStepErrorDomain Code=0 "Locating signing assets failed." UserInfo={NSLocalizedDescription=Locating signing assets failed., IDEDistributionSigningAssetStepUnderlyingErrors=(
    "Error Domain=IDEProvisioningErrorDomain Code=9 \"\"name.app\" requires a provisioning profile with the Push Notifications feature.\" UserInfo={NSLocalizedDescription=\"name.app\" requires a provisioning profile with the Push Notifications feature., NSLocalizedRecoverySuggestion=Add a profile to the \"provisioningProfiles\" dictionary in your Export Options property list.}"
)}
error: exportArchive: "name.app" requires a provisioning profile with the Push Notifications feature.

Error Domain=IDEProvisioningErrorDomain Code=9 ""name.app" requires a provisioning profile with the Push Notifications feature." UserInfo={NSLocalizedDescription="name.app" requires a provisioning profile with the Push Notifications feature., NSLocalizedRecoverySuggestion=Add a profile to the "provisioningProfiles" dictionary in your Export Options property list.}

** EXPORT FAILED **

第一反应是不是重新制作一遍Push证书,是的吧。我也是这样想的,然后发现然并卵。

自动脚本发现不可行时,我想到的方法是那只能手动了。选择相应的Build Configuration进行Building生成*.app.

切换编译配置

切换编译配置

然后获取到.app,将这个文件拖到iTunes上进行.app转*.ipa,是吧。

这个一个坑啊。,你会发现怎么找不到“应用程序”的选项啊。
iTunes的最新版本已经将app Store的功能取消了,已经没有应用程序的选项了。折磨了大半天,发现None is None。

最后只能使用最后的方法了,使用Xcode的Archive再导出ipa包。与上传App Store的方法类似。(不会给我留言哈)
最后生成的文件有:

ipa文件

ipa文件

将生成的*.ipa文件上传到Fir上,工作完成。

首先是不是觉得怎么多了3个文件啊,呵呵哒。这就是Xcode的改变啊。主要变化是多了ExportOptions文件,这个应该与之前的报错有关。
其次找到了自动上传的方法了,将这个ExportOptions的文件放到项目中,我的目录是

9BCA754A-C6AF-4529-9360-8453E0ADD652.png

9BCA754A-C6AF-4529-9360-8453E0ADD652.png

然后在执行自动化上传Fir脚本成功。

发现在Xcode 9中,exportOptions.plist的变化,

之前

之前

之后

之后

之后

主要多了provisioningProfiles, signingCertificate和signingStyle。针对自动化脚本的报错,应该是少了provisioningProfiles的属性。
按图片手动添加所有属性就可以执行成功了,当然你也可以先使用Xcode导出一次获取到exportOptions.plist文件。

每次升级系统或Xcode都会有一天的时间是在等待和解决问题。今天iOS11 还碰到了因为使用了WebViewJavascriptBridge第三方库导致奔溃的问题,又是忙了一阵,解决方法:

WebViewJavascriptBridgeBase *base = [[WebViewJavascriptBridgeBase alloc] init];
        if ([base isWebViewJavascriptBridgeURL:navigationAction.request.URL]) {
            DLog(@"isWebViewJavascriptBridgeURL is YES.");
            return;
        }
decisionHandler(WKNavigationActionPolicyAllow);

END


最近在发现每次打开发包测试非常的耗时和麻烦 , 发布包就无所谓了现在需求就一个包,
然后研究了下自动打包,google 了一番各种尝试 然后实行了一个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/bin/bash
SCHEMENAME=boosjdance
BRANCHNAME=develop
DATE=$(date +"%Y_%m_%d_%H_%M_%S")
SOURCEPATH=$( cd "$( dirname $0 )" && pwd)
#打包ipa 文件目录
IPAPATH=$SOURCEPATH/AutoBuildIPA/$BRANCHNAME/$DATE
#打包ipa 文件名字
IPANAME=boosjdance_$DATE.ipa
#delete trash files
if [ -e $IPAPATH/* ]; then
mv $IPAPATH/* ~/.Trash
if [ $? -ne 0 ]; then
echo "error : delete trash files!!"
exit 1
fi
fi
# build boosjdance
xcodebuild \
-workspace $SOURCEPATH/boosjdance.xcworkspace \
-scheme $SCHEMENAME \
-configuration Debug \
clean \
build \
-derivedDataPath $IPAPATH
if [ -e $IPAPATH ]; then
echo "xcodebuild Successful"
else
echo "error:Build failde!!"
exit 1
fi
#xcrun .ipa
xcrun -sdk iphoneos PackageApplication \
-v $IPAPATH/Build/Products/Debug-iphoneos/$SCHEMENAME.app \
-o $IPAPATH/$IPANAME
if [ -e $IPAPATH/$IPANAME ]; then
echo "\n ---------------------------------------\n\n\n"
echo "configuration! build SuccessFul!"
echo "\n-----------------------------\n\n"
echo "Current Branch log:"
open $IPAPATH
else
echo "\n-----------------------------\n"
echo "error:IPA failed!"
echo "\n------------------------\n"
fi

build.sh 文件,放在项目目录
cd 到目录 sh build.sh
运行
OK了,发现很多文章说配置文件的路径 和名字问题, 这个sh 没有这样的配置,还能是默认的,反正实现了我的自动打包,以后在研究 ,


优化了下代码打包正式包 ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#!/bin/bash
#xcodebuild -list 可查
SCHEMENAME=boosjdance
# 文件地址
BRANCHNAME=develop
DATE=$(date +"%Y_%m_%d_%H_%M_%S")
SOURCEPATH=$( cd "$( dirname $0 )" && pwd)
#打包ipa 文件目录
IPAPATH=$SOURCEPATH/AutoBuildIPA/$BRANCHNAME/$DATE
#打包ipa 文件名字
IPANAME=boosjdance_$DATE.ipa
#delete trash files
if [ -e $IPAPATH/* ]; then
mv $IPAPATH/* ~/.Trash
if [ $? -ne 0 ]; then
echo "error : delete trash files!!"
exit 1
fi
fi
# $1 (发现传入不了写死 后面会根据这个参数去判断path路径)表示传入的第一个参数,启动脚本传入Debug或者Release就可以
CONGRUATION=Debug
# 下面两个可写 可不写 ,写在 configuration 下面 可以定义参数判断是正式的还是测试的
# 因为是自己可以自动识别就不写了位置在xcode 上找到相应的位置上看设置 注,写了报错
#CODE_SIGN_IDENTITY="iPhone Developer: Cheng'en hu (xxxxxxxxx)" \
#PROVISIONING_PROFILE="boosjdance" \
# build boosjdance
xcodebuild \
-workspace $SOURCEPATH/boosjdance.xcworkspace \
-scheme $SCHEMENAME \
-configuration $CONGRUATION \
clean \
build \
-derivedDataPath $IPAPATH
if [ -e $IPAPATH ]; then
echo "xcodebuild Successful"
else
echo "error:Build failde!!"
exit 1
fi
#xcrun .ipa
if [ $CONGRUATION == Debug ]; then
IPhoneosPath=Debug-iphoneos
else
IPhoneosPath=Release-iphoneos
fi
xcrun -sdk iphoneos PackageApplication \
-v $IPAPATH/Build/Products/$IPhoneosPath/$SCHEMENAME.app \
-o $IPAPATH/$IPANAME
if [ -e $IPAPATH/$IPANAME ]; then
echo "\n ---------------------------------------\n\n\n"
echo "configuration! build SuccessFul!"
echo "\n-----------------------------\n\n"
echo "Current Branch log:"
open $IPAPATH
else
echo "\n-----------------------------\n"
echo "error:IPA failed!"
echo "\n------------------------\n"
fi

迷你版本无打包方式不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
WORKSPACE="boosjdance.xcworkspace"
SCHEME="boosjdance"
# Release or Debug
CONFIGURATION="Debug"
RELEASE_BUILDDIR="Build/Products/Debug-iphoneos"
APPLICATION_NAME="boosjdance"
# 输出目录
BUILD_OUTPUT_DIR="buildOutput"
# 描述文件名
PROVISONING_PROFILE="boosjdance"
rm -rf $BUILD_OUTPUT_DIR
mkdir $BUILD_OUTPUT_DIR
xcodebuild -workspace $WORKSPACE -scheme $SCHEME -configuration $CONFIGURATION clean
xcrun agvtool new-version -all "${BUILD_NUMBER}"
xcodebuild -workspace $WORKSPACE -scheme $SCHEME -configuration $CONFIGURATION archive -archivePath "${BUILD_OUTPUT_DIR}/${APPLICATION_NAME}.xcarchive"
cd $BUILD_OUTPUT_DIR
7z a -t7z -m0=lzma -mx=9 -mfb=64 -md=32m -ms=on "${APPLICATION_NAME}.xcarchive.7z" "${APPLICATION_NAME}.xcarchive"
cd ..
xcodebuild -exportArchive -exportFormat IPA -exportProvisioningProfile "${PROVISONING_PROFILE}" -archivePath "${BUILD_OUTPUT_DIR}/${APPLICATION_NAME}.xcarchive" -exportPath "${BUILD_OUTPUT_DIR}/${APPLICATION_NAME}.ipa"

2016 双11 更新
自动上传fir.im
安装

1
gem install fir-cli

要是证书问题自行搜索,我重新安装了下更新了下ssl ok了

上传代码跟在上面的后面

1
2
3
4
5
6
7
8
9
# 上传ipa 包
export LANG=en_US
export LC_ALL=en_US;
echo "正在上传到fir.im...."
echo "$IPAPATH/$IPANAME"
#####http://fir.im/api/v2/app/appID?token=APIToken,里面的appID是你要上传应用的appID,APIToken是你fir上的APIToken
fir p $IPAPATH/$IPANAME
curl -X PUT --data "更新日志=$IPANAME" http://fir.im/api/v2/app/5822c666ca87a86919000b6b?token=d929e94bd98485d7c056509ab5f3990f
echo "\n打包上传更新成功!"

– #############上传到蒲公英##############

1
2
3
4
5
6
7
# 上传ipa 包 蒲公英
#上传蒲公英
curl -F "file=@${BUILD_OUTPUT_DIR}/${BUILD_OUTPUT_DIR2}/${APPLICATION_NAME}.ipa" \
-F "uKey=xxxxxxx" \
-F "_api_key=xxxx" \
https://www.pgyer.com/apiv1/app/upload
echo "\n打包上传更新完成 具体看日志!"

发现fir.im 需要ruby 2.2.4 版本 出了一个ssl 错误, 然后升级版本改了pem 文件好了 但是还是觉得是ruby 版本的问题

参考原文简书:
http://www.jianshu.com/p/97c97c2ec1ca

如何在CentOS 6.x/7.x上安装git及最新版

记录下远程登录 方法和解说

1
ssh -p 27304 root@104.194.94.204

远程登录 -p 端口 root 用户名 @ 后面是地址

方式一、yum安装

1
# yum install git

通过yum方式安装,版本比较旧,CentOS6.5上安装好是1.7.1版。如果想安装最新版或其他版本,需要使用源码编译安装的方式。

方式二、源码包安装

步骤1. 安装依赖包

1
2
# yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel
# yum install gcc perl-ExtUtils-MakeMaker

步骤2. 卸载旧的git版本(如果之前有安装rpm包)

1
2
# yum remove git

步骤3. 下载&解压

1
2
3
# cd /usr/src
# wget https://www.kernel.org/pub/software/scm/git/git-2.5.0.tar.gz
# tar -zxvf git-2.5.0.tar.gz

或 # wget https://github.com/git/git/archive/v2.5.0.tar.gz 下载

步骤4. 编译安装

1
2
3
4
5
# cd git-2.5.0
# make prefix=/usr/local/git all
# make prefix=/usr/local/git install
# echo "export PATH=$PATH:/usr/local/git/bin" >> /etc/bashrc
# source /etc/bashrc

步骤5. 检查git版本

1
2
# git --version
git version 2.5.0

转载的
https://my.oschina.net/antsky/blog/514586
// 这个说的时git 使用配置自动部署hexo
http://www.swiftyper.com/2016/04/17/deploy-hexo-with-git-hook/

//配置git 后自动部署hexo
http://www.swiftyper.com/2016/04/17/deploy-hexo-with-git-hook/

// nginx 配置
https://gist.github.com/czboosj/cbbf6a44a8bc8a6802ebc7716ca29ec1

http://www.cnblogs.com/liscookie/p/4032928.html

安装完nginx服务器后发现nginx的根目录在/usr/share/nginx/html/下,但是对于部署文件来说,在该目录下是不太习惯的,我就尝试着更改nginx访问的根目录

1
# vi /etc/nginx/conf.d/default.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#
# The default server
#
server {
listen 80;
server_name localhost;
root /var/www; #修改新的目录为var下的www目录
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root /var/www;
index index.html index.htm index.php index.phtml; #添加index.php和index.phtml
# example
#ModSecurityEnabled on;
#ModSecurityConfig /etc/nginx/modsecurity.conf;
}
error_page 404 /404.html;
location = /404.html {
root /var/www; #修改新的目录文件
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /var/www; #修改新的目录文件
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
location ~ \.php$ {
root /var/www; #修改新的目录文件
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
参考:http://stackoverflow.com/questions/21820715/how-to-install-latest-version-of-git-on-centos-6-x-7-x

IOS 数据持久化 键盘

先上自己整理的 , 原生得找不到方法就NS了下
1 .plist swift 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/// 写数组到plist 文件
///
/// - Parameters:
/// - fileName: fileName 文件名, 不带.plist 后缀
/// - array: <#array description#>
func saveArrayToPlist(fileName:String,array:[Any]){
let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
if path == nil{return}
let fileURLString = path! + "/\(fileName).plist"
(array as NSArray).write(toFile: fileURLString, atomically: true)
}
/// 添加一个数组到 plist 文件 (根据需求,得去掉重复的数据)
///
/// - Parameters:
/// - fileName: <#fileName description#>
/// - array: <#array description#>
func addArrayToPlist(fileName:String,text:String){
let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
if path == nil{return}
let fileURLString = path! + "/\(fileName).plist"
var oldArr = self.redArrayToPlist(fileName: fileName)
for i in 0 ..< oldArr.count{
if (oldArr[i] as! String) == text{
oldArr.remove(at: i)
oldArr.insert(text, at: 0)
(oldArr as NSArray).write(toFile: fileURLString, atomically: true)
return
}
}
oldArr.insert(text, at: 0)
(oldArr as NSArray).write(toFile: fileURLString, atomically: true)
}
/// 读取plist 里面的数据Array类型
///
/// - Parameter fileName: <#fileName description#>
/// - Returns: <#return value description#>
func redArrayToPlist(fileName:String)->[Any]{
let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
if path == nil{return []}
let fileURLString = path! + "/\(fileName).plist"
let array = NSArray(contentsOfFile: fileURLString)
return array == nil ?[]:array as! [Any]
}
/// 删除写入的数据 写入一个空数据进去
///
/// - Parameter fileName: <#fileName description#>
func removeArrayToPlist(fileName:String){
let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
if path == nil{return}
let fileURLString = path! + "/\(fileName).plist"
try? FileManager.default.removeItem(atPath: fileURLString)
// ([] as NSArray).write(toFile: fileURLString, atomically: true)
}

2 配置文件swift3

1
2
3
4
5
6
// 读取
let dic: NSDictionary? = UserDefaults.standard.object(forKey: "UserInfo") as? NSDictionary//读取
// 保存
UserDefaults.standard.set(UserDataDic, forKey: "UserInfo") //
UserDefaults.standard.synchronize()// 立马存入文件

copy 的一些

#概论
所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据。在iOS开发中,有很多数据持久化的方案,接下来我将尝试着介绍一下5种方案:

plist文件(属性列表)
preference(偏好设置)
NSKeyedArchiver(归档)
SQLite 3
CoreData

##沙盒
在介绍各种存储方法之前,有必要说明以下沙盒机制。iOS程序默认情况下只能访问程序自己的目录,这个目录被称为“沙盒”。

###1.结构
既然沙盒就是一个文件夹,那就看看里面有什么吧。沙盒的目录结构如下:

“应用程序包”

1
2
3
4
5
Documents
Library
Caches
Preferences
tmp

###2.目录特性
虽然沙盒中有这么多文件夹,但是没有文件夹都不尽相同,都有各自的特性。所以在选择存放目录时,一定要认真选择适合的目录。

“应用程序包”: 这里面存放的是应用程序的源文件,包括资源文件和可执行文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
NSString *path = [[NSBundle mainBundle] bundlePath];
NSLog(@"%@", path);
Documents: 最常用的目录,iTunes同步该应用时会同步此文件夹中的内容,适合存储重要数据。
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"%@", path);
Library/Caches: iTunes不会同步此文件夹,适合存储体积大,不需要备份的非重要数据。
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"%@", path);
Library/Preferences: iTunes同步该应用时会同步此文件夹中的内容,通常保存应用的设置信息。
tmp: iTunes不会同步此文件夹,系统可能在应用没运行时就删除该目录下的文件,所以此目录适合保存应用中的一些临时文件,用完就删除。
NSString *path = NSTemporaryDirectory();
NSLog(@"%@", path);
plist文件
plist文件是将某些特定的类,通过XML文件的方式保存在目录中。

可以被序列化的类型只有如下几种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;
1.获得文件路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@"123.plist"];
2.存储
NSArray *array = @[@"123", @"456", @"789"];
[array writeToFile:fileName atomically:YES];
3.读取
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@"%@", result);

4.注意
只有以上列出的类型才能使用plist文件存储。
存储时使用writeToFile: atomically:方法。 其中atomically表示是否需要先写入一个辅助文件,再把辅助文件拷贝到目标文件地址。这是更安全的写入文件方法,一般都写YES。
读取时使用arrayWithContentsOfFile:方法。
Preference
1.使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1.获得NSUserDefaults文件
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//2.向文件中写入内容
[userDefaults setObject:@"AAA" forKey:@"a"];
[userDefaults setBool:YES forKey:@"sex"];
[userDefaults setInteger:21 forKey:@"age"];
//2.1立即同步
[userDefaults synchronize];
//3.读取文件
NSString *name = [userDefaults objectForKey:@"a"];
BOOL sex = [userDefaults boolForKey:@"sex"];
NSInteger age = [userDefaults integerForKey:@"age"];
NSLog(@"%@, %d, %ld", name, sex, age);

2.注意
偏好设置是专门用来保存应用程序的配置信息的,一般不要在偏好设置中保存其他数据。
如果没有调用synchronize方法,系统会根据I/O情况不定时刻地保存到文件中。所以如果需要立即写入文件的就必须调用synchronize方法。
偏好设置会将所有数据保存到同一个文件中。即preference目录下的一个以此应用包名来命名的plist文件。
NSKeyedArchiver
归档在iOS中是另一种形式的序列化,只要遵循了NSCoding协议的对象都可以通过它实现序列化。由于决大多数支持存储数据的Foundation和Cocoa Touch类都遵循了NSCoding协议,因此,对于大多数类来说,归档相对而言还是比较容易实现的。

1.遵循NSCoding协议
NSCoding协议声明了两个方法,这两个方法都是必须实现的。一个用来说明如何将对象编码到归档中,另一个说明如何进行解档来获取一个新对象。

遵循协议和设置属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//1.遵循NSCoding协议
@interface Person : NSObject <NSCoding>
//2.设置属性
@property (strong, nonatomic) UIImage *avatar;
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger age;
@end
实现协议方法
//解档
- (id)initWithCoder:(NSCoder *)aDecoder {
if ([super init]) {
self.avatar = [aDecoder decodeObjectForKey:@"avatar"];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
//归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.avatar forKey:@"avatar"];
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}

特别注意

如果需要归档的类是某个自定义类的子类时,就需要在归档和解档之前先实现父类的归档和解档方法。即 [super encodeWithCoder:aCoder] 和 [super initWithCoder:aDecoder] 方法;

2.使用
需要把对象归档是调用NSKeyedArchiver的工厂方法 archiveRootObject: toFile: 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [[Person alloc] init];
person.avatar = self.avatarView.image;
person.name = self.nameField.text;
person.age = [self.ageField.text integerValue];
[NSKeyedArchiver archiveRootObject:person toFile:file];
需要从文件中解档对象就调用NSKeyedUnarchiver的一个工厂方法 unarchiveObjectWithFile: 即可。
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
if (person) {
self.avatarView.image = person.avatar;
self.nameField.text = person.name;
self.ageField.text = [NSString stringWithFormat:@"%ld", person.age];
}

3.注意
必须遵循并实现NSCoding协议
保存文件的扩展名可以任意指定
继承时必须先调用父类的归档解档方法
SQLite3
之前的所有存储方法,都是覆盖存储。如果想要增加一条数据就必须把整个文件读出来,然后修改数据后再把整个内容覆盖写入文件。所以它们都不适合存储大量的内容。

1.字段类型
表面上SQLite将数据分为以下几种类型:

integer : 整数
real : 实数(浮点数)
text : 文本字符串
blob : 二进制数据,比如文件,图片之类的
实际上SQLite是无类型的。即不管你在创表时指定的字段类型是什么,存储是依然可以存储任意类型的数据。而且在创表时也可以不指定字段类型。SQLite之所以什么类型就是为了良好的编程规范和方便开发人员交流,所以平时在使用时最好设置正确的字段类型!主键必须设置成integer

  1. 准备工作
    准备工作就是导入依赖库啦,在iOS中要使用SQLite3,需要添加库文件:libsqlite3.dylib并导入主头文件,这是一个C语言的库,所以直接使用SQLite3还是比较麻烦的。

3.使用
创建数据库并打开

操作数据库之前必须先指定数据库文件和要操作的表,所以使用SQLite3,首先要打开数据库文件,然后指定或创建一张表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 打开数据库并创建一个表
*/
- (void)openDatabase {
//1.设置文件名
NSString *filename = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.db"];
//2.打开数据库文件,如果没有会自动创建一个文件
NSInteger result = sqlite3_open(filename.UTF8String, &_sqlite3);
if (result == SQLITE_OK) {
NSLog(@"打开数据库成功!");
//3.创建一个数据库表
char *errmsg = NULL;
sqlite3_exec(_sqlite3, "CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)", NULL, NULL, &errmsg);
if (errmsg) {
NSLog(@"错误:%s", errmsg);
} else {
NSLog(@"创表成功!");
}
} else {
NSLog(@"打开数据库失败!");
}
}
执行指令
使用 sqlite3_exec() 方法可以执行任何SQL语句,比如创表、更新、插入和删除操作。但是一般不用它执行查询语句,因为它不会返回查询到的数据。
/**
* 往表中插入1000条数据
*/
- (void)insertData {
NSString *nameStr;
NSInteger age;
for (NSInteger i = 0; i < 1000; i++) {
nameStr = [NSString stringWithFormat:@"Bourne-%d", arc4random_uniform(10000)];
age = arc4random_uniform(80) + 20;
NSString *sql = [NSString stringWithFormat:@"INSERT INTO t_person (name, age) VALUES('%@', '%ld')", nameStr, age];
char *errmsg = NULL;
sqlite3_exec(_sqlite3, sql.UTF8String, NULL, NULL, &errmsg);
if (errmsg) {
NSLog(@"错误:%s", errmsg);
}
}
NSLog(@"插入完毕!");
}

查询指令

前面说过一般不使用 sqlite3_exec() 方法查询数据。因为查询数据必须要获得查询结果,所以查询相对比较麻烦。示例代码如下:

sqlite3_prepare_v2() : 检查sql的合法性
sqlite3_step() : 逐行获取查询结果,不断重复,直到最后一条记录
sqlite3_coloum_xxx() : 获取对应类型的内容,iCol对应的就是SQL语句中字段的顺序,从0开始。根据实际查询字段的属性,使用sqlite3_column_xxx取得对应的内容即可。
sqlite3_finalize() : 释放stmt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 从表中读取数据到数组中
*/
- (void)readData {
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1000];
char *sql = "select name, age from t_person;";
sqlite3_stmt *stmt;
NSInteger result = sqlite3_prepare_v2(_sqlite3, sql, -1, &stmt, NULL);
if (result == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
char *name = (char *)sqlite3_column_text(stmt, 0);
NSInteger age = sqlite3_column_int(stmt, 1);
//创建对象
Person *person = [Person personWithName:[NSString stringWithUTF8String:name] Age:age];
[mArray addObject:person];
}
self.dataList = mArray;
}
sqlite3_finalize(stmt);
}

4.总结
总得来说,SQLite3的使用还是比较麻烦的,因为都是些c语言的函数,理解起来有些困难。不过在一般开发过程中,使用的都是第三方开源库 FMDB,封装了这些基本的c语言方法,使得我们在使用时更加容易理解,提高开发效率。

FMDB
1.简介
FMDB是iOS平台的SQLite数据库框架,它是以OC的方式封装了SQLite的C语言API,它相对于cocoa自带的C语言框架有如下的优点:

使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码
对比苹果自带的Core Data框架,更加轻量级和灵活
提供了多线程安全的数据库操作方法,有效地防止数据混乱
注:FMDB的gitHub地址

2.核心类
FMDB有三个主要的类:

FMDatabase
一个FMDatabase对象就代表一个单独的SQLite数据库,用来执行SQL语句

FMResultSet
使用FMDatabase执行查询后的结果集

FMDatabaseQueue
用于在多线程中执行多个查询或更新,它是线程安全的

3.打开数据库
和c语言框架一样,FMDB通过指定SQLite数据库文件路径来创建FMDatabase对象,但FMDB更加容易理解,使用起来更容易,使用之前一样需要导入sqlite3.dylib。打开数据库方法如下:

1
2
3
4
5
6
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.db"];
FMDatabase *database = [FMDatabase databaseWithPath:path];
if (![database open]) {
NSLog(@"数据库打开失败!");
}

值得注意的是,Path的值可以传入以下三种情况:

具体文件路径,如果不存在会自动创建

空字符串@””,会在临时目录创建一个空的数据库,当FMDatabase连接关闭时,数据库文件也被删除

nil,会创建一个内存中临时数据库,当FMDatabase连接关闭时,数据库会被销毁

4.更新
在FMDB中,除查询以外的所有操作,都称为“更新”, 如:create、drop、insert、update、delete等操作,使用executeUpdate:方法执行更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//常用方法有以下3种:
- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
//示例
[database executeUpdate:@"CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)"];
//或者
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES(?, ?)", @"Bourne", [NSNumber numberWithInt:42]];
5.查询
查询方法也有3种,使用起来相当简单:
- (FMResultSet *)executeQuery:(NSString*)sql, ...
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
查询示例:
//1.执行查询
FMResultSet *result = [database executeQuery:@"SELECT * FROM t_person"];
//2.遍历结果集
while ([result next]) {
NSString *name = [result stringForColumn:@"name"];
int age = [result intForColumn:@"age"];
}

6.线程安全
在多个线程中同时使用一个FMDatabase实例是不明智的。不要让多个线程分享同一个FMDatabase实例,它无法在多个线程中同时使用。 如果在多个线程中同时使用一个FMDatabase实例,会造成数据混乱等问题。所以,请使用 FMDatabaseQueue,它是线程安全的。以下是使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
创建队列。
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
使用队列
[queue inDatabase:^(FMDatabase *database) {
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_1", [NSNumber numberWithInt:1]];
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_2", [NSNumber numberWithInt:2]];
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_3", [NSNumber numberWithInt:3]];
FMResultSet *result = [database executeQuery:@"select * from t_person"];
while([result next]) {
}
}];
而且可以轻松地把简单任务包装到事务里:
[queue inTransaction:^(FMDatabase *database, BOOL *rollback) {
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_1", [NSNumber numberWithInt:1]];
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_2", [NSNumber numberWithInt:2]];
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_3", [NSNumber numberWithInt:3]];
FMResultSet *result = [database executeQuery:@"select * from t_person"];
while([result next]) {
}
//回滚
*rollback = YES;
}];

FMDatabaseQueue 后台会建立系列化的G-C-D队列,并执行你传给G-C-D队列的块。这意味着 你从多线程同时调用调用方法,GDC也会按它接收的块的顺序来执行。

文/伯恩的遗产(简书作者)
原文链接:http://www.jianshu.com/p/7616cbd72845
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

IOS swift3 键盘

ios swift3 键盘高度获取

1
2
3
4
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
//deinit 的时候remove 下

处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func keyboardWillShow(_ sender:Notification){
// 获取键盘高度
let userInfo = sender.userInfo
if userInfo == nil{
return
}
let value:NSValue = userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue
let keyBoardRect = value.cgRectValue
let height = keyBoardRect.size.height
}
func keyboardWillHide(_ sender:Notification){
// 隐藏了. 高度自然为0
}

oc 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (void)viewDidLoad
{
[super viewDidLoad];
//增加监听,当键盘出现或改变时收出消息
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
//增加监听,当键退出时收出消息
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
//当键盘出现或改变时调用
- (void)keyboardWillShow:(NSNotification *)aNotification
{
//获取键盘的高度
NSDictionary *userInfo = [aNotification userInfo];
NSValue *aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardRect = [aValue CGRectValue];
int height = keyboardRect.size.height;
}
//当键退出时调用
- (void)keyboardWillHide:(NSNotification *)aNotification{}

//键盘上面view 自动位置

1
2
NotificationCenter.default.addObserver(self, selector: #selector(self.keybordFrameChange(_:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keybordHid(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func keybordHid(_ sender:Notification){
var info = (sender as NSNotification).userInfo //as? NSDictionary
let keyboardDuration = (info?[UIKeyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue ?? 0
self.inputDefaultView.mas_updateConstraints { (make) in
make?.bottom.mas_equalTo()(self.view)
}
UIView.animate(withDuration: keyboardDuration) {
self.view.layoutIfNeeded()
}
}
func keybordFrameChange(_ sender :Notification){
var keyHeight:CGFloat = 246
var info = (sender as NSNotification).userInfo //as? NSDictionary
let keyboardSize = (info?[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue //UIKeyboardFrameEndUserInfoKey UIKeyboardFrameBeginUserInfoKey ,, begin的位置会有差距, 结束的最终的才是ok 的
let keyboardDuration = (info?[UIKeyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue ?? 0
if keyboardSize != nil{
keyHeight = (keyboardSize!.height)
}
self.inputDefaultView.mas_updateConstraints { (make) in
make?.bottom.mas_equalTo()(self.view)?.offset()(-keyHeight)
}
UIView.animate(withDuration: keyboardDuration) {
self.view.layoutIfNeeded()
}
}

//原本用这个 但是发现有时候位置不对, 换上面的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
func keyBoardWillShow(_ note:Notification)
{
if (note as NSNotification).userInfo == nil{
return
}
let userInfo = (note as NSNotification).userInfo! as NSDictionary
let keyBoardBounds = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
// let keyBoardBoundsRect = self.view.convertRect(keyBoardBounds, toView:nil)
// let keyBaoardViewFrame = inputeView.frame
let deltaY = keyBoardBounds.size.height
self.inputeDefaultView.isHidden = false
self.view.bringSubview(toFront: self.inputeDefaultView)
let animations:(() -> Void) = {
self.inputeDefaultView.transform = CGAffineTransform(translationX: 0,y: -deltaY)
}
if duration > 0 {
let options = UIViewAnimationOptions(rawValue: UInt((userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).intValue << 16))
UIView.animate(withDuration: duration, delay: 0, options:options, animations: animations, completion: nil)
}else{
animations()
}
}
func keyBoardWillHide(_ note:Notification)
{
if (note as NSNotification).userInfo == nil{
return
}
let userInfo = (note as NSNotification).userInfo! as NSDictionary
let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
let animations:(() -> Void) = {
self.inputeDefaultView.transform = CGAffineTransform.identity
}
if duration > 0 {
let options = UIViewAnimationOptions(rawValue: UInt((userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).intValue << 16))
UIView.animate(withDuration: duration, delay: 0, options:options, animations: animations, completion: nil)
UIView.animate(withDuration: duration, delay: 0, options: options, animations: animations, completion: { [weak self](bool) in
if let weakSelf = self{
weakSelf.inputeDefaultView.isHidden = true
}
})
}else{
animations()
}
}

IOS KeyChain

转:
https://cnbin.github.io/blog/2015/08/18/ios-keychain-ji-chu/

根据苹果的介绍,iOS设备中的Keychain是一个安全的存储容器,可以用来为不同应用保存敏感信息比如用户名,密码,网络密码,认证令牌。苹果自己用keychain来保存Wi-Fi网络密码,VPN凭证等等。它是一个sqlite数据库,位于/private/var/Keychains/keychain-2.db,其保存的所有数据都是加密过的。

开发者通常会希望能够利用操作系统提供的功能来保存凭证(credentials)而不是把它们(凭证)保存到NSUserDefaults,plist文件等地方。保存这些数据的原因是开发者不想用户每次都要登录,因此会把认证信息保存到设备上的某个地方并且在用户再次打开应用的时候用这些数据自动登录。Keychain的信息是存在于每个应用(app)的沙盒之外的。

通过keychain access groups可以在应用之间共享keychain中的数据。要求在保存数据到keychain的时候指定group。把数据保存到keychain的最好方法就是用苹果提供的KeychainItemWrapper。可以到这下载例子工程。第一步就是创建这个类的实例。

1
KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@”Password” accessGroup:nil];

标识符(Identifier)在后面我们要从keychain中取数据的时候会用到。如果你想要在应用之间共享信息,那么你需要指定访问组(access group)。有同样的访问组 的应用能够访问同样的keychain信息。

1
KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@”Account Number” accessGroup:@”YOUR_APP_ID_HERE.com.yourcompany.GenericKeychainSuite”];

要把信息保存到keychain中,使用 setObject:forKey: 方法。在这里, (id)kSecAttrAccount 是一个预先定义好的键(key),我们可以用它来保存账号名称。 kSecClass指定了我们要保存的某类信息,在这里是一个通用的密码。kSecValueData可以被用来保存任意的数据,在这里是一个密码。

1
2
3
4
5
6
7
8
9
[wrapper setObject:kSecClassGenericPassword forKey:(id)kSecClass];
[wrapper setObject:@"username" forKey:(id)kSecAttrAccount];
[wrapper setObject:@"password"forKey:(id)kSecValueData];
[wrapper setObject:(id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(id)kSecAttrAccessible];
kSecAttrAccessiblein变量用来指定这个应用合适需要访问这个数据。我们需要对这个选项特别注意,并且使用最严格的选项。这个键(key)可以设置6种值。

当然,我们应该绝对不要使用kSecAttrAccessibleAlways。一个安全点的选项是kSecAttrAccessibleWhenUnlocked。有些选项是以 ThisDeviceOnly 结尾的,如果选中了这个选项,那么数据就会被以硬件相关的密钥(key)加密,因此不能被传输到或者被其他设备看到。即使它们提供了进一步的安全性,使用它们可能不是一个好主意,除非你有一个更好的理由不允许数据在备份之间迁移。

要从keychain中获取数据,可以用

1
NSString *accountName = [wrapper objectForKey:(id)kSecAttrAccount];

钥匙串中的条目称为SecItem,但它是存储在CFDictionary中的。SecItemRef类型并不存在。SecItem有五类:通用密码、互联网密码、证书、密钥和身份。在大多数情况下,我们用到的都是通用密码。许多问题都是开发人员尝试用互联网密码造成的。互联网密码要复杂得多,而且相比之下优势寥寥无几,除非开发Web浏览器,否则没必要用它。KeyChainItemWrapper只使用通用密码,这也是我喜欢它的原因之一。iOS应用很少将密钥和身份存储起来,所以我们在本书中不会讨论这方面的内容。只有公钥的证书通常应该存储在文件中,而不是钥匙串中。

最后,我们需要在钥匙串中搜索需要的内容。密钥有很多个部分可用来搜索,但最好的办法是将自己的标识符赋给它,然后搜索。通用密码条目都包含属性kSecAttrGeneric,可以用它来存储标识符。这也是KeyChainItemWrapper的处理方式。

钥匙串中的条目都有几个可搜索的属性和一个加密过的值。对于通用密码条目,比较重要的属性有账户(kSecAttrAccount)、服务(kSecAttrService)和标识符(kSecAttrGeneric)。而值通常是密码。

设备唯一字符串
http://www.jianshu.com/p/faa4854ce180

, 主要用到上述的KeyChain 保存唯一的字符串
上述简书 上有个demo , 特此记录原理和代码

IOS 加密整理

SHA 加密:

oc 写法,转得忘记出处了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//
//NSString+SHA.h
//
#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonCryptor.h>
@interface NSString(SHA)
-(NSString *) sha1;
-(NSString *) sha224;
-(NSString *) sha256;
-(NSString *) sha384;
-(NSString *) sha512;
@end
#import "NSString+SHA.h"
@implementation NSString (SHA)
-(NSString *) sha1{
const char *cstr = [self cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:self.length];
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, (CC_LONG)data.length, digest);
NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for (int i=0; i<CC_SHA1_DIGEST_LENGTH; i++) {
[output appendFormat:@"%02x",digest[i]];
}
return output;
}
-(NSString *) sha224{
const char *cstr = [self cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:self.length];
uint8_t digest[CC_SHA224_DIGEST_LENGTH];
CC_SHA224(data.bytes, (CC_LONG)data.length, digest);
NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA224_DIGEST_LENGTH*2];
for (int i=0; i<CC_SHA224_DIGEST_LENGTH; i++) {
[output appendFormat:@"%02x",digest[i]];
}
return output;
}
- (NSString*) sha256
{
const char *cstr = [self cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:self.length];
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(data.bytes, (CC_LONG)data.length, digest);
NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++)
[output appendFormat:@"%02x", digest[i]];
return output;
}
- (NSString*) sha384
{
const char *cstr = [self cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:self.length];
uint8_t digest[CC_SHA384_DIGEST_LENGTH];
CC_SHA384(data.bytes, (CC_LONG)data.length, digest);
NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA384_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_SHA384_DIGEST_LENGTH; i++)
[output appendFormat:@"%02x", digest[i]];
return output;
}
- (NSString*) sha512
{
const char *cstr = [self cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:self.length];
uint8_t digest[CC_SHA512_DIGEST_LENGTH];
CC_SHA512(data.bytes, (CC_LONG)data.length, digest);
NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA512_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_SHA512_DIGEST_LENGTH; i++)
[output appendFormat:@"%02x", digest[i]];
return output;
}
@end

下面是一个加盐的SHA加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//
// HmacUtils.m
// Inspector
//
// Created by yaoliangjun on 16/5/20.
// Copyright © 2016年 yaoliangjun. All rights reserved.
//
#import "HmacUtils.h"
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonHMAC.h>
@implementation HmacUtils
/**
* 加密方式,MAC算法: HmacSHA256
*
* @param plaintext 要加密的文本
* @param key 秘钥
*
* @return 加密后的字符串
*/
+ (NSString *)hmac:(NSString *)plaintext withKey:(NSString *)key
{
const charchar *cKey = [key cStringUsingEncoding:NSASCIIStringEncoding];
const charchar *cData = [plaintext cStringUsingEncoding:NSASCIIStringEncoding];
unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
NSData *HMACData = [NSData dataWithBytes:cHMAC length:sizeof(cHMAC)];
const unsigned charchar *buffer = (const unsigned charchar *)[HMACData bytes];
NSMutableString *HMAC = [NSMutableString stringWithCapacity:HMACData.length * 2];
for (int i = 0; i < HMACData.length; ++i){
[HMAC appendFormat:@"%02x", buffer[i]];
}
return HMAC;
}
@end

swift 3 加密 ,其他SHA 类似方法 , 桥接头中加入系统加密类库
转自: http://stackoverflow.com/questions/25388747/sha256-in-swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 加密系统头
*/
//#import <CommonCrypto/CommonDigest.h>
//#import <CommonCrypto/CommonCrypto.h>
// Created by boosj on 16/10/20.
// Copyright © 2016年 cz. All rights reserved.
//
import Foundation
public extension String{
func sha256() -> String{
if let stringData = self.data(using: String.Encoding.utf8) {
return hexStringFromData(input: digestSha256(input: stringData as NSData))
}
return ""
}
private func digestSha256(input : NSData) -> NSData {
let digestLength = Int(CC_SHA256_DIGEST_LENGTH)
var hash = [UInt8](repeating: 0, count: digestLength)
CC_SHA256(input.bytes, UInt32(input.length), &hash)
return NSData(bytes: hash, length: digestLength)
}
private func hexStringFromData(input: NSData) -> String {
var bytes = [UInt8](repeating: 0, count: input.length)
input.getBytes(&bytes, length: input.length)
var hexString = ""
for byte in bytes {
hexString += String(format:"%02x", UInt8(byte))
}
return hexString
}
}

IOS swift3 设备型号获取

swift 版本很多设备获取都会改变不像oc 那么稳定,
这次是看到一个好的方式觉得比我以前的觉得代码要优雅特此整理完善

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//
// Created by boosj on 16/10/19.
// Copyright © 2016年 cz. All rights reserved.
// UIDevice.current.modelName.rawValue
//判断也不需要直接string字符串 防止代码写错
public enum Model : String {
case iPod1 = "iPod 1",
iPod2 = "iPod 2",
iPod3 = "iPod 3",
iPod4 = "iPod 4",
iPod5 = "iPod 5",
iPad2 = "iPad 2",
iPad3 = "iPad 3",
iPad4 = "iPad 4",
iPhone4 = "iPhone 4",
iPhone4S = "iPhone 4S",
iPhone5 = "iPhone 5",
iPhone5S = "iPhone 5S",
iPhone5C = "iPhone 5C",
iPadMini1 = "iPad Mini 1",
iPadMini2 = "iPad Mini 2",
iPadMini3 = "iPad Mini 3",
iPadAir1 = "iPad Air 1",
iPadAir2 = "iPad Air 2",
iPhone6 = "iPhone 6",
iPhone6plus = "iPhone 6 Plus",
iPhone6S = "iPhone 6S",
iPhone6Splus = "iPhone 6S Plus",
iPhoneSE = "iPhone SE",
iPhone7 = "iPhone 7",
iPhone7plus = "iPhone 7 Plus",
iPadPro9 = "iPad Pro(9.7)",
iPadPro = "iPad Pro",
AppleTV = "Apple TV",
iPad = "iPad",
iPod = "iPod",
simulator = "simulator",
iPhone = "iPhone",
unrecognized = "unrecognized"
}
public extension UIDevice {
public var modelName: Model {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8 , value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
var modelMap : [ String : Model ] = [
"i386" : .simulator,
"x86_64" : .simulator,
"iPod1,1" : .iPod1,
"iPod2,1" : .iPod2,
"iPod3,1" : .iPod3,
"iPod4,1" : .iPod4,
"iPod5,1" : .iPod5,
"iPad2,1" : .iPad2,
"iPad2,2" : .iPad2,
"iPad2,3" : .iPad2,
"iPad2,4" : .iPad2,
"iPad2,5" : .iPadMini1,
"iPad2,6" : .iPadMini1,
"iPad2,7" : .iPadMini1,
"iPhone3,1" : .iPhone4,
"iPhone3,2" : .iPhone4,
"iPhone3,3" : .iPhone4,
"iPhone4,1" : .iPhone4S,
"iPhone5,1" : .iPhone5,
"iPhone5,2" : .iPhone5,
"iPhone5,3" : .iPhone5C,
"iPhone5,4" : .iPhone5C,
"iPad3,1" : .iPad3,
"iPad3,2" : .iPad3,
"iPad3,3" : .iPad3,
"iPad3,4" : .iPad4,
"iPad3,5" : .iPad4,
"iPad3,6" : .iPad4,
"iPad6,4" : .iPadPro9,
"iPad6,3" : .iPadPro9,
"iPad6,7" : .iPadPro,
"iPad6,8" : .iPadPro,
"iPhone6,1" : .iPhone5S,
"iPhone6,2" : .iPhone5S,
"iPad4,1" : .iPadAir1,
"iPad4,2" : .iPadAir2,
"iPad4,4" : .iPadMini2,
"iPad4,5" : .iPadMini2,
"iPad4,6" : .iPadMini2,
"iPad4,7" : .iPadMini3,
"iPad4,8" : .iPadMini3,
"iPad4,9" : .iPadMini3,
"iPhone7,1" : .iPhone6plus,
"iPhone7,2" : .iPhone6,
"iPhone8,1" : .iPhone6S,
"iPhone8,2" : .iPhone6Splus,
"iPhone9,1" : .iPhone7,
"iPhone9,3" : .iPhone7,
"iPhone9,2" : .iPhone7plus,
"iPhone9,4" : .iPhone7plus
]
if let model = modelMap[identifier] {
return model
}
if identifier.range(of: "iPad") != nil{
return Model.iPad
}
if identifier.range(of: "iPod") != nil{
return Model.iPod
}
if identifier.range(of: "iPhone") != nil{
return Model.iPhone
}
return Model.unrecognized
}
}

主要知识从http://stackoverflow.com/questions/26028918/ios-how-to-determine-iphone-model-in-swift
转载

IOS 音视频合成

相关链接:
http://www.jianshu.com/p/8e1c7815af0e
http://www.devzhang.cn/2016/09/09/%E7%BC%96%E8%BE%91Assets/

今天去查询音视频合成相关资料,找到一个demo 觉得很是不错

转文:http://www.jianshu.com/p/9f83af9dbbef

####音视频主要是利用AVFoundation框架下的AVMutableComposition来合成音视频.

####在AVMutableComposition中传入两个数据流,一个是音频一个是视频,之后调用合成方法就可以了

#上代码

##storyBoard中拖入一个button,一个imageView
这里写图片描述

##为了效果好可以将IamgeView的背景色调为黑色

##然后在ViewController中添加以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "MBProgressHUD+MJ.h"
@interface ViewController ()
/** 用于播放 */
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)mergeAction:(UIButton *)sender {
[self merge];
}
// 混合音乐
- (void)merge{
// mbp提示框
[MBProgressHUD showMessage:@"正在处理中"];
// 路径
NSString *documents = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
// 声音来源
NSURL *audioInputUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"五环之歌" ofType:@"mp3"]];
// 视频来源
NSURL *videoInputUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"myPlayer" ofType:@"mp4"]];
// 最终合成输出路径
NSString *outPutFilePath = [documents stringByAppendingPathComponent:@"merge.mp4"];
// 添加合成路径
NSURL *outputFileUrl = [NSURL fileURLWithPath:outPutFilePath];
// 时间起点
CMTime nextClistartTime = kCMTimeZero;
// 创建可变的音视频组合
AVMutableComposition *comosition = [AVMutableComposition composition];
// 视频采集
AVURLAsset *videoAsset = [[AVURLAsset alloc] initWithURL:videoInputUrl options:nil];
// 视频时间范围
CMTimeRange videoTimeRange = CMTimeRangeMake(kCMTimeZero, videoAsset.duration);
// 视频通道 枚举 kCMPersistentTrackID_Invalid = 0
AVMutableCompositionTrack *videoTrack = [comosition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
// 视频采集通道
AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
// 把采集轨道数据加入到可变轨道之中
[videoTrack insertTimeRange:videoTimeRange ofTrack:videoAssetTrack atTime:nextClistartTime error:nil];
// 声音采集
AVURLAsset *audioAsset = [[AVURLAsset alloc] initWithURL:audioInputUrl options:nil];
// 因为视频短这里就直接用视频长度了,如果自动化需要自己写判断
CMTimeRange audioTimeRange = videoTimeRange;
// 音频通道
AVMutableCompositionTrack *audioTrack = [comosition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
// 音频采集通道
AVAssetTrack *audioAssetTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
// 加入合成轨道之中
[audioTrack insertTimeRange:audioTimeRange ofTrack:audioAssetTrack atTime:nextClistartTime error:nil];
// 创建一个输出
AVAssetExportSession *assetExport = [[AVAssetExportSession alloc] initWithAsset:comosition presetName:AVAssetExportPresetMediumQuality];
// 输出类型
assetExport.outputFileType = AVFileTypeQuickTimeMovie;
// 输出地址
assetExport.outputURL = outputFileUrl;
// 优化
assetExport.shouldOptimizeForNetworkUse = YES;
// 合成完毕
[assetExport exportAsynchronouslyWithCompletionHandler:^{
// 这里还得把状态监听下 assetExport status ,AVAssetExportSessionStatusCompleted
// 回到主线程
dispatch_async(dispatch_get_main_queue(), ^{
// 调用播放方法
[self playWithUrl:outputFileUrl];
});
}];
// assetExport.progress 进度 time 定时去检测
}
/** 播放方法 */
- (void)playWithUrl:(NSURL *)url{
// 传入地址
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:url];
// 播放器
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
// 播放器layer
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame = self.imageView.frame;
// 视频填充模式
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
// 添加到imageview的layer上
[self.imageView.layer addSublayer:playerLayer];
// 隐藏提示框 开始播放
[MBProgressHUD hideHUD];
[MBProgressHUD showSuccess:@"合成完成"];
// 播放
[player play];
}

##MBP是一个第三方提示类,如果不关心这个功能可以删除这三行代码和头文件

1
2
3
4
5
// mbp提示框
[MBProgressHUD showMessage:@"正在处理中"];
// 隐藏提示框 开始播放
[MBProgressHUD hideHUD];
[MBProgressHUD showSuccess:@"合成完成"];

这里写图片描述

##GitHub:https://github.com/Lafree317/MergeVideoAndMusic

补充: 单音轨, 多音频合成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 声音采集2
AVURLAsset *audioAsset2 = [[AVURLAsset alloc] initWithURL:audioInputUrl2 options:nil];
// 因为视频短这里就直接用视频长度了,如果自动化需要自己写判断 , 注: 如果两个音频接着播放的得,得考虑 两个长度问题, 这个会以长的做节点, 自己计算基准事件和音频时间
CMTime aduioTime2 = CMTimeMakeWithSeconds(10, audioAsset2.duration.timescale);//audioAsset2.duration;//CMTimeMake(videoAsset.duration.value - audioAsset.duration.value, audioAsset2.duration.timescale);
CMTimeRange audioTimeRange2 = CMTimeRangeMake(kCMTimeZero, aduioTime2);//videoTimeRange;
// 音频通道
// AVMutableCompositionTrack *audioTrack2 = [comosition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
// 音频采集通道
AVAssetTrack *audioAssetTrack2 = [[audioAsset2 tracksWithMediaType:AVMediaTypeAudio] firstObject];
// 加入合成轨道之中
//[audioTrack insertTimeRange:audioTimeRange ofTrack:audioAssetTrack atTime:nextClistartTime error:nil];
[audioTrack insertTimeRanges:@[[NSValue valueWithCMTimeRange:audioTimeRange],[NSValue valueWithCMTimeRange:audioTimeRange2]] ofTracks:@[audioAssetTrack,audioAssetTrack2] atTime:nextClistartTime error:nil];

补充3 : 多音轨合成
2 也可以直接取出视频的音轨进行合并,达到保留视频音轨(没测试)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 声音采集2
AVURLAsset *audioAsset2 = [[AVURLAsset alloc] initWithURL:audioInputUrl2 options:nil];
// 因为视频短这里就直接用视频长度了,如果自动化需要自己写判断 , 注: 如果两个音频接着播放的得,得考虑 两个长度问题, 这个会以长的做节点, 自己计算基准事件和音频时间
CMTime aduioTime2 = audioAsset2.duration;//CMTimeMake(videoAsset.duration.value - audioAsset.duration.value, audioAsset2.duration.timescale);
CMTimeRange audioTimeRange2 = CMTimeRangeMake(kCMTimeZero, aduioTime2);//videoTimeRange;
// 音频通道
AVMutableCompositionTrack *audioTrack2 = [comosition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
// 音频采集通道
AVAssetTrack *audioAssetTrack2 = [[audioAsset2 tracksWithMediaType:AVMediaTypeAudio] firstObject];
// 加入合成轨道之中
[audioTrack insertTimeRange:audioTimeRange ofTrack:audioAssetTrack atTime:nextClistartTime error:nil];
[audioTrack2 insertTimeRange:audioTimeRange2 ofTrack:audioAssetTrack2 atTime:nextClistartTime error:nil];

// 翻译https://changjianfeishui.gitbooks.io/avfoundation-programming-guide/content/chapter1.html
挺不错,可供参考


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
双轨道合成备注下 但是最好一个轨道
/**
片头拼接
@param exportVideoFilePath <#exportVideoFilePath description#>
@param inputeVideoURL <#inputeVideoURL description#>
@param videoHiveURL <#videoHiveURL description#>
*/
-(void)builderVideoHiveToMp4Pth: (NSString *)exportVideoFilePath
withInputeVideoURL: (NSURL *)inputeVideoURL
withVideoHiveURL: (NSURL*)videoHiveURL{
_effectType = 1;
unlink([exportVideoFilePath UTF8String]);
if (!inputeVideoURL || ![inputeVideoURL isFileURL] || !exportVideoFilePath || [exportVideoFilePath isEqualToString:@""]){
//没有输入或者输出等, 返回NO
NSLog(@"inputeVideoURL 或者 exportVideoFilePath 地址有错");
return;
}
// 创建asset 从输入url
AVURLAsset *videoAsset = [[AVURLAsset alloc] initWithURL:inputeVideoURL options:nil];
AVURLAsset *videoAsset2 = [[AVURLAsset alloc] initWithURL:videoHiveURL options:nil];
if (videoAsset == nil || [[videoAsset tracksWithMediaType:AVMediaTypeVideo] count]<1 || [[videoAsset2 tracksWithMediaType:AVMediaTypeVideo] count]<1){
// 没有建立成功,或者没有视频轨道 + 音频判断
return;
}
NSParameterAssert(videoAsset);
// 视频和音频的合成类
AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];
/// MARK视频轨道 合成类 先add 的会显示在最底层
// 空的轨道
AVMutableCompositionTrack *videoTrack2 = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *videoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
// 轨道数据
AVAssetTrack *assetVideoTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *assetVideoTrack2 = [[videoAsset2 tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
[videoTrack2 insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset2.duration ) ofTrack:assetVideoTrack2 atTime:kCMTimeZero error:nil];
[videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration ) ofTrack:assetVideoTrack atTime:videoAsset2.duration error:nil];
// layer 数据轨道的合成指令集
AVMutableVideoCompositionLayerInstruction *videolayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:assetVideoTrack];
AVMutableVideoCompositionLayerInstruction *videolayerInstruction2 = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:assetVideoTrack2];
[videolayerInstruction2 setOpacity:0.0 atTime:videoAsset2.duration];
// AVMutableVideoCompositionLayerInstruction *audiolayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:assetAudioTrack];
//Make a "pass through video track" video composition.
/// 视频合成的指令
AVMutableVideoCompositionInstruction *mainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeAdd(videoAsset.duration, videoAsset2.duration));
mainInstruction.layerInstructions = [NSArray arrayWithObjects:videolayerInstruction,videolayerInstruction2, nil];
// 视频合成类
AVMutableVideoComposition *mainVideoComposition = [[AVMutableVideoComposition alloc] init];
mainVideoComposition.instructions = [NSArray arrayWithObjects:mainInstruction, nil];
mainVideoComposition.frameDuration = CMTimeMake(1, 30);
mainVideoComposition.renderSize = assetVideoTrack.naturalSize;
// 所有效果的父类
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
parentLayer.contentsScale = [UIScreen mainScreen].scale;
videoLayer.contentsScale = [UIScreen mainScreen].scale;
parentLayer.frame = CGRectMake(0, 0, assetVideoTrack.naturalSize.width, assetVideoTrack.naturalSize.height);
videoLayer.frame = parentLayer.frame;
[parentLayer addSublayer:videoLayer];
mainVideoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
if (_animationlayer){
[_animationlayer setAllFrame:parentLayer.bounds];
[parentLayer addSublayer:_animationlayer];
}
// export to mp4
NSString *mp4Quality = AVAssetExportPresetHighestQuality;
NSString *exportPath = exportVideoFilePath;
NSURL *exportUrl = [NSURL fileURLWithPath:exportPath];
_videotoTalTime = CMTimeGetSeconds(videoAsset.duration);
_exportSession = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:mp4Quality];
_exportSession.outputURL = exportUrl;
_exportSession.outputFileType = AVFileTypeQuickTimeMovie;// 转换格式
_exportSession.shouldOptimizeForNetworkUse = YES;
_exportSession.videoComposition = mainVideoComposition;
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
// 定时更新进度
_timerEffect = [NSTimer CZ_scheduledTimerWithTimeInterval:0.1f repeats:YES callback:^{
[weakSelf retrievingProgressMP4];
}];
});
[_exportSession exportAsynchronouslyWithCompletionHandler:^{
if (!weakSelf){return;}
NSLog(@"转码状态 %ld",(long)[weakSelf.exportSession status]);
switch ([weakSelf.exportSession status]) {
case AVAssetExportSessionStatusCompleted:
{
dispatch_async(dispatch_get_main_queue(), ^{
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(AVAssetExportVideoHiveMP4SessionStatus:)]){
[weakSelf.delegate AVAssetExportVideoHiveMP4SessionStatus:[weakSelf.exportSession status]];
}
[weakSelf clearAll];
});
NSLog(@"视频转码成功");
break;
}
case AVAssetExportSessionStatusFailed:
{
dispatch_async(dispatch_get_main_queue(), ^{
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(AVAssetExportVideoHiveMP4SessionStatus:)]){
[weakSelf.delegate AVAssetExportVideoHiveMP4SessionStatus:[weakSelf.exportSession status]];
}
[weakSelf clearAll];
});
NSLog(@"视频转码失败%@",[weakSelf.exportSession error]);
break;
}
case AVAssetExportSessionStatusCancelled:
break;
case AVAssetExportSessionStatusWaiting:
break;
case AVAssetExportSessionStatusExporting:
break;
case AVAssetExportSessionStatusUnknown:
default:
break;
}
}];
}

/// 后期看到
来源
AVURLAssetPreferPreciseDurationAndTimingKey的选项,选项带有@YES值可以确保当资源的属性使用AVAsynchronousKeyValueLoading协议载入时可以计算出准确的时长和时间信息。虽然使用这个option时还会载入过程增添一些额外开销,不过这可以保证资源正处于合适的编辑状态。生成水印的代码如下:
NSDictionary opts = [NSDictionary dictionaryWithObject:@(YES) forKey:AVURLAssetPreferPreciseDurationAndTimingKey];
AVURLAsset
videoAsset = [AVURLAsset URLAssetWithURL:videoPathURL options:opts]; //初始化视频媒体文件

IOS 通知

ios 的通知

swift2.3

1
2
3
4
5
6
7
8
9
10
11
NSNotificationCenter.defaultCenter().postNotificationName("HomRefresh", object: data)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "homeRefresh:", name: "HomRefresh", object: nil)//接受消息
func homeRefresh(Notification:NSNotification){
// 传递的各种类型的数据
let data = Notification.object as XX
}

swift 3 里面写法变了 更新下写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "namexxx"), object: "数据", userInfo: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.collcetNotifiaction(_:)), name: NSNotification.Name(rawValue:VideoCollectMessage), object: nil)
/// 收藏 消息接受
func collcetNotifiaction(_ sender:Notification) {
let cell = self.tableView.cellForRow(at: IndexPath(row: 0, section: 2)) as? VideoNewPageOtherViewMenuCell
if cell != nil{
DispatchQueue.main.async {
cell!.setMenuBtnStatus(3, status: true)
}
}
}