0%

原文地址

我认为我最喜欢 Swift 4.2 的可能是编译器自动为枚举生成一个 allCases 的集合。

如何知道在我的枚举里有多少个 Case

知道一个枚举有多少种情况或者去迭代所有的情况这通常是很有用的。在 Swift4.2 之前,你也许使用一个元编程工具例如Sourcery或者手动的维护这个集合

1
enum Direction {
2
  case left
3
  case right
4
}
5
6
extension Direction {
7
  static let allItems: [Direction] = [.left, .right]
8
}
9
10
Direction.allItems.count
11
// 2
12
Direction.allItems.forEach { print($0) }
13
// left
14
// right

手动维护是痛苦的,而且当我为枚举添加额外的 Cases,而忘记更新 allItems 的实现时,他无法按照预期工作

1
enum Direction {
2
  case left
3
  case right
4
  case up
5
  case down
6
}
7
8
Direction.allItems.count
9
// 2 ???

CaseIterable 协议

Swift4.2 为我们带来了 CaseIterable 协议。他们像Equatable,ComparableHashable都是通过编译器自动合成来实现的

你需要做的就是声明你的枚举遵循这个协议

1
enum Direction: CaseIterable {
2
  case left
3
  case right
4
  case up
5
  case down
6
}

编译器提供给你一个包含所有情况叫做allCases的集合

1
Direction.allCases.count
2
// 4
3
Direction.allCases.forEach { print($0) }
4
// left right up down.

注意:

  • 集合的顺序遵循枚举声明的顺序。
  • 编译器不会自动为具有关联值得枚举合成allCases的实现,因为他可能具有无限数量的情况
  • 文档中仍然要求你必须定义枚举的时候遵循这个协议,但在 Swift4.2 中不再是这样,只要在同一个文件中,就可以在扩展中声明一致性:

swift4.2 之后

1
enum Direction { ... }
2
extension Direction: CaseIterable {}

swift4.2 之前

1
enum Direction: CaseIterable { ... }
  • 你可以自己实现 CaseIterable 协议,缺点是如果有人向枚举添加新的 case,那么你将维护返回的实现并使其保持最新状态

原文地址
模式匹配在 swift 里是随处可见的,虽然switch case是匹配模式最常见的用法,但是 Swift 有多种类型的模式,这些模式可以混合甚至在switches外部使用,从而写出真正酷而短的代码。我特别感兴趣的事模式匹配可以用作于各种各样的事情。

乍一看下面的代码,很容易把模式匹配看做是简单的等值检查

1
switch 80 {
2
case 100:
3
    //
4
case 80:
5
    //匹配, 因为 80 == 80
6
default:
7
    break
8
}

鉴于下面的代码这种情况,你可能会认为像 case "eighty" 这样的将不会被编译,毕竟”eighty”和 80 不是相同的类型,如果你现在尝试一下的确会编译不通过

1
if case "eighty" = 80 {
2
    //error: expression pattern of type 'String' cannot match values of type 'Int'
3
}

但事实并非如此,在项目开发中,你可能已经注意到某些类型之间存在着特殊的交互,例如Ranges它们及其相关类型

1
switch 80 {
2
case 0...20:
3
	break
4
case 21...50:
5
    break
6
case 51...100:
7
    //匹配, 因为 80 在 51...100 之间
8
default:
9
    break
10
}

究其原因是~=这个匹配模式运算符,这个操作符看起来在常规项目里没什么用(你之前可能已经看到过这个确切的数字范围内的例子),但是他在 swift 内部使用很多,而且他是用于确认 case 语句
很多情况下,这个运算符是一个简单的等值检查的包装器(例如 Int),但是 Range 有一个特殊的实现,当针对其自己的关联类型使用时,允许他在模式匹配时具有自定义的行为

1
extension RangeExpression {
2
    @inlinable
3
    public static func ~= (pattern: Self, value: Bound) -> Bool {
4
      return pattern.contains(value)
5
    }
6
}

并且由于~=的作用域是全局的,为了编写自己的模式匹配的逻辑,你可以重载它。

例如,要让”eighty”匹配 80,你需要做的是,重载一个~=,使它将 String 模式和 Int 值匹配

1
func ~= (pattern: String, value: Int) -> Bool {
2
    if pattern == "eighty" {
3
        return value == 80
4
    } else if pattern == "not eighty" {
5
        return value != 80
6
    } else {
7
        return false
8
    }
9
}
10
11
switch 80 {
12
case "eighty":
13
    //编译通过并且匹配
14
case "not eighty":
15
    //
16
default:
17
   break
18
}

现在假设我的 APP 收到了字符串形式的深层链接,我需要知道这个深层链接属于哪一个控制器

1
enum AppTab: String {
2
    case home
3
    case orderHistory
4
    case profile
5
}
6
7
let deepLink = DeepLink(path: "home", parameters: [:])

这里有很多种方法来做这到这个功能,包括向 DeepLink 添加属性,如 correspondingTab 或者对 DeepLink 进行子类化,也可以使用自定义模式匹配,只需要一行代码而且无需修改 DeepLink。

1
func ~= (pattern: AppTab, value: DeepLink) -> Bool {
2
    return value.path.hasPrefix(pattern.rawValue)
3
}
4
5
switch deepLink {
6
case .home:
7
    homeViewController.handle(deepLink: deepLink)
8
case .orderHistory:
9
    historyViewController.handle(deepLink: deepLink)
10
case .profile:
11
    profileViewController.handle(deepLink: deepLink)
12
default:
13
    break
14
}

这允许你可以绕过将更广泛的类型映射到更具体的类型,例如如何将 Int 映射到工作日枚举。在这种情况下,您的后端将返回一周作为一个 Int 或一个 String,并使用自定义模式匹配,您可以使用这种更广泛的类型,同时仍然将其视为映射到更特定的枚举类型。当您想尝试使用而不想引用某些方法或类型时,这可能是有用的:

