swift 可选协议

可选接口和接口扩展
转自 : http://swifter.tips/objc-protocol/

Objective-C 中的 protocol 里存在 @optional 关键字,被这个关键字修饰的方法并非必须要被实现。我们可以通过接口定义一系列方法,然后由实现接口的类选择性地实现其中几个方法。在 Cocoa API 中很多情况下接口方法都是可选的,这点和 Swift 中的 protocol 的所有方法都必须被实现这一特性完全不同。

那些如果没有实现则接口就无法正常工作的方法一般是必须的,而相对地像作为事件通知或者对非关键属性进行配置的方法一般都是可选的。最好的例子我想应该是 UITableViewDataSource 和 UITableViewDelegate。前者中有两个必要方法:

1
2
--tableView:numberOfRowsInSection:
--tableView:cellForRowAtIndexPath:

分别用来计算和准备 tableView 的高度以及提供每一个 cell 的样式,而其他的像是返回 section 个数或者询问 cell 是否能被编辑的方法都有默认的行为,都是可选方法;后者 (UITableViewDelegate) 中的所有方法都是详细的配置和事件回传,因此全部都是可选的。

原生的 Swift protocol 里没有可选项,所有定义的方法都是必须实现的。如果我们想要像 Objective-C 里那样定义可选的接口方法,就需要将接口本身定义为 Objective-C 的,也即在 protocol 定义之前加上 @objc。另外和 Objective-C 中的 @optional 不同,我们使用没有 @ 符号的关键字 optional 来定义可选方法:

1
2
3
@objc protocol OptionalProtocol {
optional func optionalMethod()
}

另外,对于所有的声明,它们的前缀修饰是完全分开的。也就是说你不能像是在 Objective-C 里那样用一个 @optional 指定接下来的若干个方法都是可选的了,必须对每一个可选方法添加前缀,对于没有前缀的方法来说,它们是默认必须实现的:

1
2
3
4
5
@objc protocol OptionalProtocol {
optional func optionalMethod() // 可选
func necessaryMethod() // 必须
optional func anotherOptionalMethod() // 可选
}

一个不可避免的限制是,使用 @objc 修饰的 protocol 就只能被 class 实现了,也就是说,对于 struct 和 enum 类型,我们是无法令它们所实现的接口中含有可选方法或者属性的。另外,实现它的 class 中的方法还必须也被标注为 @objc,或者整个类就是继承自 NSObject。这对我们写代码来说是一种很让人郁闷的限制。

在 Swift 2.0 中,我们有了另一种选择,那就是使用 protocol extension。我们可以在声明一个 protocol 之后再用 extension 的方式给出部分方法默认的实现。这样这些方法在实际的类中就是可选实现的了。还是举上面的例子,使用接口扩展的话,会是这个样子:

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
protocol OptionalProtocol {
func optionalMethod() // 可选
func necessaryMethod() // 必须
func anotherOptionalMethod() // 可选
}
extension OptionalProtocol {
// 把需要可选的在这里做一个空实现
func optionalMethod() {
print("Implemented in extension")
}
func anotherOptionalMethod() {
print("Implemented in extension")
}
}
class MyClass: OptionalProtocol {
func necessaryMethod() {
print("Implemented in Class3")
}
func optionalMethod() {
print("Implemented in Class3")
}
}
let obj = MyClass()
obj.necessaryMethod() // Implemented in Class3
obj.optionalMethod() // Implemented in Class3
obj.anotherOptionalMethod() // Implemented in extension

protocol extension

转自: http://swifter.tips/protocol-extension/

Swift 2 中引入了一个非常重要的特性,那就是 protocol extension。在 Swift 1.x 中,extension 仅只能作用在实际的类型上 (也就是 class, struct 等等),而不能扩展一个 protocol。在 Swift 中,标准库的功能基本都是基于 protocol 来实现的,举个最简单的例子,我们每天使用的 Array 就是遵守了 CollectionType 这个 protocol 的。CollectionType 可以说是 Swift 中非常重要的接口,除了 Array 以外,像是 Dictionary 和 Set 也都实现了这个接口所定义的内容。

