Kenny 肉桂的主页

记录自己的进步


  • 首页

  • 归档

  • 标签

  • 搜索
close

RACObserve 和 rac_textSignal 的搭配使用

发表于 2016-06-18   |   分类于 ReactiveCocoa   |  

项目中有个小需求,文本框与按钮绑定.当文本框内容符合规则的时候,按钮才会可用.把判定条件修改一下,代码如下:

1
2
3
 RAC(self.loginButton,enabled)  = [self.textFiled.rac_textSignal  map:^id(NSString *value) {
return @(value.length>3);
}];

但是如果在发送请求之后,通过代码清除了文本框的内容,按钮并不会改变状态.

想到了, 应该是这个 rac_textSignal 出现问题了.
看一下它的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 - (RACSignal *)rac_textSignal {
@weakify(self);
return [[[[[RACSignal
defer:^{
@strongify(self);
return [RACSignal return:self];
}]
concat:[self rac_signalForControlEvents:UIControlEventAllEditingEvents]]
map:^(UITextField *x) {
return x.text;
}]
takeUntil:self.rac_willDeallocSignal]
setNameWithFormat:@"%@ -rac_textSignal", self.rac_description];
}

几个关键词的解释:
defer : 将代码的创建推迟到信号被订阅
concat: 连接信号,第一个信号必须发送完成,第二个信号才会被激活
map : 映射,将信号内容转换
takeUtil : signalA takeUntil:signalB 当signalB激活之后,停止signalA 的订阅

其实主要的是, 这个 signal 是监听的: UIControlEventAllEditingEvents . 那么也就是说对setter 方式不会触发信号

RACObserve 是一个常用的宏,我们都知道是监听属性值改变的.

1
2
3
4
5
6
7
8
#define RACObserve(TARGET, KEYPATH) \
({ \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
__weak id target_ = (TARGET); \
[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
_Pragma("clang diagnostic pop") \
})

主要代码是:[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self];
然后这个rac_valuesForKeyPath的实现如下:

1
2
3
4
5
6
7
8
9
- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer {
return [[[self
rac_valuesAndChangesForKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:observer]
map:^(RACTuple *value) {
// -map: because it doesn't require the block trampoline that -reduceEach: uses
return value[0];
}]
setNameWithFormat:@"RACObserve(%@, %@)", self.rac_description, keyPath];
}

主要代码:

1
rac_valuesAndChangesForKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:observer]

也就是这个是通过 KVO 实现的. 而 KVO 得实现是通过临时生成一个子类,并重写父类的 setter 方法.这个在官方文档中有说明:

1
2
3
4
5
6
Key-Value Observing Implementation Details

Automatic key-value observing is implemented using a technique called isa-swizzling. 
The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data. 
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance. 
You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.

最终实现

了解了两者的实现,就可以很容易实现代码:

1
2
3
RAC(self.loginButton,enabled) =[ [RACObserve(self.textFiled, text)  merge:self.textFiled.rac_textSignal ] map:^id(NSString *value) {
return @(value.length>3);
}];

Swift3 新特性

发表于 2016-06-15   |   分类于 Swift   |  

看了一下刚出的 Session 406,简单记录一下关于 Swift3.0相比2.2的改动. 能力有限,若有谬误,欢迎指正 .

简化的 API

  • 优化将方法名切分,将操作中的动词提取,作为方法名.将其余部分作为参数名.
  • 在不引起歧义的情况下,去掉了重复性的名词.

Swift 2.2

1
2
3
4
5
6
7
8
9
var array:Array = [1,2]

array.appendContentsOf([2,3,4])
array.insert(1, atIndex: 0)

var url = NSURL(string: "randomLoacation")!

if url.fileURL {}
var x = url.URLByAppendingPathComponent("file.txt")

Swift 3.0

1
2
3
4
5
6
7
8
9
10
//Swift.Array
var array:Array = [1,2]

array.append(contentsOf: [2,3,4])
array.insert(1, at: 0) //index没有什么特定信息,只是重复

//Foudation.NSURL
var url = NSURL(string: "randomLoacation")!
if url.isFileURL {} //意思更明确
var x = url.appendingPathComponent("file.txt")

一致的函数参数标签

原来2.2中, 第一个参数标签可以省略.类似这样:

1
2
3
4
5
6