1
enum WeekDay: Int {
2
    case sunday
3
    case monday
4
    case tuesday
5
    case wednesday
6
    case thursday
7
    case friday
8
    case saturday
9
}
10
11
func ~= (pattern: WeekDay, value: Int) -> Bool {
12
    return pattern.rawValue == value
13
}
14
15
// Server returns:
16
// { nextHoliday: { weekDay: 5 } }
17
18
if case .friday? = nextHoliday?.weekDay {
19
    print("Woohoo!")
20
}

创建自定义模式是编写更干净代码的一种简单方法,不需要太多的努力,因为您可以通过 cases 直接跳到这一点而无需为类型添加其他属性 - 同时确保您的代码不会变得难以理解。

我很想知道你是否做过类似重载过~=这样的事情,如果你有任何建议,意见和反馈,请随时与我联系,@rockthebruno

参考和推荐阅读

Apple Docs:模式

在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数判断数组中是否含有该整数

实现思路: 由于这个二维数组是每行有序,每列有序。所以可以这样

  1. 可以每次选取二维数组中,右上角的数字。
  2. 若该数字等于该查找的数字,则查找过程结束。
  3. 如果该数字小于要查找的数字,则可以排除此行的其他数字
  4. 如果该数字大于要查找的数字,则可以排行此列及后面的每一列
  5. 重复第一步,直到把所有的行和列都排除掉

代码实现: Swift
这里的实现是对 Swift 里的 Array 添加了 contains 函数,这个 contains 函数利用泛型限制 Array 里的元素的类型必须是 Array,并且子 Array 里的元素必须是遵循 Comparable 协议

1
extension Array{
2
    func contains<C:Comparable>(element:C) -> Bool where Element == Array<C>  {
3
        let count = self.count
4
        var line = 0
5
        var maxRow:Int?
6
        while line < count && (maxRow == nil || maxRow! >= 0){
7
            let row = maxRow != nil ? Swift.min(maxRow!, self[line].count - 1) : (self[line].count - 1)
8
            let currentElement = self[line][row]
9
            if currentElement == element {
10
                return true
11
            }else if currentElement < element {
12
                line += 1
13
            }else {
14
                if maxRow != nil {
15
                    maxRow! -= 1
16
                }else {
17
                    maxRow = row - 1
18
                }
19
            }
20
        }
21
        return false
22
    }
23
}
24
<!--测试-->
25
let array = [[1,2,8,9,10],[2,4,9,12],[4,7,10,10,13],[6,8,11,15,18,20]]
26
for number in 0..<22 {
27
    print("数组中\(array.contains(element: number) ? "包含" : "不包含")\(number)")
28
}

测试结果
针对 [[1,2,8,9,10],[2,4,9,12],[4,7,10,10,13],[6,8,11,15,18,20]]这个数组,测试了从最小值-1 到最大值+1(0-21)之间的所有数据,显示结果是正确的

为什么说 KVO 是基于 KVC 实现的

在阅读本篇文章之前,需要你了解 KVC 和 KVO 的基本实现,如不清楚,可先阅读下面两篇文档KVC 的基本使用KVO 的基本使用

在学习 KVO 的时候,看很多文章都说 KVO 是基于 KVC 实现的

KVO 实现原理(只针对普通对象,不包括集合)

  1. 当某个类的属性被观察时,系统会在运行时动态的创建一个该类的子类。并且把改对象的 isa 指向这个子类

  2. 假设被观察的属性名是name,若父类里有setName:或这_setName:,那么在子类里重写这 2 个方法,若 2 个方法同时存在,则只会重写setName:一个(这里和 KVCset 时的搜索顺序是一样的)

  3. 若被观察的类型是 NSString,那么重写的方法的实现会指向_NSSetObjectValueAndNotify这个函数,这个函数里会调用willChangeValueForKey:didChangevlueForKey:,并且会在这 2 个方法调用之间,调用父类 set 方法的实现

  4. 系统会在willChangeValueForKey:对 observe 里的 change[old]赋值,取值是用valueForKey:取值的,didChangevlueForKey:对 observe 里的 change[new]赋值,然后调用 observe 的这个方法- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;

  5. 当使用 KVC 赋值的时候,在 NSObject 里的setValue:forKey:方法里,若父类不存在setName:或这_setName:这些方法,会调用_NSSetValueAndNotifyForKeyInIvar这个函数,这个函数里同样也会调用willChangeValueForKey:didChangevlueForKey:,若存在则调用

为什么说 KVO 基于 KVC 实现的

  1. 当使用 KVO 观察某个类属性时,会为该类创建一个子类,子类重写 setter 方法时,跟 KVCset 时的搜索顺序是一样的,都是先搜索 set,然后在搜_set。对于 KVC,若不存在会有后续操作,参考上面的文档

  2. 在为observe的 change 字典里的 old 和 new 赋值时,用到了 KVC 的valueForKey:

  3. 也许是苹果在 KVO 文档里的这句话。为了理解 KVO,你首先要理解 KVC

    Important: In order to understand key-value observing, you must first understand key-value coding.

参考

  1. https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/BasicPrinciples.html
  2. https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html

扫描线种子填充算法基本步骤:

  1. 初始化一个空栈用于存放种子点,将种子点(x,y)入栈
  2. 判断栈是否为空,如果栈为空则算法结束,否则取出栈顶元素作为当前扫描线的种子点(x,y),y 是当前的扫描线
  3. 从种子点(x,y)出发,沿当前扫描线向左向右两个方向填充,直到边界。分别标记区段的左右端点为 xLeft,xRight
  4. 分别检查与当前扫描线相邻的 y-1 和 y+1 两条扫描线在区间[xLeft,xRight]中的像素,从 xLeft 开始 xRight 方向搜索,若存在非边界且未填充的像素点,则找出这些相邻像素点中最右边的一个,并将其作为种子点入栈,然后返回第 2 步(注:一条扫描线上可能存在多个种子点)