在 protocol 不能被扩展的时候,当我们想要为实现了某个接口的所有类型添加一些另外的共通的功能时,会非常麻烦。一个很好的例子是 Swift 1.x 时像是 map 或者 filter 这样的函数。大体来说,我们有两种思路进行添加:第一种方式是在接口中定义这个方法,然后在所有实现了这个接口的类型中都去实现一遍。每有一个这样的类型,我们就需要写一份类似甚至相同的方法,这显然是不可取的,不仅麻烦,而且完全没有可维护性。另一种方法是在全局范围实现一个接受该 protocol 的实例的方法,相比于前一种方式,我们只需要维护一份代码,显然要好不少,但是缺点在于在全局作用域中引入了只和特定 protocol 有关的东西,这并不符合代码设计的美学。作为妥协,Apple 在 Swift 1.x 中采用的是后一种,也就是全局方法,如果你尝试寻找的话,可以在 Swift 1.x 的标准库的全局 scope 中找到像是 map 和 filter 这样的方法。

在 Swift 2 中这个问题被彻底解决了。现在我们可以对一个已有的 protocol 进行扩展,而扩展中实现的方法将作为实现扩展的类型的默认实现。也就是说,假设我们有下面的 protocol 声明,以及一个对该接口的扩展:

1
2
3
4
5
6
7
8
9
protocol MyProtocol {
func method()
}
extension MyProtocol {
func method() {
print("Called")
}
}

在具体的实现这个接口的类型中,即使我们什么都不写,也可以编译通过。进行调用的话,会直接使用 extension 中的实现:

1
2
3
4
5
6
7
struct MyStruct: MyProtocol {
}
MyStruct().method()
// 输出:
// Called in extension

当然,如果我们需要在类型中进行其他实现的话,可以像以前那样在具体类型中添加这个方法:

1
2
3
4
5
6
7
8
9
struct MyStruct: MyProtocol {
func method() {
print("Called in struct")
}
}
MyStruct().method()
// 输出:
// Called in struct

也就是说,protocol extension 为 protocol 中定义的方法提供了一个默认的实现。有了这个特性以后,之前被放在全局环境中的接受 CollectionType 的 map 方法,就可以被移动到 CollectionType 的接口扩展中去了:

1
2
3
4
extension CollectionType {
public func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]
//...
}

在日常开发中,另一个可以用到 protocol extension 的地方是 optional 的接口方法。通过提供 protocol 的 extension,我们为 protocol 提供了默认实现,这相当于变相将 protocol 中的方法设定为了 optional。关于这个,我们在可选接口和接口扩展一节中已经讲述过,就不再重复了。

对于 protocol extension 来说,有一种会非常让人迷惑的情况,就是在接口的扩展中实现了接口里没有定义的方法时的情况。举个例子,比如我们定义了这样的一个接口和它的一个扩展:

1
2
3
4
5
6
7
8
9
protocol A1 {
func method1() -> String
}
struct B1: A1 {
func method1() -> String {
return "hello"
}
}

在使用的时候,无论我们将实例的类型为 A1 还是 B1,因为实现只有一个,所以没有任何疑问,调用方法时的输出都是 “hello”:

1
2
3
4
5
6
7
8
let b1 = B1() // b1 is B1
b1.method1()
// hello
let a1: A1 = B1()
// a1 is A1
a1.method1()
// hello

但是如果在接口里只定义了一个方法,而在接口扩展中实现了额外的方法的话,事情就变得有趣起来了。考虑下面这组接口和它的扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
protocol A2 {
func method1() -> String
}
extension A2 {
func method1() -> String {
return "hi"
}
func method2() -> String {
return "hi"
}
}

扩展中除了实现接口定义的 method1 之外,还定义了一个接口中不存在的方法 method2。我们尝试来实现这个接口:

1
2
3
4
5
6
7
8
9
struct B2: A2 {
func method1() -> String {
return "hello"
}
func method2() -> String {
return "hello"
}
}

B2 中实现了 method1 和 method2。接下来,我们尝试初始化一个 B2 对象,然后对这两个方法进行调用:

1
2
3
4
let b2 = B2()
b2.method1() // hello
b2.method2() // hello

结果在我们的意料之中,虽然在 protocol extension 中已经实现了这两个方法,但是它们只是默认的实现,我们在具体实现接口的类型中可以对默认实现进行覆盖,这非常合理。但是如果我们稍作改变,在上面的代码后面继续添加:

1
2
3
4
let a2 = b2 as A2
a2.method1() // hello
a2.method2() // hi

a2 和 b2 是同一个对象,只不过我们通过 as 告诉编译器我们在这里需要的类型是 A2。但是这时候在这个同样的对象上调用同样的方法调用却得到了不同的结果,发生了什么?