func myFunction(a: Int , b: Int , c: Int){

}

myFunction(42, b: 57, c: 39)

在 3.0 中,上面的用法会报错. 需要显式写出标签,类似这样:

1
2
3
4
5
6

func myFunction(a: Int , b: Int , c: Int){

}

myFunction(a: 10, b: 10, c: 10)

当然,如果实在不想写,可以在声明的时候使用 _ .

1
2
3
4
5
6

func myFunction(_ a: Int , b: Int , c: Int){

}

myFunction(10, b: 10, c: 10)

移动 Where 语句到函数声明的结尾

在 Swift2.2中,泛型约束语句Where需要放到泛型的<>中.

1
2
3
4
5
6
7
func anyCommon<T: SequenceType, U: SequenceType
where T.Generator.Element : Equatable,
T.Generator.Element == U.Generator.Element>(lhs: T, rhs: U) -> Bool{
print("here")
return true;

}

在 Swift3.0中,将其提取到声明之后:
代码是参考的 Session 中的代码,但是本人用 Xcode8.0的 Playground 报错.但是主旨不会错,就是将Where语句提取出来.

1
2
3
4
5
6
7
func anyCommons<T: Sequence, U: Sequence>(lhs: T, rhs: U) -> Bool
where T.Element: Equatable,T.Element == U.Element
{
print("here")
return true;

}

结果未使用警告的方法

对于一个没有使用的函数返回值(或者自定义的变量值),编译器都会给你一个警告.因为既然你认为有返回值, 而你却没有使用.这可能就是你忘记了.这种操作可能会造成你编码的 bug

1
2
3
4
5
6
func plusOne(_ a : Int) ->Int{
print(a)
return a+1
}

plusOne(3) //得到警告 Result of call to 'plusOne' is unused

但是一些情况下.我们确实不需要用到这个返回值,而且仅仅是为了函数的一个副作用( side effect) 而去调用函数. 例如,上面代码中的print(a).这就是我调用函数的目的.
为了消除警告,可以使用如下方式:

1
2
3
4
5
6
7

func plusOne(_ a : Int) ->Int{
print(a)
return a+1
}

_ = plusOne(3)

或者使用标记 @discardableResult:

1
2
3
4
5
6
7
@discardableResult
func plusOne(_ a : Int) ->Int{
print(a)
return a+1
}

plusOne(3)

索引集合

1
2
3
let collection = ["a","b","c","d"]

var i = collection.startIndex

在 Swift 2.2 中

1
swift 2 : var next = i.successor()

在 Swift 3.0 中

1
var next = collection.index(after: 2)

浮点型和数值型

  • Float,Double,Float80 和 CGFloat统一使用新的浮点协议( Floating Point Protocol )
  • 提供 IEEE-754 相关的属性和方法 (IEEE 754 标准是IEEE二进位浮点数算术标准(IEEE Standard for Floating-Point Arithmetic)的标准编号 ,等同于国际标准ISO/IEC/IEEE 60559[2])
  • 允许在所有的浮点类型上使用算法泛型(水平有限,原文是:permits algrithms to be gerneric over all floating point types)

用 PI 来举个例子:
Swift 2.2

1
let v = 2 * Float(M_PI)

而在 Swift3.0中

1
let v = 2 * Float.pi

如果第一个操作数是浮点数,还可以这样写:

1
let anotherV = 2.0 * .pi / 180

隐式解包可选

在 Swift2.2中

1
2
3
4
5
func f(value: Int!){
let x = value + 1 // x 是 Int 类型 - 强制解包
let y = value // y 是 Int!

}

在 Swift 3.0 中

1
2
3
4
5
func f(value: Int!){
let x = value + 1 // x 是 Int 类型 - 强制解包
let y = value // y 是 Int?

}

具体参考:
SE-0054

一些细节的增强

  • 当前文件访问级别,新增的权限控制关键字 fileprivate SE-0025
  • case中的标签可以使用多种模式 SE-0043
  • 泛型别名 SE-0048
  • 引用Objective-C 的 key-paths SE-0062
  • 引用 Objective-C中属性的 getter 和 setter SE-0064
  • 协议和协议拓展的别名 SE-0092

    移除的功能

  1. 函数柯里化Currying介绍
  2. 函数参数中的 var 详细介绍
  3. 去掉 ++ 和 -- 操作符
  4. C 风格的for循环
  5. 通过元组的形式传递函数参数列表 .详细介绍