涂鸦效果

效果图.gif

iOS 中如何实现扫描线种子填充算法

  1. 扫描的是什么东东?

    扫描的是图片上所有的像素点的集合,而常用的 png,jpg 是压缩过的位图,所以首先要把 png,jpg 图片进行解压缩

  2. 在 iOS 中如何把 UIImage 转成像素点的集合?

    主要利用 CGContext 的下面三个 API

    1
    //初始化 CGContext
    2
    public init?(data: UnsafeMutableRawPointer?, width: Int, height: Int, bitsPerComponent: Int, bytesPerRow: Int, space: CGColorSpace, bitmapInfo: UInt32)
    3
    //将位图也就是像素点的集合绘制到上下文中
    4
    public func draw(_ image: CGImage, in rect: CGRect)
    5
    //得到上下文中的位图
    6
    public func makeImage() -> CGImage?

    主要解释一下第一个方法的各个参数的含义
    data:存放像素点的指针
    width,height:位图的宽高
    bitsPerComponent:颜色空间中每个通道占用的 bit;(注,此单位是 bit)
    bytesPerRow:位图的每一行使用的字节数(注,此单位是 byte,1byte=8bit)大小等于 width*height*每个像素占用的大小,在 iOS 里颜色空间是 RGB 时,每个像素占用的大小是 32
    space:像素点的颜色空间
    bitmapInfo:位图的布局信息,主要包含了 alpha 的信息;颜色分量是否为浮点数;像素格式的字节顺序

    1
    let image = UIImage(named: "test")
    2
    if let imageRef = image?.cgImage  {
    3
            let width = imageRef.width
    4
            let height = imageRef.height
    5
            var pixels = Array<UInt32>(repeating: 0, count: width * height)
    6
            let colorSpace = CGColorSpaceCreateDeviceRGB() //像素点的颜色空间
    7
            let bitsPerComponent = 8 //颜色空间每个通道占用的bit
    8
            let bytesPerRow = width * 4 //位图的每一行使用的字节数
    9
            let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
    10
            if let context = CGContext(data: &(pixels), width: width, height: height, bitsPerComponent: bitsPerComponent,
    11
                                       bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) {
    12
                context.draw(imageRef, in: CGRect(x: 0, y: 0, width: width, height: height))
    13
            }
    14
    }
  3. 如何把触摸在 imageView 上的坐标转换为 UIImage 上的种子点

    由于 UIImageView 的大小也 UIImage 得大小是不一样的,所以当我们获取手势在 ImageView 上的坐标的时候,要经过变换得到 UIImage 上的坐标,把此点作为种子点入栈

1
//注,self是UIImageView的子类
2
 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
3
        if touches.count == 1 , let touch = touches.first , let imageRef = self.image?.cgImage{
4
            let point = touch.location(in: self)
5
            let width = imageRef.width
6
            let height = imageRef.height
7
            let widthScale = CGFloat(width) / bounds.width
8
            let heightScale = CGFloat(height) / bounds.height
9
            //把相对于view的touch point 转换成image的像素点的坐标点
10
            let realPoint = CGPoint(x: point.x * widthScale, y: point.y * heightScale)
11
        }
12
    }
  1. 实现扫描线种子填充算法

    核心代码如下,以下代码是写在自定义的 UIImageView 的子类中
1
 //MARK: private method
2
    /// 填充颜色
3
    ///
4
    /// - Parameters:
5
    ///   - point: 种子点
6
    ///   - color: 填充颜色
7
    private func floodFill(from point:CGPoint) {
8
        if let imageRef = image?.cgImage  {
9
            let width = imageRef.width
10
            let height = imageRef.height
11
            let widthScale = CGFloat(width) / bounds.width
12
            let heightScale = CGFloat(height) / bounds.height
13
            //把相对于view的touch point 转换成image的像素点的坐标点
14
            let realPoint = CGPoint(x: point.x * widthScale, y: point.y * heightScale)
15
            scanedLines = [:]
16
            imageSize = CGSize(width: width, height: height)
17
            pixels = Array<UInt32>(repeating: 0, count: width * height)
18
            let colorSpace = CGColorSpaceCreateDeviceRGB() //像素点的颜色空间
19
            let bitsPerComponent = 8 //颜色空间每个通道占用的bit
20
            let bytesPerRow = width * 4 //image每一行所占用的byte
21
            let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
22
            if let context = CGContext(data: &(pixels), width: width, height: height, bitsPerComponent: bitsPerComponent,
23
                                       bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) {
24
                context.draw(imageRef, in: CGRect(x: 0, y: 0, width: width, height: height))
25
                let pixelIndex = lrintf(Float(realPoint.y)) * width + lrintf(Float(realPoint.x))
26
                let newColorRgbaValue = newColor.rgbaValue
27
                let colorRgbaValue = pixels[pixelIndex]
28
                //如果点击的黑色边框,直接退出
29
                if isBlackColor(color: colorRgbaValue) {
30
                    return
31
                }
32
                //如果点击的颜色和新颜色一样,退出
33
                if compareColor(color: colorRgbaValue, otherColor: newColorRgbaValue, tolorance: colorTolorance) {
34
                    return
35
                }
36
                //存放种子点的栈
37
                seedPointList.push(realPoint)
38
                while !seedPointList.isEmpty {
39
                    if let point = seedPointList.pop() {
40
                        let (xLeft,xRight) = fillLine(seedPoint: point, newColorRgbaValue: newColorRgbaValue,
41
                                                      originalColorRgbaValue: colorRgbaValue)
42
                        scanLine(lineNumer: Int(point.y) + 1, xLeft: xLeft, xRight: xRight, originalColorRgbaValue: colorRgbaValue)
43
                        scanLine(lineNumer: Int(point.y) - 1, xLeft: xLeft, xRight: xRight, originalColorRgbaValue: colorRgbaValue)
44
                    }
45
                }
46
                if let cgImage = context.makeImage() {
47
                    image = UIImage(cgImage: cgImage, scale: image?.scale ?? 2, orientation: .up)
48
                }
49
            }
50
        }
51
    }