我们可以看到,对 a2 调用 method2 实际上是接口扩展中的方法被调用了,而不是 a2 实例中的方法被调用。我们不妨这样来理解:对于 method1,因为它在 protocol 中被定义了,因此对于一个被声明为遵守接口的类型的实例 (也就是对于 a2) 来说,可以确定实例必然实现了 method1,我们可以放心大胆地用动态派发的方式使用最终的实现 (不论它是在类型中的具体实现,还是在接口扩展中的默认实现);但是对于 method2 来说,我们只是在接口扩展中进行了定义,没有任何规定说它必须在最终的类型中被实现。在使用时,因为 a2 只是一个符合 A2 接口的实例,编译器对 method2 唯一能确定的只是在接口扩展中有一个默认实现,因此在调用时,无法确定安全,也就不会去进行动态派发,而是转而编译期间就确定的默认实现。

也许在这个例子中你会觉得无所谓,因为实际中估计并不会有人将一个已知类型实例转回接口类型。但是要考虑到如果你的一些泛型 API 中有类似的直接拿到一个接口类型的结果的时候,调用它的扩展方法时就需要特别小心了:一般来说,如果有这样的需求的话,我们可以考虑将这个接口类型再转回实际的类型,然后进行调用。

整理一下相关的规则的话:

如果类型推断得到的是实际的类型
那么类型中的实现将被调用;如果类型中没有实现的话,那么接口扩展中的默认实现将被使用
如果类型推断得到的是接口,而不是实际类型
并且方法在接口中进行了定义,那么类型中的实现将被调用;如果类型中没有实现,那么接口扩展中的默认实现被使用
否则 (也就是方法没有在接口中定义),扩展中的默认实现将被调用

UIView实现水平翻转

1
2
let view = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.layer.transform = CATransform3DMakeRotation(CGFloat(M_PI), 0, 1, 0)

swift LOG 输出

