
作者:shanks & pmst
本周整理问题如下:
Question on extracting a substring from a url
All func pointers need a protocol now
Property for Double and CGFloat
Question on dismissing model ViewController
count numbers in array and order them by count in swift
Cannot invoke [Method] with argument list of type (,)
Swift programming style
对应的代码都放到了 github 上,有兴趣的同学可以下载下来研究:点击下载
Question1: Question on extracting a substring from a urlQ1链接地址
问题描述这个问题在实战中经常被提及,譬如现在有一个字符串URL:https://api.github.com/gists/public?page=2 ,我需要从中提取出“?page=2”以及“https://api.github.com/gists/public”,将这两个部分存储到两个变量中,如下:
var strBase = "https://API.github.com/gists/public"var strPage = "?page=2"
问题解答前了解下小知识:
http GET请求中,url 与请求参数之间用?分隔,参数与参数之间是&分隔。
问题解答思路:显而易见,关注点应该放在?上,解决方式也五花八门,现提供以下几种方式:
方法一
var urlStr = "https://API.github.com/gists/public?page=2"
var strBase:String // 请求地址
var strPage:String // 请求参数
if let qIndex = urlStr.characters.indexOf("?") { //35
// 2
strBase = urlStr.substringToIndex(qIndex)
strPage = urlStr.substringFromIndex(qIndex)
} else {
// 3
strBase = urlStr
strPage = ""
}
首先定位到 ? 的 index 索引值。
请求地址中存在参数(即存在?),那么通过substringToIndex 和 substringFromIndex 获取到两个部分
请求地址中不存在参数,那么直接传入的地址就是strBase。
这里的qIndex是一个索引值,类型是String.CharacterVIEw.Index,修改该值并非是用 +、- *** 作,而是调用qIndex.successor() 取到下一位索引值,使用qIndex.predecessor()取到前一位索引值。
对于substringToIndex(qIndex)方法,从 urlStr 的 startIndex(这里是0) 开始截取直到 qIndex;对于substringFromIndex(qIndex) 方法,即从 qIndex 索引开始直到endindex前面一个索引,为什么这么说?举个例子,“Hello”字符的 startIndex 毫无疑问是等于0,但 endindex 不是等于4!!而是5!!最后一个字符的后一位!希望大家记住。
方法二
这里是使用了componentsSeparatedByString方法,通过?字符将字符串分隔符将原字符串分割成多个部分到数组中。
let parts = urlStr.componentsSeparatedByString("?")// 1strBase = parts[0]// 2strPage = parts.count >= 2 ? "?" + parts[1] : "" 显然这里只有一个 ? 字符,将原字符串分割成两个部分,第一个部分当然是 strBase 喽
通过数组的count 来判断是否存在请求参数。
方法三
这里使用了 NSURLComponents 方法:
if let urlCompo = NSURLComponents(string: urlStr) {strPage = urlCompo.query != nil ? "?" + urlCompo.query! : ""urlCompo.query = nilstrBase = urlCompo.string!} else {fatalError("invalID URL")} 举一反三,如果我还想获得请求参数中的各个参数,那应该怎么做呢?我先抛砖引玉下:
var urlStr1 = "https://API.github.com/gists/public?page=2&name=machao&pwd=222"let parts1 = urlStr1.componentsSeparatedByString("?")strBase = parts1[0]var strPara = parts1.count >= 2 ? "?" + parts[1] : ""strPara.removeAtIndex(strPara.startIndex)let Paras1 = strPara.componentsSeparatedByString("&")//就是这个! Question2: All func pointers need a protocol Now? Q2链接地址
问题描述Gargoyle 想实现一个对象之间的回调,但是却未使用 protocol + delegate 的设计模式,而是使用了如下方法:
final class MyVIEw: UIVIEw {var onSomeAction: ((String) -> VoID)!}final class MyVIEwController: UIVIEwController {let myVIEw = MyVIEw(frame: CGRectZero)overrIDe func vIEwDIDLoad() {super.vIEwDIDLoad()myVIEw.onSomeAction = someFunc}private func someFunc(str: String) {}} 注意到 myVIEwController 实例与 myVIEw 实例会形成一个 retain cycle ,倘若你想为 var onSomeAction 使用 weak 关键字,抱歉!报错“weak cannot be applIEd to non-class type xxxx” ,显然对于非 class 对象你无法使用 weak 关键字。
Jessy 提供了一种解决方案:
var onSomeAction_get: () -> (String -> VoID)! = {nil}myVIEw.onSomeAction_get = {[uNowned self] in self.someFunc} 首先对闭包类型进行了修改,从(String) -> VoID 变为 () -> (String -> VoID) ;其次为 myVIEw 的 onSomeAction_get 赋值是用闭包方式,其中使用了[uNowned self] 保证不会形成retain cycle,这也是问题解决的关键所在。
Q3链接地址
问题描述Albinus 希望判断是一个随意给定的 number 是否是整型,譬如给定一个 number 是 Double 类型,他现在是通过extension 来实现的,代码如下:
extension Double { var isInteger: Bool { return Double(self) == Double(Int(self)) }} 注意到通过 extension 扩展了一个computed property isInteger , 布尔类型,判断方式也很简单,是通过类型转换先 Int 去除小数部分(如果有的话),然后再转成 Double 类型,这样做很繁琐,但是 Swift 作为一门强类型语言,类型安全放在首位,任何时候都要先保证类型统一。
再来说说其他数字类型,譬如 CGfloat 、 float 等等,我们都可以通过 extension 依葫芦画瓢来实现,不过有木有更简单,直观的方式呢?
问题解答OOPer 给出了自己解答:
protocol IntegerCheckablefloatType: floatliteralConvertible,Equatable { func % (lhs: Self,rhs: Self) -> Self // % 可是取余运算符哦 var isInteger: Bool {get} }extension IntegerCheckablefloatType { var isInteger: Bool { return self % 1.0 == 0.0 as Self }}extension Double: IntegerCheckablefloatType {}extension CGfloat: IntegerCheckablefloatType {}extension float: IntegerCheckablefloatType {}(123.4).isInteger //->false(123.0).isInteger //->true 首先定义 IntegerCheckablefloatType 协议,它遵循(实现)了 floatliteralConvertible 和 Equatable 协议,当然协议本身还要求一个 method 以及 read-only property,分别是 func % (lhs: Self,rhs: Self) -> Self 和 isInteger。
对于 floatliteralConvertible 协议内容:
typealias floatliteralType/// Create an instance initialized to `value`.public init(floatliteral value: Self.floatliteralType)
floatliteralType 又是什么鬼?其实是public typealias floatliteralType = Double 正如你所看到其实就是 Double。 只要实现了这个协议的类型就能使用浮点类型字面量进行初识化。
Equatable 更简单,协议内容如下:
public func ==(lhs: Self,rhs: Self) -> Bool
从名称就知道这个协议用于 == 进行两个数之间的判断了,返回一个布尔类型值来标示是否相等。
现在巧妙的是我们使用了 Protocol 中的 extension 特性,为协议增加了默认行为,如下:
extension IntegerCheckablefloatType { var isInteger: Bool { return self % 1.0 == 0.0 as Self }} 根据协议的知识,一个类型遵循了某个协议,那么必须实现该协议中的所有“要求”。但是!倘若你使用了 extension 特性为协议增加了一个默认行为,那么类型遵循了该协议,本身却不实现,则会调用默认行为。就拿上文例子说吧,如果类型遵循了IntegerCheckablefloatType 协议却没有实现 var isInteger:Bool 这个只读属性,就会调用默认行为self % 1.0 == 0.0 as Self ,不过一旦你自己实现了 var isInteger:Bool这个协议内容,就会覆盖原有的默认行为。
再来看看后面部分:
extension Double: IntegerCheckablefloatType {}extension CGfloat: IntegerCheckablefloatType {}extension float: IntegerCheckablefloatType {} 我们令 Double、CGfloat和float 遵循自定义协议IntegerCheckablefloatType,你可能会问怎么不实现协议内容呢?原因是这些类型本身就已经是实现了floatliteralConvertible 以及 Equatable 协议,另外func % (lhs: Self,rhs: Self) -> Self 也是实现的。所以你只需要贴心地用extension对已有类型进行扩展,告知它们是遵循IntegerCheckablefloatType即可,想要判断是否是整型,只需要获取isInteger属性即可。
Q4链接地址
问题描述VolmDev 问了一个自己在实际开发中遇到的问题:应用需要登录 *** 作,为此当主界面出现之后,跳转到登陆界面,输入账号密码,点击 Login 按钮,关闭登陆界面,回到主界面,一切就应该结束,不是吗? 可问题偏偏出现了!当关闭登陆界面后居然又跳出了登陆界面!再关闭又打开? VolmDev 抓狂了,这尼玛什么情况!他附上了完整的代码,如下:
这是VIEwController.swift 文件源代码:
// VIEwController.swiftimport UIKitimport SafariServicesclass VIEwController: UIVIEwController,LoginVIEwDelegate {var safariVIEwController: SFSafariVIEwController?overrIDe func vIEwDIDLoad() { super.vIEwDIDLoad()}overrIDe func vIEwDIDAppear(animated: Bool) { super.vIEwDIDAppear(animated) showPopUp()}overrIDe func dIDReceiveMemoryWarning() { super.dIDReceiveMemoryWarning()}func showPopUp(){ let storyboard = UIStoryboard(name: "Main",bundle: NSBundle.mainBundle()) if let loginVC = storyboard.instantiateVIEwControllerWithIDentifIEr("LoginVIEwController") as? LoginVIEwController { loginVC.delegate = self self.presentVIEwController(loginVC,animated: true,completion: nil) }}// delegate function.func dIDTapLoginbutton() {self.dismissVIEwControllerAnimated(false,completion: nil)// if let authURL = GitHubapimanager.sharedInstance.URLToStartOAuth2Login() {// safariVIEwController = SFSafariVIEwController(URL: authURL)// safariVIEwController?.delegate = self// if let webVIEwController = safariVIEwController {// self.presentVIEwController(webVIEwController,completion: nil)// }// }}} 这是 LoginVIEwController.swift 文件源代码:
// LoginVIEwController.swift// TestingPopUpsimport UIKit// this is a delegate function that can be used// to call a funct+-*`*ion outsIDe this class to do// something on behafe of this class.protocol LoginVIEwDelegate: class { func dIDTapLoginbutton()}class LoginVIEwController: UIVIEwController {weak var delegate: LoginVIEwDelegate?@IBAction func btndismiss(sender: AnyObject) {// Note: I trIEd dismissing here too with failure//self.dismissVIEwControllerAnimated(false,completion: nil)if let delegate = self.delegate { delegate.dIDTapLoginbutton()}}overrIDe func vIEwDIDLoad() { super.vIEwDIDLoad() // Do any additional setup after loading the vIEw.}overrIDe func dIDReceiveMemoryWarning() { super.dIDReceiveMemoryWarning() // dispose of any resources that can be recreated. }} 当然,storyboard 还有些不关紧要的东西,倘若有兴趣,你可以自己来补充。
倘若不想看源代码思考下,请直接跳转到问题解答。
问题解答该例出现的问题其实很简单,但也是新手经常犯的错误。贴出逻辑错误的部分:
overrIDe func vIEwDIDAppear(animated: Bool) { super.vIEwDIDAppear(animated) showPopUp()} 当 vIEwController 的主视图出现之后,调用showPopUp方法d出登陆界面,貌似没什么错误。此时登陆界面位于主视图之上,输入账号密码之后点击按钮,通过 protocol+delegate 方法告知 vIEwController 视图控制器用户点击了登陆按钮,此时事件处理只是简单的关闭登陆视图:
func dIDTapLoginbutton() { self.dismissVIEwControllerAnimated(false,completion: nil)} 关闭视图之后,意味着 vIEwController 的视图又出现了!那么就会调用vIEwDIDAppear 方法,再次调用了showPopUp方法!问题就是这么来的。
今天的问题很基础,但是同样有我们值得学习的地方,我简单归纳下:
你需要了解 VIEwController 中几个方法的调用顺序,譬如:vIEwDIDLoad() vIEwDIDAppear() LoadVIEw()等等
Protocol + Delegate 的用法。
从 StoryBoard 中加载视图控制器。也就是 storyboard.instantiateVIEwControllerWithIDentifIEr()。
Q5链接地址
问题描述楼主提了一个算法问题:
1、已知一个数组,按照单个元素在数组中出现的次数作为重新排序的依据,个数多的排在前面
2、相同个数时候,元素值大的排前面
例如:
[1,2,3,5,5]
经过计算得到的结果是:
[5,1]问题解答
问题解答思路也比较简单,首先是要计算每个元素在数组中出现的次数,然后再做排序。
跟帖提供了 2 中解法,都是这样的思路。不过实现方式略有不同:
解法1:
扩展 SequenceType,因为数组的遍历是基于 SequenceType 协议的,所以自然会拥有扩展的方法 frequencIEs,用来计算元素在数组出现的次数。然后通过 sort 方法输出比较的结果。注意结果数组元素是元组形式。如果想变成与输入相同的结构,要做一下遍历输出。
extension SequenceType where Generator.Element : Hashable { func frequencIEs() -> [Generator.Element:Int] { var results : [Generator.Element:Int] = [:] for element in self { results[element] = (results[element] ?? 0) + 1 } return results }}let Alpha = [2,8,6,1,6]let beta = [6,1]let sorted = Alpha.frequencIEs().sort { if var arr1 = [2,6]var arr2 = [6,1]var counting = [Int: Int]()// fill counting dictionaryfor num in arr1 { if counting[num] != nil { counting[num]!++ } else { counting[num] = 1 }}// [6: 3,2: 3,8: 2,1: 1]print(counting)func order(i1: Int,i2: Int) -> Bool { let count1 = counting[i1] let count2 = counting[i2] // if counting is the same: compare which number is greater if count1 == count2 { return i1 > i2 } else { return count1 > count2 }}// [6,1]print(arr1.sort(order))print(arr2).1 > .1 { // if the frequency is higher,return true return true } else if protocol A { }protocol B: A { }class Utility { func add<T:A>(t:T.Type,param:T){ }}class Test { var util: Utility init() { util = Utility() } func addItem(data:B){ //util.add(B.self,param: data) //这里报错:expected an argument List of type '(T.Type,param: T)' }}.1 == .1 { // if the frequency is equal return class Test1 { var util: Utility init() { util = Utility() } func addItem<T: B>(data: T) { util.add(T.self,param: data) }}.0 > .0 // return value is higher } else { return false // else return false }} 解答2:
定义一个字典数组,专门来存储每个元素在数组出现的次数,然后用 sort 方法来输出结果:
struct xxx {}extension xxx { func yyy() {}}//: 可以合并成以下代码:struct xxx1 { func yyy() {}} Question6: Cannot invoke [Method] with argument List of type (,) 问题链接 Q6链接地址
问题描述楼主的问题是:
已知协议 A,B 其中协议 B 遵从 协议 A。类 Utility 定义了一个泛型方法 add,T 遵从 A 协议。
在另外一个 Test 类的方法 addItem 中,调用了这个 add 方法,但是传入的 data 类型是 B,但调用时候出错,见下面代码:
楼主把 data 的类型定义为协议 B,这不是正确的用法,应该定义一个泛型类型 T,T 遵从 B 协议,见以下代码:
Question7: Swift programming style 问题链接Q7链接地址
问题描述楼主的问题是:extension 存在的意义是啥,为什么不直接写到一个定义里面,比如:
问题解答此问题其实很容易解答,extension 存在的意义,是去扩展已有的类型,而不是新建了类型,而去用 extension 扩展新的逻辑。比如,对 Int,Double 新增功能,就需要用到 extension。跟帖中也建议楼主好好读读官方文档,理解一下extension存在的价值。
总结以上是内存溢出为你收集整理的每周 Swift 社区问答 2016-01-06全部内容,希望文章能够帮你解决每周 Swift 社区问答 2016-01-06所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)