52
53
    /// 通过种子点向左向右填充
54
    ///
55
    /// - Parameters:
56
    ///   - seedPoint: 种子点
57
    ///   - newColorRgbaValue: 填充的新颜色的值
58
    ///   - originalColorRgbaValue: 触摸点颜色的值
59
    /// - Returns: 种子点填充的左右区间 都是闭区间
60
   private  func fillLine(seedPoint:CGPoint,newColorRgbaValue:UInt32,originalColorRgbaValue:UInt32) -> (Int,Int) {
61
        let imageW = Int(imageSize.width)
62
        let currntLineMinIndex = Int(seedPoint.y) * imageW
63
        let currntLineMaxIndex = currntLineMinIndex + imageW
64
        let currentPixelIndex = currntLineMinIndex + Int(seedPoint.x)
65
        var xleft = Int(seedPoint.x)
66
        var xright = xleft
67
        if pixels.count >= currntLineMaxIndex {
68
            var tmpIndex = currentPixelIndex
69
            while tmpIndex >=  currntLineMinIndex &&
70
                  compareColor(color: originalColorRgbaValue, otherColor: pixels[tmpIndex], tolorance: colorTolorance){
71
                pixels[tmpIndex] = newColorRgbaValue
72
                tmpIndex -= 1
73
                xleft -= 1
74
            }
75
            tmpIndex = currentPixelIndex + 1
76
            while tmpIndex < currntLineMaxIndex &&
77
                  compareColor(color: originalColorRgbaValue, otherColor: pixels[tmpIndex], tolorance: colorTolorance){
78
                pixels[tmpIndex] = newColorRgbaValue
79
                tmpIndex += 1
80
                xright += 1
81
            }
82
        }
83
        return (xleft + 1,xright)
84
    }
85
86
87
    /// 从xLeft到xRight的扫描第lineNumer行
88
    ///
89
    /// - Parameters:
90
    ///   - lineNumer: 行数
91
    ///   - xLeft: 扫描线的最左侧
92
    ///   - xRight: 扫描线的最右侧
93
    ///   - originalColorRgbaValue:  触摸点颜色的值
94
   private func scanLine(lineNumer:Int,xLeft:Int,xRight:Int,originalColorRgbaValue:UInt32) {
95
        if lineNumer < 0 || CGFloat(lineNumer) > imageSize.height - 1{
96
            return
97
        }
98
        var xCurrent = xLeft //当前被扫描的点的x位置
99
        let currentLineOriginalIndex = lineNumer * Int(imageSize.width)
100
        var currentPixelIndex = currentLineOriginalIndex + xLeft //当前被扫描的点的所在像素点的位置
101
        var currntLineMaxIndex = currentLineOriginalIndex + xRight //当前扫描线需要扫描的最后一个点的位置
102
        //此处是对种子扫描线算法的一点小优化
103
        var leftSpiltIndex:Int?
104
        if var scanLine = scanedLines[lineNumer] {
105
            if scanLine.xLeft >= xRight || scanLine.xRight <= xLeft {//没有相交,什么也不做
106
            }else if scanLine.xLeft <= xLeft && scanLine.xRight >= xRight { //旧扫描与新扫描的范围关系是包含
107
                return
108
            }else if scanLine.xLeft <= xLeft && scanLine.xRight <= xRight {//旧扫描与新扫描的范围关系是左包含右被包含
109
                xCurrent = scanLine.xRight + 1
110
                currentPixelIndex = currentLineOriginalIndex + scanLine.xRight + 1
111
                scanLine.xRight = xRight
112
                scanedLines[lineNumer] = scanLine
113
            }else if scanLine.xLeft >= xLeft && scanLine.xRight >= xRight {//旧扫描与新扫描的范围关系是左被包含右包含
114
                currntLineMaxIndex = currentLineOriginalIndex + scanLine.xLeft - 1
115
                leftSpiltIndex = currentLineOriginalIndex + scanLine.xLeft
116
                scanLine.xLeft = xLeft
117
                scanedLines[lineNumer] = scanLine
118
            }else if scanLine.xLeft >= xLeft && scanLine.xRight <= xRight {//旧扫描与新扫描的范围关系是被包含
119
                scanLine.xLeft = xLeft
120
                scanLine.xRight = xRight
121
                scanedLines[lineNumer] = scanLine
122
            }
123
        }else {
124
            scanedLines[lineNumer] = FillLineInfo(lineNumber: lineNumer, xLeft: xLeft, xRight: xRight)
125
        }
126
        while currentPixelIndex <= currntLineMaxIndex {
127
            var isFindSeed = false
128
            //找到此区间的种子点,种子点是存在非边界且未填充的像素点,这些相邻的像素点中最右边的一个
129
            while currentPixelIndex < currntLineMaxIndex &&
130
                  compareColor(color: originalColorRgbaValue, otherColor: pixels[currentPixelIndex], tolorance: colorTolorance) {
131
                isFindSeed = true
132
                currentPixelIndex += 1
133
                xCurrent += 1
134
            }
135
136
            if isFindSeed {
137
                //如果找到种子点,需要判断while循环的退出条件是什么
138
                //如果是到区间最右边的倒数第二个点,则需要判断最右边的点是否和originalColorRgbaValue颜色一样,如果一样,则最右边的入栈,否则把上一个点入栈
139
                //如果是碰到了边界点退出的,则把当前点的上一个点入栈
140
                if compareColor(color: originalColorRgbaValue, otherColor: pixels[currentPixelIndex], tolorance: colorTolorance) &&
141
                   currentPixelIndex == currntLineMaxIndex {
142
                    //若当旧扫描与新扫描的范围关系是左被包含右包含,需要扫描的范围应该是新扫描范围的左点到旧扫描范围的左点的上一个点
143
                    //此时若扫描范围内的最右点颜色与originalColorRgbaValue一样,并且旧扫描范围的左点的颜色也与originalColorRgbaValue一样,则不需要入栈
144
                    if leftSpiltIndex == nil ||
145
                       !compareColor(color: originalColorRgbaValue, otherColor: pixels[leftSpiltIndex!], tolorance: colorTolorance){
146
                        seedPointList.push(CGPoint(x: xCurrent, y: lineNumer))
147
                    }
148
                }else {
149
                    seedPointList.push(CGPoint(x: xCurrent - 1, y: lineNumer))
150
                }
151
            }
152
            currentPixelIndex += 1
153
            xCurrent += 1
154
        }
155
    }