对第5条做一个说明:
在 swift 2.2 中,我们可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let param = (20,"kenny","male")

func personInfo(age: Int ,_ name: String ,_ gender: String){

}

personInfo(param)

//也可以不省略标签

let param = (age:20, name:"kenny", gender:"male")

func personInfo(age age: Int , name: String , gender: String){

}

personInfo(param)

这个语法在 Swift3.0中被去掉了.原因是这么传递,可能会和有一个元组类型参数的函数混淆.

参考 : WWDC 2016 Session 402 What’s new in Swift

在 Swift 中使用DEBUG标记

发表于 2016-06-14   |   分类于 Swift   |  

在 OC 中,我们经常会使用 DEBUG 标记来区分开发版本和发行版本,例如,控制调试信息的打印.

但是在使用 Swift 之后,我们发现DEBUG 不能正常工作.原因是 Swift 中没有DEBUG这个标记.例如下面的代码:

1
2
3
4
5
func GUILog<T>(_ message: T, fileName: String = #function, methodName: String = #function, lineNumber: Int = #line) {
#if DEBUG
print("\(methodName)[\(lineNumber)]:\(message)")
#endif
}

虽然并不会报错,但是始终不会有打印.

解决方案

在 Build Settings 中搜索 other swift flags

点击图中红色框内部(注意不是直接点击+),会出现一个这样的对话框.(可能已经有其他标记,如果你使用了 cocoapods 的话,不过不用在意),点击左下角的+ ,或者双击任意空白行,输入-DDEBUG.

点击其它任意区域.结果如图

现在,DEBUG就能正常工作了!
可以通过调整模式来进行测试:

把图中的地方改成Release,即可进行测试.

Tips

这个编译标记可以随便定义,并不是只能是DEBUG,只要标记能够对应,即可正常工作.编译器只是会看,在Debug模式下,有哪些标记.(或者在 Release 模式下有哪些标记.)例如:

代码:

1
2
3
4
5
func GUILog<T>(_ message: T, fileName: String = #function, methodName: String = #function, lineNumber: Int = #line) {
#if DEFAULT
print("\(methodName)[\(lineNumber)]:\(message)")
#endif
}

从 variable with a setter must also have a getter 引发的思考

发表于 2016-05-13   |   分类于 Swift进阶   |  

今天一哥们在群里问:”为什么我不能只重写 setter ,还要多写个 getter ?”
我一听也是懵了,后来在 playgroud 里面写,果然得到了一个错误:

1
2
3
4
5
6
7
8
9
10
11
class Person{

var labelText:String
{
set(newValue){
self.labelText = newValue
}

}

}

Variable With a setter must also have a getter

后来,查找 stackflow, 找到了答案:

一旦你给一个属性添加了 getter 或者 setter ,这个属性将变成 计算属性.

后来,我在The Swift Programming Language 2.2 的 Properties 一章中,找到这样一段描述:

1
“In addition to stored properties, classes, structures, and enumerations can define computed properties, which do not actually store a value. Instead, they provide a getter and an optional setter to retrieve and set other properties and values indirectly.”

除了存储属性之外,类,结构体和枚举还可以定义计算属性,计算属性 不存储值 ,它们提供一个 getter 和一个 可选的 setter,借此来读取和间接地设置其他属性和值.

我在文档中没有找到更多的说明,下面来说说我自己的思考:

  1. 计算属性不存储值,那么也就是说,只能我显式地告诉程序,这个值是怎么来的.所以,需要有一个 getter ,它是必须的,否则这个属性就不是计算属性.而是存储属性.

  2. setter是可选的,可以不写 (不写 setter 不写 getter,那么这个属性还是存储属性).但是一旦写了setter(这个属性变成了计算属性),那么getter也是必须的

willSet 和 didSet

刚刚说明了关于计算属性的一些特点,但是问题来了,我们经常有拦截setter来做一些自定义处理的需求. 在 OC 中,这个很简单,重写 setter方法就好了,但是, Swift 中, 添加setter会让属性变味.

Swift 的设计者肯定也是考虑到了这一点,所以提供了 willSet 和 didSet这两个属性观察方法.用法如下:

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