LOG 输出,一直用oc 的条件编译, 刚发现swift 同样可以实现
现记录
转: http://swifter.tips/log/
http://swifter.tips/condition-compile/

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
LOG 输出
由 王巍 (@ONEVCAT) 发布于 2015-12-30
Log 输出是程序开发中很重要的组成部分,虽然它并不是直接的业务代码,但是却可以忠实地反映我们的程序是如何工作的,以及记录程序运行的过程中发生了什么。
Swift 中,最简单的输出方法就是使用 print,在我们关心的地方输出字符串和值。但是这并不够,试想一下当程序变得非常复杂的时候,我们可能会输出很多内容,而想在其中寻找到我们希望的输出其实并不容易。我们往往需要更好更精确的输出,这包括输出这个 log 的文件,调用的行号以及所处的方法名字等等。
我们当然可以在 print 的时候将当前的文件名字和那些必要的信息作为参数同我们的消息一起进行打印:
// Test.swift
func method() {
//...
print("文件名:Test.swift, 方法名:method,这是一条输出")
//...
}
但是这显然非常麻烦,每次输入文件名和方法名不说,随着代码的改变,这些 Log 的位置也可能发生改变,这时我们可能还需要不断地去维护这些输出,代价实在太大。
Swift 中,编译器为我们准备了几个很有用的编译符号,用来处理类似这样的需求,它们分别是:
符号 类型 描述
#file String 包含这个符号的文件的路径
#line Int 符号出现处的行号
#column Int 符号出现处的列
#function String 包含这个符号的方法名字
因此,我们可以通过使用这些符号来写一个好一些的 Log 输出方法:
func printLog<T>(message: T,
file: String = #file,
method: String = #function,
line: Int = #line)
{
print("\((file as NSString).lastPathComponent)[\(line)], \(method): \(message)")
}
这样,在进行 log 的时候我们只需要使用这个方法就能完成文件名,行号以及方法名的输出了。最棒的是,我们不再需要对这样的输出进行维护,无论在哪里它都能正确地输出各个参数:
// Test.swift
func method() {
//...
printLog("这是一条输出")
//...
}
// 输出:
// Test.swift[62], method(): 这是一条输出
另外,对于 log 输出更多地其实是用在程序开发和调试的过程中的,过多的输出有可能对运行的性能造成影响。在 Release 版本中关闭掉向控制台的输出也是软件开发中一种常见的做法。如果我们在开发中就注意使用了统一的 log 输出的话,这就变得非常简单了。使用条件编译的方法,我们可以添加条件,并设置合适的编译配置,使 printLog 的内容在 Release 时被去掉,从而成为一个空方法:
func printLog<T>(message: T,
file: String = #file,
method: String = #function,
line: Int = #line)
{
#if DEBUG
print("\((file as NSString).lastPathComponent)[\(line)], \(method): \(message)")
#endif
}
新版本的 LLVM 编译器在遇到这个空方法时,甚至会直接将这个方法整个去掉,完全不去调用它,从而实现零成本。

主要用到条件编译
上述需要设置

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
条件编译
由 王巍 (@ONEVCAT) 发布于 2014-12-02
在 C 系语言中,可以使用 #if 或者 #ifdef 之类的编译条件分支来控制哪些代码需要编译,而哪些代码不需要。Swift 中没有宏定义的概念,因此我们不能使用 #ifdef 的方法来检查某个符号是否经过宏定义。但是为了控制编译流程和内容,Swift 还是为我们提供了几种简单的机制来根据需求定制编译内容的。
首先是 #if 这一套编译标记还是存在的,使用的语法也和原来没有区别:
#if <condition>
#elseif <condition>
#else
#endif
当然,#elseif#else 是可选的。
但是这几个表达式里的 condition 并不是任意的。Swift 内建了几种平台和架构的组合,来帮助我们为不同的平台编译不同的代码,具体地:
方法 可选参数
os() OSX, iOS
arch() x86_64, arm, arm64, i386
注意这些方法和参数都是大小写敏感的。举个例子,如果我们统一我们在 iOS 平台和 Mac 平台的关于颜色的 API 的话,一种可能的方法就是配合 typealias 进行条件编译:
#if os(OSX)
typealias Color = NSColor
#else
typealias Color = UIColor
#endif
另外对于 arch() 的参数需要说明的是 arm 和 arm64 两项分别对应 32 位 CPU 和 64 位 CPU 的真机情况,而对于模拟器,相应地 32 位设备的模拟器和 64 位设备的模拟器所对应的分别是 i386 和 x86_64,它们也是需要分开对待的。
另一种方式是对自定义的符号进行条件编译,比如我们需要使用同一个 target 完成同一个 app 的收费版和免费版两个版本,并且希望在点击某个按钮时收费版本执行功能,而免费版本弹出提示的话,可以使用类似下面的方法:
@IBAction func someButtonPressed(sender: AnyObject!) {
#if FREE_VERSION
// 弹出购买提示,导航至商店等
#else
// 实际功能
#endif
}
在这里我们用 FREE_VERSION 这个编译符号来代表免费版本。为了使之有效,我们需要在项目的编译选项中进行设置,在项目的 Build Settings 中,找到 Swift Compiler - Custom Flags,并在其中的 Other Swift Flags 加上 -D FREE_VERSION 就可以了。

自己实现 :
设置
Switf Compiler - Custom Flags

中 (
active Compilation Conditions (不需要-D) 或者
Other swift flags (需要 -D 前缀)

1
2
3
4
#if DEBUG
// 逻辑
#endif

)debug 选项中加入 -D DEBUG

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// 全局函数输入log 条件编译,正式版本不输出
///
/// - Parameters:
/// - message: <#message description#>
/// - file: <#file description#>
/// - method: <#method description#>
/// - line: <#line description#>
func printLog<T>(_ message: T,
file: String = #file,
method: String = #function,
line: Int = #line)
{
// 条件编译还不行 需要在项目中配置条件 Swift Compiler - Custom Flags 加上-D XXX ,debug条件下加
#if DEBUG
print("\((file as NSString).lastPathComponent)[\(line)], \(method): \(message)")
#endif
}

IOS 错误集锦

开发中遇到很多小错误大错误, 很多时候下次遇到可能已经忘记,这里做一个记录,
注:很多是个人的理解不一定是正确答案

1 上传包ERROR ITMS-90206

1
2
3
4
5
ERROR ITMS-90206: "Invalid Bundle. The bundle at 'xxx WatchKit Extension.appex' contains disallowed file 'Frameworks'." I have tried all the ...
这个发现是扩展加载了一些不该加载的,
xcode8 把报错的扩展 build settings 搜索"Always Embed Swift Standard Libraries"
设置成NO
OK 了 上传包不报错了

2 上传包error -22421

原因 苹果服务器抽风多次尝试ok

3 上传包 -4238

原因 自己抽风忘记更改版本号码有重复的


This application’s application-identifier entitlement does not match that of the installed application. These values must match for an upgrade to be allowed

原因:这个项目App已经存在这个真机上了,这个App是旧的identifier运行的。
真机手动删除也ok

解决方法:Xcode —>Window —> Devices —> 自己的真机 —> installed Apps —-> 删除这个App —-> 重新运行


Cannot Synthesize Weak Property Because The Current Deployment Target Does Not Support Weak References

在用 pod 升级依赖(Installing StreamingKit 0.1.30 (was 0.1.29)) 项目后报错:

原因 :
https://github.com/tumtumtum/StreamingKit/blob/master/StreamingKit.podspec
最低支持ios4.3 不支持weak

解决方案:
在 Podfile 下面添加如下代码:

1
2
3
4
5
6
7
8
9
10
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if target.name == 'StreamingKit'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '7.0'
end
end
end
end

From: https://zhuanlan.zhihu.com/p/35251092

升级到 Swift 4.1 的两个 Crash

前两天升级了 Xcode 9.3, 也就升级到 Swift 4.1。使用它来编译一些例子工程,源码基本不用修改,只是报了警告,几个地方需要将 flatMap 名字修改成 compactMap。见提案SE-0187

但工程运行起来,出现两个 Crash,记录一下。

1.

Crash 在如下地方,详细情况参见 [Swift/SR-7240]

swiftgetObjectType
UIApplicationDelegate.application(
:open:sourceApplication:annotation:)

原因是

func application(_ application: UIApplication, 
                      open url: URL, 
             sourceApplication: String?, 
                    annotation: Any) -> Bool

已经在 iOS 9.0 中废弃了。需要将其修改为

public func application(_ app: UIApplication, 
                     open url: URL, 
                      options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool

2.

例子工程使用了一个 HandyJSON 的库,升级到 Swift 4.1 后 Crash 了。

HandyJSON 应该是裁剪使用了一些 Reflection 代码。标准的 Swift Api 还没有完整反射功能的,HandyJSON 的某些接口实际上利用了 Swift 对象,没有公开的内存布局进行赋值,这种做法是一种 Hack 手段,比较危险。

Crash 在

var numberOfFields: Int {
    return Int(pointer.pointee.numberOfFields)
}

修正方法见, Xcode9.3 Swift4.1 crash 解决方案 #239

苹果每次升级,并不追求完全的兼容。升级后,原来的 App 总会有些小问题,强迫着开发者跟进修改。AppStore 很多两三年没有更新的 App, 实际上已经不能运行了。而微软就很讲究兼容性,Win95 上编译好的软件,在 Windows 7 也照样可以跑起来。

打破兼容性,抛弃历史包袱,的确可以使得生态更好。但打破兼容,假若开发者不快速跟进,整个生态早就崩掉了。苹果命好,如此任性。


【iOS 开发】解决使用 CocoaPods 执行 pod install 时出现 - Use the $(inherited) flag … 警告
pod 升级1.5.0 后出了不少警告

1
2
3
4
5
[!] The `boosjdance [Debug]` target overrides the `ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES` build setting defined in `Pods/Target Support Files/Pods-boosjdance/Pods-boosjdance.debug.xcconfig'. This can lead to problems with the CocoaPods installation
- Use the `$(inherited)` flag, or
- Remove the build settings from the target.
[!] The `boosjdance [Release]` target overrides the `ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES` build setting defined in `Pods/Target Support Files/Pods-boosjdance/Pods-boosjdance.release.xcconfig'. This can lead to problems with the CocoaPods installation

解决方法
打开项目 Target - Build Settings ,搜索 Other Linker Flags ,在这个设置上加入 $(inherited) 。

打开项目 Target - Build Settings,依次搜索如下图所示的警告上提示的设置名称,将这些设置选项全部改为 $(inherited) ,或者选中这些设置按下 delete 键恢复原设置。

如果有 FRAMEWORK_SEARCH_PATHS 这个设置的警告的话,最好先把当前的设置项记录下来,然后选中设置按下 delete 以后,再把之前的设置加进去,否则编译可能会出现很多报错。

然后重新执行 pod install 或者 pod update 就会发现警告消失了。

如果我的方法不能够解决你的问题的话,可以试一下网上的另一种方法,就是点击项目文件 project.xcodeproj ,右键显示包内容,用文本编辑器打开 project.pbxproj ,command + F 搜索 OTHER_LDFLAGS ,删除搜索到的设置,command + S 保存,然后重新执行 pod install 或者 pod update 。


更新xcode9.3 出现Block implicitly retains ‘self’;explicitly mention ‘self’ to in dicate this… 警告,

解决 Building Setting -> 搜索
implicit retain of ‘self’
将对应的值改为NO


xcode报错, 系统迁移后, 运行错误,提示权限问题

Permission denied Command PhaseScriptExecution failed with a nonzero exit code

错误提示没有权限,
chmod a+x .sh文件权限 (注意拷贝过来的地址可能需要转译)


UIActionSheet 在ipad中弹不出的问题

UIActionSheet 其实已经弃用,但是我们还在适配ios7 所以还在用着, 遇到过两次ipad 弹不出的问题, 然后时间久了会忘记,特此记录一下
创建用

1
2
3
4
5
6
7
8
9
let actionSheet = UIActionSheet()
actionSheet.delegate = self
actionSheet.actionSheetStyle = UIActionSheetStyle.default
actionSheet.addButton(withTitle: "取消")
actionSheet.addButton(withTitle: "从手机相册选择")
actionSheet.addButton(withTitle: "拍照")
actionSheet.cancelButtonIndex = 0
actionSheet.show(in: (controller?.view)!)

代理方法改用diss 的那个

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
func actionSheet(_ actionSheet: UIActionSheet, didDismissWithButtonIndex buttonIndex: Int) {
if buttonIndex == 0{
return
}
let picker = UIImagePickerController()
if buttonIndex == 1{
picker.sourceType = UIImagePickerControllerSourceType.photoLibrary
}else if buttonIndex == 2{
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera) == false{
return
}
picker.sourceType = UIImagePickerControllerSourceType.camera
}
picker.delegate = self
picker.allowsEditing = true
self.findController().present(picker, animated: true) {
}
}
// 手机上ok 但是ipad 报错
// func actionSheet(_ actionSheet: UIActionSheet, clickedButtonAt buttonIndex: Int){
// Getdevice.println("clickindex \(buttonIndex)")
// if buttonIndex == 0{
// return
// }
// let picker = UIImagePickerController()
//
// if buttonIndex == 1{
// picker.sourceType = UIImagePickerControllerSourceType.photoLibrary
// }else if buttonIndex == 2{
// if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera) == false{
// return
// }
// picker.sourceType = UIImagePickerControllerSourceType.camera
// }
// picker.delegate = self
// picker.allowsEditing = true
// self.findController().present(picker, animated: true) {
//
// }
// }

Swift中String和Character的使用与总结

使用String字面量给常量赋值

1
2
let string = "string literal value"
//常量string将会自动推断为String类型

初始化一个空的String

1
2
3
var emptyStr = "" //使用空字符串字面量
var anotherEmptyStr = String() //使用构造方法
//两者没有区别

使用isEmpty判断空String:

1
2
3
if emptyStr.isEmpty {
print("have nothing here")
}

String的可变性
使用“+”连接字符串,当然也支持自加运算符”+=”

1
2
3
4
5
6
7
8
var variableStr = "LastName"
variableStr += "and FirstName"
/// variableStr is "LastName and FirstName"
//**but if:
let constantStr = "Gender"
constantStr += "and another Highlander"
///编译器会报错,被声明为常量的字符串不能被修改!

跟oc不同,swift的String通过var/let 变量/常量 标识决定其是否可变(can be mutated),而不需要选择NSString 还是 NSMutableString。

String跟Characters的连接

1
2
3
4
5
let str = "hello world "
let char: Character = "!"
str.append(char)
// 结果str为: "hello world !"

遍历String

1
2
3
4
5
6
7
8
for char in "myStr".characters {
print(char)
}
//m
//y
//S
//t
//r

字符串插值
在字符串中插入常量变量表达式等,构造一个新的字符串”通过()”:

1
2
3
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
/// message is "3 times 2.5 is 7.5"

String中使用转义字符
在字符串中输入反斜线”\” 水平制表符”t” 换行”n” 双引号”“” 单引号”’” 等都需要在前面添加”\”进行转义,同时可以在转义字符后添加Unicode来进行特殊符号表情的显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//**双引号转义
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
///"Imagination is more important than knowledge" - Einstein
//**Unicode转义
let dollarSign = "\u{24}" // $, Unicode scalar U+0024
let blackHeart = "\u{2665}" // ♥, Unicode scalar U+2665
let sparklingHeart = "\u{1F496}" // ��, Unicode scalar U+1F496
//**扩展自行集
//**对应关系
// \u{D55C}----한
// \u{1112}----ᄒ
// \u{1161}----ᅡ
// \u{11AB}----ᆫ
let KoreaStr = "\u{D55C}\u{1112}\u{1161}\u{11AB}" //한한

String长度
string.characters.count

1
2
3
4
5
6
7
8
9
10
11
12
13
let str = "1234567890"
print("str has \(str.characters.count) characters")
//输出 "star has 10 characters"
//**为String增加笔画不会造成长度增加:
var str = "cafe"
print("the number of characters in \(word) is \(word.characters.count)")
// 输出 "the number of characters in cafe is 4"
//**now append some Unicode:
word += "\u{301}"
print("the number of characters in \(word) is \(word.characters.count)")
//输出 "the number of characters in café is 4"
//仅仅是改变了最后一个字符,并没有增加字符串的长度

正因为swift支持扩展字形集,不同的字符,和相同的不同表示的字符可能需要不同量的存储器来存储,所以在swift中characters所占用的存储量是不一定相同的,因此不能像oc计算NSString那样使用字符串来迭代计算,而应该遍历字符串的characters来确定字符串的长度。

####访问和修改字符串
可以通过其方法和属性,或者下标,来访问或者修改字符串

###字符串索引
swift中的字符串具有相关连的索引类型(String.Index),可对应其每个位置的Character

正如上面所说,不同的字符串可能需要不同数量的内存来存储,所以为了确定哪些character在特定的位置上,我们必须遍历确定每个Unicode的开始结束位置,因此,String不能使用整形作索引。

startIndex: 访问String第一个位置的字符 endIndex: 访问String最后一个位置的字符
(一个空的字符串或者长度为1的字符串,startIndex和endIndex相等)

predecessor(), successor(), advancedBy() 一个String.Index值可以通过调用predecessor()方法来访问其前一个index, 调用successor()来访问其后一个index, 或者调用advancedBy()来指定访问相对位置的index( 之后5位的index: advancedBy(5) 往前5位的index: advancedBy(-5) )

1
2
3
4
5
6
7
8
9
10
11
let greeting = "Guten Tag!"
greeting[greeting.startIndex]
//G
greeting[greeting.endIndex.predecessor()]
//!
greeting[greeting.startIndex.successor()]
//u
let index = greeting.startIndex.advancedBy(7)
//a
greeting[index]
//输出 a

indiced : 字符串Index的集合

1
2
3
4
for index in greeting.characters.indices {
print("\(greeting[index])", terminator: " ")
}
///prints "G u t e n T a g !"

插入/移除
利用index,在制定位置插入字符character

1
2
3
var helloStr = "hello"
helloStr.insert("~", atIndex: helloStr.endIndex)
// hello~

同理,插入字符串(字符的集合)

1
2
3
4
5
6
7
8
var helloStr = "hello!"
helloStr.insertContentOf(" world!".characters, at: hello.endIndex)
// hello! world
//用上面的知识,再追求下完美:
var helloStr = "hello!"
helloStr.insertContentOf(" world".characters, at: hello.endIndex.predecessor())
// hello world!

移除(index):

1
2
3
4
5
6
7
var helloStr = "hello world!"
helloStr.removeAtIndex(helloStr.endIndex.predecessor())
// hello world
//注意:
// endIndex是指最后一个index位(将要输入内容的index位)
//所以删除最后一个字符使用的index是endIndex.predecessor()(将要输入内容的index的前一个index位)
//而不是endIndex

移除(Range):

1
2
3
4
5
6
7
var helloStr = "hello world!"
let range = Range(start: helloStr.endIndex.advancedBy(-6), end: helloStr.endIndex.predecessor())
// 顺便贴一个new Range的简易写法:
// let range = helloStr.endIndex.advancedBy(-6)..<helloStr.endIndex
// 效果是一样的
helloStr.removeRange(range)
// hello

####字符串比较
两个纯字符串比较

1
2
3
4
5
6
7
8
let oneStr = "We're a lot alike, you and I."
let anotherStr = "We're a lot alike, you and I."
if oneStr == anotherStr {
print("These two strings are considered equal")
}
//输出: These two strings are considered equal
//相等

两个由characters组成的字符串比较

1
2
3
4
5
6
7
8
9
10
11
let oneStr = "Voulez-vous un caf\u{E9}?"
//Voulez-vous un café?
let anotherStr = "Voulez-vous un caf\u{65}\u{301}?"
//Voulez-vous un café?
//两者虽然看起来内容字符不同,其实\u{65}\u{301}是一个e和一个音调符号,根据上面的知识,结果组合成é(\u{E9})
if oneStr == anotherStr {
print("These two strings are considered equal")
}
//输出: These two strings are considered equal
//相等

两个表现相同的character比较

1
2
3
4
5
6
7
8
9
10
let oneChar: Character = "\u{41}"
//拉丁字母中的A
let anotherChar: Character = "\u{0410}"
//西里尔字母中的A
if oneChar != anotherChar {
print(These two characters are not equivalent)
}
//输出: These two characters are not equivalent
//不相等!

前缀和后缀的比较 我们可以使用hasPrefix()方法和hasSuffix()去匹配String的前缀和后缀,并返回一个Boolean值

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
let romeoAndJuliet = [
"Act 1 Scene 1: Verona, A public place",
"Act 1 Scene 2: Capulet's mansion",
"Act 1 Scene 3: A room in Capulet's mansion",
"Act 1 Scene 4: A street outside Capulet's mansion",
"Act 1 Scene 5: The Great Hall in Capulet's mansion",
"Act 2 Scene 1: Outside Capulet's mansion",
"Act 2 Scene 2: Capulet's orchard",
"Act 2 Scene 3: Outside Friar Lawrence's cell",
"Act 2 Scene 4: A street in Verona",
"Act 2 Scene 5: Capulet's mansion",
"Act 2 Scene 6: Friar Lawrence's cell"
]
//----遍历这个字符数组,匹配下前缀看看效果
var count = 0
for str in romeoAndJuliet {
if str.hasPrefix("Act 1 ") {
count++
}
}
print("There are \(count) string with Act 1 ")
// 输出: "There are 5 string with Act 1"
//----后缀呢
var count = 0
for str in romeoAndJuliet {
if str.hasSuffix("Capulet's mansion") {
count++
}
}
print("There are \(count) mansion string")
// 输出: "There are 6 mansion stressing"

String使用UTF-8编码表示
复习一下,上面也提到,Swift中的String支持emoji表情和众多特殊字符,这也是String一个单位长度不一定等于两个character(汉字)或者1个character(英文字母)的原因。 先回到我们的话题。String和UTF-8的对应关系,我们来看一张官方电子书中的表: ![]/content/images/2015/12/utf8.png()

1
2
3
4
5
6
7
8
//上图中对应的String:
//let dogString = "Dog!!��"
//同时String中的UTF-8编码也是可以像char那样遍历的
for unitCode in dogString.utf8 {
print("\(unitCode) ", terminator: "")
}
//输出: 68 111 103 226 128 188 240 159 144 182

同理String也可以以UTF-16 和Unicode的方式遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for unitCode in dogString.utf16 {
}
for scalar in dogString.unicodeScalars {
print("\(scalar.value) ", terminator: "")
}
// 68 111 103 8252 128054
for scalar in dogString.unicodeScalars {
print("\(scalar) ", terminator: "")
}
// D o g !! ��
//注意: 直接printunicodeScalar的话跟String的输出是一样效果的
//我们print出他的value,才是我们想要的编码

copy 自:
http://zyden.vicp.cc/string-character/

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
                     }

OS X终端使用配置socks5 代理

OS X终端使用配置socks5 代理

在终端环境下科学上网,本来是想用proxychains4的,可是不知道什么问题,在我的电脑osx 10.11.1下没有代理效果。
如果已经启用shadowsocks 本地代理为 socks5://127.0.0.1:1080
在终端下使用

1
export ALL_PROXY=socks5://127.0.0.1:1080

清除代理

1
unset ALL_PROXY

为了方便呢,可以在.bash_profile中加上这个

1
2
3
4
5
6
7
8
9
function setproxy() {
# export {HTTP,HTTPS,FTP}_PROXY="http://127.0.0.1:3128" 也可以设置http代理
export ALL_PROXY=socks5://127.0.0.1:1080
}
function unsetproxy() {
# unset {HTTP,HTTPS,FTP}_PROXY
unset ALL_PROXY
}

原文链接:http://www.jianshu.com/p/16d7275ec736

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