156
157
   /// 判断颜色是否是黑色
158
   ///
159
   /// - Returns: true 是 or false 不是
160
   private func isBlackColor(color:UInt32) -> Bool {
161
        let colorRed = Int((color >> 0) & 0xff)
162
        let colorGreen = Int((color >> 8) & 0xff)
163
        let colorBlue = Int((color >> 16) & 0xff)
164
        let colorAlpha = Int((color >> 24) & 0xff)
165
166
        if colorRed < colorTolorance &&
167
            colorGreen < colorTolorance &&
168
            colorBlue < colorTolorance &&
169
            colorAlpha > 255 - colorTolorance{
170
            return true
171
        }
172
        return false
173
    }
174
175
    /// 是否是相似的颜色
176
    ///
177
    /// - Returns: true 相似 or false 不相似
178
    private func compareColor(color:UInt32, otherColor:UInt32, tolorance:Int) -> Bool {
179
        if color == otherColor {
180
            return true
181
        }
182
        let colorRed = Int((color >> 0) & 0xff)
183
        let colorGreen = Int((color >> 8) & 0x00ff)
184
        let colorBlue = Int((color >> 16) & 0xff)
185
        let colorAlpha = Int((color >> 24) & 0xff)
186
187
        let otherColorRed = Int((otherColor >> 0) & 0xff)
188
        let otherColorGreen = Int((otherColor >> 8) & 0xff)
189
        let otherColorBlue = Int((otherColor >> 16) & 0xff)
190
        let otherColorAlpha = Int((otherColor >> 24) & 0xff)
191
192
        if abs(colorRed - otherColorRed) > tolorance ||
193
           abs(colorGreen - otherColorGreen) > tolorance   ||
194
           abs(colorBlue - otherColorBlue) > tolorance ||
195
           abs(colorAlpha - otherColorAlpha) > tolorance {
196
            return false
197
        }
198
        return true
199
    }
200
    extension UIColor {
201
    /// 获取颜色的UInt32表示形式
202
    fileprivate var rgbaValue:UInt32 {
203
        var red:CGFloat = 0
204
        var green:CGFloat = 0
205
        var blue:CGFloat = 0
206
        var alpha:CGFloat = 0
207
        getRed(&red, green: &green, blue: &blue, alpha: &alpha)
208
        return UInt32(red * 255) << 0 | UInt32(green * 255) << 8 | UInt32(blue * 255) << 16 | UInt32(alpha * 255) << 24
209
    }
210
}
  1. 做此功能的一些其他收获

    • UIScrollView 很容易实现视图的缩放功能,只要在代理方法中返回需要缩放的视图即可,UIScrollView 是如何实现子视图的缩放的?

      UIScrollView 是通过改变子视图的 transform 来实现缩放的

    • 当使用 transform 把视图缩放后,frame 和 bounds 会如何变化,该视图的子视图的 frame 和 bounds 又会如何变化

      frame 会同比缩放,而 bounds 不会变化,子视图的 frame 和 bounds 都不变
      原因猜测(纯属猜测)如下:frame.size 代表的是视图的大小,这个大小是逻辑大小,而不是真正的像素大小。而 bounds 也是逻辑大小。以 iphone6 举例,在无缩放的情况下,frame.size.width = 1 代表着 2 个像素点,在缩放的过程中。当前缩放的视图的 frame.size 每个逻辑大小对应的像素点不变,而 bounds.size 每个逻辑大小对应的像素点则同比缩放,对于子视图来说,frame.size 每个逻辑大小对应的像素点等于父视图的 bounds.size 每个逻辑大小对应的像素点,bounds.size 每个逻辑大小对应的像素点则等于父视图的 bounds.size 每个逻辑大小对应的像素点和自身的缩放的乘积

    • 当使用 transform 把视图放大之后,触摸点 point 的范围是否会放大,也就是说如果放大之前视图的大小为 375*373,point 的范围为(0,0)-(375,375),那么放大 2 倍后,point 的范围是(0,0)-(375,375)还是(0,0)-(750,750)

      范围还是(0,0)-(375,375),原因猜测如下:手势获取坐标的时候是基于视图的 bounds 的

  2. demo 的 GitHub 地址

  3. 参考文章

第一题

1
NSInteger age = 20;
2
    void(^block)() = ^(){
3
        NSLog(@"%li",age);//打印的值为几
4
    };
5
    age = 40;
6
    block();

第二题

1
SCPerson *person = [[SCPerson alloc]init];
2
    person.age = 20;
3
    void(^block)() = ^(){
4
        NSLog(@"%li",person.age);//打印的值为几
5
    };
6
    person.age = 40;
7
    block();

第三题

1
SCPerson *person = [[SCPerson alloc]init];
2
    person.age = 20;