var labelText:String = "great"
{
willSet{
print("labeltext will change!")

}
didSet{
print("labeltext did change!")
// labelText = "defalut"
}
}
}

输出结果:

1
2
3
labeltext will change!
labeltext did change!
11

这样,我们可以过滤值,可以自己处理一些事情,也不会误把存储属性转换成计算属性.

手把手教你创建自己的Cocoapods库

发表于 2016-05-12   |   分类于 开发环境相关   |  

如果您能看这篇文章,相信您已经了解并喜欢上了 Cocoapods, 下面我们一步一步的创建一个属于自己的 Cocoapods 库.

创建

通过下面命令,可以使用一个创建向导简历 Cocoapods 库

1
pod lib create GUICodeSnippet

然后终端会进行引导,依次选择即可. 回车键确认默认选择.

问题依次是:

  1. 语言

  2. 创建demo工程( 极力推荐创建)

  3. 选择测试框架 (可选择默认,如果非常确定没有需要,可以选择None)

  4. View-based Testing (官方推荐使用 FBSnapShotTestCase)

  5. OC类前缀 (如果语言选择非OC,不会有这项)

打开工程,会发现有一个 GUICodeSnippet 和一个 Pods, 在
Pods->Development Pods->GUICodeSnippet->Pod->Classes中,有一个ReplaceMe.m

你需要将这个文件替换成自己的代码文件.

源码验证

进入 Example 目录 , 执行

1
pod install

然后 Xcode 打开 xcworkspace 文件 ,去跑一下项目,如果没有错误,证明添加的代码没有问题,可以继续进行

准备工作

配置 podspec 文件

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
#
# Be sure to run `pod lib lint GUICodeSnippet.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#

Pod::Spec.new do |s|
s.name = "GUICodeSnippet"
s.version = "0.1.8"
s.summary = "code segment of kennyGui"

# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
# * Try to keep it short, snappy and to the point.
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!
s.description = <<-DESC
code segment of kenny ,and in most situation ,is for private use only
DESC