3
    void(^block)() = ^(){
4
        NSLog(@"%li",person.age);//打印的值为几
5
    };
6
    person = [[SCPerson alloc]init];
7
    person.age = 40;
8
    block();

第四题

1
    SCPerson *person = [[SCPerson alloc]init];
2
    person.age = 20;
3
    __weak SCPerson *weakPerson = person;
4
    void(^block)() = ^(){
5
        NSLog(@"%li",weakPerson.age);//打印的值为几
6
    };
7
    person = [[SCPerson alloc]init];
8
    person.age = 40;
9
    block();

第五题

1
SCPerson *person = [[SCPerson alloc]init];
2
    person.age = 20;
3
    __weak SCPerson *weakPerson = person;
4
    void(^block)() = ^(){
5
        __strong SCPerson *strongPerson = weakPerson;
6
        NSLog(@"%li",strongPerson.age);//打印的值为几
7
    };
8
    person = [[SCPerson alloc]init];
9
    person.age = 40;
10
    block();

在项目中我们有时会遇到让我们改变 placeholder 的颜色,简单介绍一下改变 placeholder 的颜色的几种方法

  1. 利用属性字符串,UITextField 有这样一个属性 attributedPlaceholder,可以让我们定制 placeholder 的属性
1
let placeHolderTextAttr = NSMutableAttributedString(string: "请输入您的名字");
2
placeHolderTextAttr.addAttributes([NSForegroundColorAttributeName:UIColor.redColor()], range: NSMakeRange(0, placeHolderTextAttr.length))
3
self.textField.attributedPlaceholder = placeHolderTextAttr;
  1. 如果用 xib 或 SB 的话,可以在 xib 中设置 user Defined Runtime Attributes
    keyPath 设置成_placeholderLabel.textColor type 设置成 Color,需要注意的是,设置之前一定要先选中 textField
    屏幕快照 2016-09-09 07.35.37.png

  2. 用 extension 和 KVC 去做,由于以上 2 种写法只针对某一个 textField,如果有多个 textField 需要设置的话,显得比较麻烦,用 extension 的话就简单多了

1
extension UITextField{
2
    @IBInspectable var placeHolderColor: UIColor? {
3
        get {
4
            return self.placeHolderColor
5
        }
6
        set {
7
            if let placeHolderLabel = self.valueForKey("placeholderLabel") as? UILabel {
8
                placeHolderLabel.textColor = newValue!;
9
            } ;
10
        }
11
    }
12
}
  1. 在 OC 中 extension 是不能添加实现的这时我们可以用分类去实现这个功能
1
@interface UITextField (SCPlaceHolder)
2
@property (nonatomic,strong)IBInspectable UIColor *sc_placeHolderColor;
3
@end
4
@implementation UITextField (SCPlaceHolder)
5
- (void)setSc_placeHolderColor:(UIColor *)sc_placeHolderColor {
6
    ((UILabel *)[self valueForKey:@"placeholderLabel"]).textColor = sc_placeHolderColor;
7
}
8
- (UIColor *)sc_placeHolderColor{
9
    return ((UILabel *)[self valueForKey:@"placeholderLabel"]).textColor;
10
}
11
@end

我们都知道在 swift 中很容易就能为一个类添加一个类属性,就像这样public static var name:String?,可在 OC 中我们应该怎么为一个类添加类属性呢?
我们可以在属性的描述词里添加一个 class 的描述,如下所示

1
@interface SPCustomType : NSObject
2
//在这添加了一个class,代表着这是一个类属性
3
@property (nonatomic,copy,class)NSString *name;
4
@property (nonatomic,assign,class)NSInteger count;
5
@end

但是值得注意的是,Xcode 并不会为我们自动生成一个对应的类变量,也就是说@property (nonatomic,copy,class)NSString *name;相当于写成这样+ (NSString *)name;+ (void)setName:(NSString *)name;,有一个+号的 getter 和一个+号 setter 方法,所以我们需要去实现这 2 个类方法。

1
@implementation SPCustomType
2
static NSString *_name;
3
static NSInteger _count;
4
+ (NSString *)name{
5
    return _name;
6
}
7
+ (void)setName:(NSString *)name{
8
    _name = name;
9
}
10
+ (NSInteger)count{
11
    return _count;
12
}
13
+ (void)setCount:(NSInteger)count{
14
    _count = count;
15
}
16
@end

接下来我们就可以用类属性了。例如

1
SPCustomType.count++;
2
NSLog(@"%ld",(long)SPCustomType.count);//打印结果是1
3
SPCustomType.name = @"testName";
4
NSLog(@"%@",SPCustomType.name);//打印结果是testName

有的人会有疑问,既然加上class这个描述词之后,仅仅相当于生成了+号 getter 和 setter 方法的声明,那为什么我们能通过点语法进行调用呢?
其实点语法并不是去访问成员变量,而是方法的调用。ClassA.test相当于[ClassA test]ClassA.test=@"test"相当于[ClassA setTest:"test"]ClassA 既可以是一个对象也可以是一个类.简单测试如下

1
//声明
2
@interface SPDotTestClass : NSObject
3
+ (void)sayHello;
4
+ (void)setName:(NSString *)name;
5
@end
6
//实现
7
@implementation SPDotTestClass
8
+ (void)sayHello{
9
    NSLog(@"sayHello");
10
}
11
+ (void)setName:(NSString *)name{
12
    NSLog(@"hello %@",name);
13
}
14
@end
15
//测试代码
16
SPDotTestClass.sayHello;//此处打印sayHello
17
SPDotTestClass.name = @"lkk";//此处打印hello lkk

还有更简单的证明,就是我们经常用的[UIApplication sharedApplication];也可以写成UIApplication.sharedApplication;当然不建议这么去写。
在回到上面为 OC 添加类属性上,并不是真正的为一个类添加了类属性,只是看起来好像有了个类属性。添加类属性这个功能应该是为了更好的兼容 Swift

相信做过 iOS 开发的人,对 UIButton 都不会陌生,只要用过 UIButton,对这个方法都不会陌生- (void)addTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;但是这个方法究竟做了什么呢?

在开始本篇文章之前,有这样几个疑问?

  1. 当我们点击按钮的时候,到响应事件这中间是怎么的一个过程?
  2. UIButton 是否会持有 target?
  3. target 是否可以为 nil,若可以为 nil,系统是怎么处理的?
  4. action 是否可以 nil?

###当我们点击按钮的时候,到响应事件这中间是怎么的一个过程?
既然我们点击的时候,它能够调用我们的方法,我猜想它应该把 target,action,event 这些东西存储起来,以便我们点击的时候调用。于是我遍历了 UIButton 及它父类 UIControl 的实例变量及属性。在 UIControl 中发现了这个实例变量targetActions他的类型是NSMutableArray,targetActions里面放的是UIControlTargetAction.这个类是一个 model 类,只有简单的四个属性_target类型 id,_action类型 SEL,_eventMask类型 unsigned long long,_cancelled类型 bool。很明显_target,_action,_eventMask 就是我们传过去的,target,action,controlEventt.

这样我们应该就能猜测到。他的实现了,当我们调用- (void)addTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;,UIButton 会根据我们传过去的信息来生成一个UIControlTargetAction对象,并把这个对象放到数组中去,当我们触摸到按钮的时候,根据 event 去遍历这个数组,如果 event 跟数组里 model 的 eventMask 相同,则[UIControlTargetAction.target performSelector:UIControlTargetAction.action withObject:self]当然了,这里少不了一系列的判断。
###UIButton 是否会持有 target?
根据文档所说,button 不会持有 target,所说文档不会骗我们,但是我还是测试了一下,测试代码如下

1
Person *person = [[Person alloc]init];
2
[btn addTarget:person action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];
3
//Person类实现如下
4
#import "Person.h"
5
@implementation Person
6
- (void)btnAction{
7
    NSLog(@"btnAction");
8
}
9
- (void)dealloc{
10
    NSLog(@"%@----dealloc",[self class]);
11
}
12
@end
13
输出:Person----dealloc

如果btn持有target的话person,就不会走persondealloc方法 ###如果有相同 event 添加了多次会怎样

1
[btn addTarget:self action:@selector(firstAction) forControlEvents:UIControlEventTouchUpInside];
2
[btn addTarget:self action:@selector(secondAction) forControlEvents:UIControlEventTouchUpInside];

我为 btn 的 TouchUpInside 事件添加了 2 个分别不同的 action,他会按照添加 action 的顺序依次调用。上面的代码,当我点击 btn 的时候,他会先调用 firstAction,再调用 secondAction.由于它是找到一个调用一个,若你在 firstAction 方法里把 secondAction 对应的 UIControlTargetAction 对象的_eventMask 的值改了,则会导致 secondAction 调用不了。
###target 是否可以为 nil
答案是肯定的,可以为 nil,若果 target 为 nil 并不是向 nil 发一个消息,而是根据响应者链往上找,若找到,则调用,否则什么也不做。

1
[btn addTarget:nil action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];

也就是说,上面的这个代码,他会先检查 btn 这个类有没有实现 btnAction 方法,若实现就调用,否则找 btn.nextResponder,再次检查是否实现,一直到 AppDelegate,如果他还没实现,则就什么也不做。

action 是否可以为 NULL

文档上说 action 不能为 NULL,但是我测试发现 action 为 NULL 的时候,程序在运行的时候并不会报错。不知道是 Apple 添加了判断而没有更新文档,还是我的测试方法有问题,测试的代码如下

1
SEL btnAction = NULL;
2
[btn addTarget:self action:btnAction forControlEvents:UIControlEventTouchUpInside];

当我点击 btn 的时候什么反应也没有。这个不知道为啥会有文档上的描述不一致。

一、WKWebView 的基本使用

  1. 初始化方法
    - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
  2. 简单介绍一下 configuration,WKWebViewConfiguration 里面有个 userContentController。可通过它为 webview 注入 javaScript 代码。并且可以添加监听 javaScript 的回调。
    (1)初始化 configurationWKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc]init];
    (2)获取 javaScript 代码,我把 javaScript 代码写在了一个文件中NSString *jsStr = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"WKWebViewJS" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil];
    (3)初始化 script 对象
1
/*@abstract 初始script对象
2
     @param source javaScript代码
3
     @param injectionTime 注入javaScript代码的时机
4
     @param forMainFrameOnly 是不是仅为MainFrame注入
5
     */
6
    WKUserScript *userScript = [[WKUserScript alloc]initWithSource:jsStr injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];

(4)添加 script 对象[configuration.userContentController addUserScript:userScript];
(5)添加 scriptMessage 回调
[configuration.userContentController addScriptMessageHandler:self name:@"imageClick"]; [configuration.userContentController addScriptMessageHandler:self name:@"popToPreviousVC"];
若添加过回调之后,在结束调用这个方法window.webkit.messageHandlers.<handleName>.postMessage(<messageBody>) 系统会调用这个方法- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message

  1. WKWebView 执行 javaScript 代码
    self.webview evaluateJavaScript:@"changeFontSize(12)" completionHandler:^(id _Nullable result, NSError * _Nullable error) { }];
  2. WKWebView 的一些简单属性的介绍
1
backForwardList            浏览历史
2
title                      网页标题             支持KVO
3
URL                        正在显示的URL        支持KVO
4
loading                    是否正在加载          支持KVO
5
estimatedProgress          加载进度             支持KVO
6
canGoBack  canGoForward    能否后退 前进         支持KVO
7
reload                     重新加载
8
stopLoading                停止加载
9
allowsBackForwardNavigationGestures  是否允许侧滑返回上一页