s.homepage = "https://git.oschina.net/kennygui/GUICodeSnippet"
# s.screenshots = "www.example.com/screenshots_1", "www.example.com/screenshots_2"
s.license = 'MIT'
s.author = { "katsurake" => "katsurake@foxmail.com" }
s.source = { :git => "https://git.oschina.net/kennygui/GUICodeSnippet.git", :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'

s.platform = :ios, '7.0'
s.requires_arc = true

s.source_files = 'Pod/Classes/*.h'
s.resource_bundles = {
'GUICodeSnippet' => ['Pod/Assets/*.png']
}

s.public_header_files = 'Pod/Classes/*.h'
s.frameworks = 'UIKit', 'MapKit'
s.dependency 'AFNetworking', '~> 2.3'
s.dependency 'CocoaLumberjack', '~> 2.2.0'
s.dependency 'MBProgressHUD', '~> 0.9.2'
s.dependency 'Masonry', '~> 0.6.4'
s.dependency 'ReactiveCocoa', '~> 2.5'
s.dependency 'YapDatabase', '~> 2.8.3'
s.dependency 'DTCoreText', '~> 1.6.17'
s.dependency 'MJExtension', '~> 3.0.10'
end

创建完毕之后,随手使用
pod lib lint
命令验证是否编写正确,如果没有正确,根据提示修改即可.

几个好用的命令:

完整打印详情,可以帮助我们调试,用法 ` pod lib lint --verbose`
1
 
 
 ```--allow-warnings``` 忽略警告,由于一些三方代码有警告,所以造成始终无法通过,可以用这个方法忽略警告,用法同上
 


### 创建代码仓库
 在github 或者其他托管平台建立一个空项目,最好项目名称和本地的一致

```objc

git add .
git commit -m “Initial Commit"
git remote add origin https://github.com/kennyGui/GUICodeSnippet.git 
git push -u origin master

其实我更喜欢的方式是, Clone 空项目,然后替换代码的 .git 文件夹,这样更暴力,具体怎么选择,看个人喜好.

打 tag

1
2
3
4
5
6
7
8
git tag -a 0.1.0 -m "初始版本"

通常的git push不会将标签对象提交到git服务器,我们需要进行显式的操作:
git push origin 0.1.2 # 将v0.1.2标签提交到git服务器
或者
git push origin –tags # 将本地所有标签一次性提交到git服务器
验证标签成功
git tag #查看标签

推送描述文件

使用这个命令推送自己的库的信息到官方的 Master 库

trunk push GUICodeSnippet.podspec ```objc
1

但是如果你是第一次使用的话,需要注册 Session
使用如下命令注册自己的邮箱(注意替换成自己的邮箱)

pod trunk register orta@cocoapods.org ‘Orta Therox’ —description=’macbook air’

1
2
3
稍后, 邮箱会收到邮件, 点击链接即可

``` pod trunk push BlinkingLabel.podspec

由于网络原因(你懂得),可能会推送失败,多尝试几次即可.

验证

可以让同事或者朋友使用

1
pod search GUICodeSnippet

这个命令进行测试,但是,如果是第一次,应该是找不到的.
需要使用
pod setup更新一下本地的 Maser 库信息

结语

好了,现在,你就可以将自己的代码共享给小伙伴使用啦.

现在 pod 里面的 Class 都是放在一个文件夹下的,没有办法实现子文件夹,(即使工程里面有多个文件夹,但是弄好 cocoapods 之后,也是都放到一个文件夹下面了)

这需要我们的 spec 里面配置 subspec ,这个知识点会在下一篇文章中说

升级 Chisel 遇到的问题和解决记录

发表于 2016-05-12   |   分类于 开发环境相关   |  

升级了 Xcode7.3后,发现 Chisel 突然不好使了,于是再度打开主页,想升级一下应该能解决问题.

安装官方说明,需要首先升级 homebrew ,其实知道 ,这一步不是必须的。但是为了后面不出现问题,还是顺手一并升级吧!

然后,开始了问题之旅:

使用 brew update,结果得到了以下提示:

1
2
3
warning: unable to unlink CONTRIBUTING.md: Permission denied
warning: unable to unlink SUPPORTERS.md: Permission denied
fatal: cannot create directory at '.github': Permission denied

不难看出,是权限问题,修改权限

sudo chmod -R 777 /usr/local

相关知识:
可以不必修改为 777 的, 关于权限的更多,感兴趣的可以自行 google
r表示读权限,4 , w表示写权限,2 , x表示执行权限, 1

可是修改之后,仍然不好用.

1
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- mach (LoadError)

一不做,二不休,干脆尝试重新安装 homebrew

在终端输入
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
然后得到了下面的提示

1
2
3
4
5
6
kenny@kenny:~|⇒  /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
-e:77: warning: Insecure world writable dir /usr/local/bin in PATH, mode 040777
It appears Homebrew is already installed. If your intent is to reinstall you
should do the following before running this installer again:
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"
The current contents of /usr/local are bin Cellar CODEOFCONDUCT.md CONTRIBUTING.md etc include lib Library LICENSE.txt opt README.md sbin share SUPPORTERS.md var .git .gitignore

提示的是 git 的错误
尝试 cd 到/usr/local目录下,
git status
果然有一堆修改内容,这些应该是之前各种操作没有成功完成,导致修改没有被妥善处理
然后:
git reset --hard

kenny@kenny:/usr/local|master⇒ git status On branch master nothing to commit, working directory clean

可能还需要执行:
git clean -df

第一个是放弃已经 stage 的文件的更改
第二个是对没有 stage 的文件进行放弃

执行安装

brew install chisel
1
Error: chisel-1.2.0 already installed
To install this version, first `brew unlink chisel`
```objc

卸载旧版
```kenny@kenny:/usr/local|master⇒  brew unlink chisel
Unlinking /usr/local/Cellar/chisel/1.2.0... 0 symlinks removed

重新安装
kenny@kenny:/usr/local|master⇒ brew install chisel ==> Downloading https://github.com/facebook/chisel/archive/1.4.0.tar.gz ==> Downloading from https://codeload.github.com/facebook/chisel/tar.gz/1.4.0objc

可以看到,现在升级到了1.4了.

到 Xcode 中测试,发现也欢乐的好用了 !

1…345…13
桂庆

桂庆

Kenny 肉桂的主页 记录自己的进步

75 日志
17 分类
23 标签
RSS
微博
© 2013 - 2017 桂庆
由 Hexo 强力驱动
主题 - NexT.Mist