5、WKWebView 的加载方法跟 UIWebView 基本一样

1
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
2
- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
3
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL;

#二 WKWebViewUIDelegate

1
//当需要打开一个新窗口的时候的调用,如a标签的target='_blank',需要返回一个新的Webview
2
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
3
以下三个类似alert的代理,一定要调用completionHandler(),这个回调,告诉webview结果
4
//webview上需要弹出alert的时候调用此方法,如果不实现此方法,则webview的alert是显示不出来,alert类似于只有确定按钮的UIAlertView
5
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
6
//webview上需要弹出confirm的时候调用此方法,如果不实现此方法,则webview的confirm是显示不出来,confirm类似于有确定按钮和取消按钮的UIAlertView
7
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler
8
 webview上需要弹出prompt的时候调用此方法,如果不实现此方法,则webview的prompt是显示不出来,prompt类似于带一个textField的UIAlertView。defaultText相当于textField的placeholed
9
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler
10
//window.close() 的时候调用
11
- (void)webViewDidClose:(WKWebView *)webView

#三 WKWebViewNavigationDelegate

1
//决定是否允许发起这个请求
2
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
3
//在webview有响应之后,再次决定是否允许这个请求
4
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandle
5
//webview开始加载的时候调用
6
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation
7
//webview内容已经加载结束,但是上面的某些资源比如图片加载之前调用
8
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation
9
//webview加载结束的时候调用
10
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation
11
//webview加载失败的时候调用
12
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error
13
//webview在commit过程中失败的时候调用,例如在didCommitNavigation这个代理方法中调用webview的stopLoading方法
14
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error

#四 WKScriptMessageHandler

1
//收到JavaScript回调的时候调用
2
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message

#五 接下来用一下做个小 demo。
功能如下:
1、点击按钮可以控制 webView 上的字体的大小和颜色
2、点击 webView 上的返回,能 pop 到上一个 VC
3、点击 webView 图片,弹出 alert 告诉我弹出的是第几张图片,和图片的大小和位置以及所有的图片的地址
4、验证代理方法
效果如下:

效果图.gif

(1)首先为 webview 注入 JS 代码,JS 代码里主要实现的是:当 window 加载之后,为 img 标签添加 click 事件,定义改变字体颜色和大小的函数

1
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc]init];
2
   NSString *jsStr = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"WKWebViewJS" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil];
3
   WKUserScript *userScript = [[WKUserScript alloc]initWithSource:jsStr injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
4
   [configuration.userContentController addUserScript:userScript];
5
   [configuration.userContentController addScriptMessageHandler:self name:@"imageClick"];
6
   [configuration.userContentController addScriptMessageHandler:self name:@"popToPreviousVC"];

(2)调用 JS 代码,实现改变字体颜色和大小

1
- (void)runJavaAcript:(UIButton *)button{
2
    if (button.tag == 0) {
3
        [self.webview evaluateJavaScript:@"changeTextColor('#f00')" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
4
        }];
5
    }else if (button.tag == 1){
6
        [self.webview evaluateJavaScript:@"changeTextColor('#666')" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
7
        }];
8
    }else if (button.tag == 2){
9
        [self.webview evaluateJavaScript:@"changeFontSize(38)" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
10
        }];
11
    }else if (button.tag == 3){
12
        [self.webview evaluateJavaScript:@"changeFontSize(12)" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
13
        }];
14
    }
15
}

(3)点击 webview 上的返回按钮 pop 到上一个 VC
1)在 html 中的定义

1
//返回标签的定义
2
<button onclick="popToPreviousVC()">返回</button>
3
//函数实现
4
function popToPreviousVC(){
5
    webkit.messageHandlers.popToPreviousVC.postMessage(true);
6
}

2)webview 收到回调的处理

1
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
2
    if ([message.name isEqualToString:@"imageClick"]) {
3
        NSInteger current = [message.body[@"current"] integerValue];
4
        UIAlertView *alert = [[UIAlertView alloc]initWithTitle:[NSString stringWithFormat:@"你点击了第%ld张图片,该图片的坐标是%@",(long)current,message.body[@"rect"]] message:[NSString stringWithFormat:@"图片的地址分别是:%@",message.body[@"srcs"]] delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
5
        [alert show];
6
    }else if([message.name isEqualToString:@"popToPreviousVC"]){
7
        [self.navigationController popViewControllerAnimated:YES];
8
    }
9
}

要想接收到回调,一定要先在先添加回调,而且添加的名字一定要和发送的名字一致
[configuration.userContentController addScriptMessageHandler:self name:@"popToPreviousVC"];

(4)点击 webView 图片,弹出 alert 告诉我弹出的是第几张图片,和图片的大小和位置以及所有的图片的地址,回调的信息如上,下面展示 js 代码

1
function initImageClick(obj,index) {
2
    obj.onclick = function imgOnclick() {
3
        //获取image的位置
4
        var rect = obj.getBoundingClientRect();
5
        //获取所有的img标签
6
        var imgs = document.getElementsByTagName('img');
7
        var srcs = new Array();
8
        for(var i in imgs){
9
            //判断img的src是否为未定义,加入srcs数组
10
            if(imgs[i].src != undefined)
11
            srcs.push(imgs[i].src);
12
        }
13
        //由于OC不识别rect这个对象,在这把rect转成了符合CGRect格式的字符串
14
        var message = {'rect':'{{'+rect.left+', '+rect.top+'}, {'+rect.width+', '+rect.height+'}}}'};
15
        //为message这个js对象的属性赋值
16
        message.current = index;
17
        message.srcs = srcs;
18
        //发送消息
19
        webkit.messageHandlers.imageClick.postMessage(message);
20
    }
21
}

代理方法的验证具体看demo