寒哥教你學 iOS - 經(jīng)驗漫談
本篇文章主要講解 4個問題
-
load妙用
-
aop面向切面編程
-
NSNumber Or Int
-
@()適配64位
1 讓appDelegate 減少負擔
經(jīng)過漫長時間的學習 你終于掌握了iOS大法 你找到了份iOS開發(fā)的工作 信誓旦旦的要開始你的coding生涯 老板對你非常器重 然后告訴你 我覺得你的技術(shù) 是非常刁的 那這個項目就你自己來搞吧 啊哦這就意味著這個項目你就從頭到尾處理 從軟件的架構(gòu) 到頁面的展示 都交給你嘍 ??
用著自己的半吊子水平 papapa的 coding 決心一定要把代碼封裝好 寫的漂亮 (其實是聽大神說 封裝 其實自己不太懂)
項目到了尾聲 老板告訴你我們的app 我們的app 將來得來個分享到朋友圈的功能吧 不然怎么體現(xiàn)我產(chǎn)品的牛逼
然后你聽說友盟比較好使(有廣告的嫌疑) 你去友盟看了他們的文檔 他告訴你你要在 appdelegate didFinishLaunch方法里面寫了這個東西
- ?[UMSocialData?setAppKey:@"XX"];
- ????//?????注冊微信
- ?[UMSocialWechatHandler?setWXAppId:@"XXX"??appSecret:@"XX"?url:@""];
- ????//????注冊QQ
- ?[UMSocialQQHandler?setQQWithAppId:@"XXX"?appKey:@"XXX"?url:@""];
過了幾天 老板又說 我們需要統(tǒng)計下我頁面的信息 你接入了友盟的統(tǒng)計 在appdelegate didFinishLaunch又 多了行代碼
需求是無窮無盡 我需要bug統(tǒng)計(fir hud) 提醒用戶評分系統(tǒng)(iRate) 推送(jPush 信鴿 個推。。)
當初你決心一定要把代碼封裝的完美 寫的漂亮的心早就被老板的需求徹底打敗了
別擔心 寒哥教你小技巧
不知道你們用過 IQKeyBoardManage和iRate這種智能庫沒
大牛的readme 寫了這段話
- Key?Features
- 1)?CODELESS,?Zero?Line?Of?Code?不需要寫任何代碼
- 2)?Works?Automatically?//自動工作
- 3)?No?More?UIScrollView?//不需要scrollview
- 4)?No?More?Subclasses?//不需要繼承父類
- 5)?No?More?Manual?Work?//不需要配置
- 6)?No?More?#imports?//不需要導入
其實不神奇 只是大牛用了 + load這個方法
學習OC都知道這個代碼會在一個類被加載到運行庫中就會被自動調(diào)用 這不就實現(xiàn)了 自動調(diào)用
寫一個類繼承自NSObject
- #import?@interface?ThirdPartService?:?NSObject
- @end
- ?#import?"ThirdPartService.h"
- ?#import?"UMSocial.h"
- ?#import?"UMSocialWechatHandler.h"
- ?#import?"UMSocialQQHandler.h"
- ?#import??#import?@implementation?ThirdPartService
- ?+?(void)load?{
- static?dispatch_once_t?onceToken;
- dispatch_once(&onceToken,?^{
- ????//????TODO??這里是我自己測試的??fir?hud
- ????[FIR?handleCrashWithKey:@"XX"];
- ????//????友盟
- ????[UMSocialData?setAppKey:@"XX"];
- ????//?????隱藏未安裝的平臺
- ????[UMSocialConfig?hiddenNotInstallPlatforms:@[UMShareToQQ,UMShareToQzone,UMShareToWechatSession,UMShareToWechatTimeline]];
- ????//?????注冊微信
- ????[UMSocialWechatHandler?setWXAppId:@"XX"?appSecret:@"XX"?url:@""];
- ????//????注冊QQ
- ????//????TODO???QQ的不是真的
- ????[UMSocialQQHandler?setQQWithAppId:@"XX"?appKey:@"XX"?url:@""];
- ????//????TODO????UM統(tǒng)計
- ????[MobClick?startWithAppkey:@""];
- ????[MobClick?setCrashReportEnabled:NO];
- ????NSLog(@"第三方服務(wù)注冊完畢");
- });
- }
- @end
類似于定位也可以這樣寫
模塊和服務(wù)完全拆開
但是有的服務(wù) 如APNS需要LaunchOption 那就只能寫在appdDelegate 不過這樣的話已經(jīng)摘除很多代碼了 只剩下幾個固定的 到時候再修改appDelegate就會感覺非常清晰 了
2 ViewController繼承?
接著上面講 我們接入了友盟統(tǒng)計 友盟統(tǒng)計最基本的東西就是 統(tǒng)計頁面的pv
友盟的這樣寫 對于新手的我們就覺得這不就so easy嗎
我打開了某個vc(HomeViewController)
在代碼里面寫上了這句
- -(void)viewWillAppear:(BOOL)animated?{
- ???[super?viewWillAppear:animated];
- #ifndef?DEBUG
- ???[MobClick?beginLogPageView:NSStringFromClass([self?class])];
- #endif
- }
- -(void)viewWillDisappear:(BOOL)animated?{
- ????[super?viewWillDisappear:animated];
- #ifndef?DEBUG
- ??[MobClick?endLogPageView:NSStringFromClass([self?class])];
- #endif
- }
然后我一個項目中可能有幾十個 甚至上百個頁面需要統(tǒng)計pv 我總不能每個節(jié)目都這樣寫吧
聰明的我們想到了繼承
如MyBaseViewController:UIViewController
這樣就要做一件事 把我們項目中所有繼承自UIViewController的類全部改為繼承自MyBaseViewController 然而你真的覺得這樣好嗎 我們一個項目中有幾十個控制器 我就要把每個控制器改一遍
這種重復性的工作一是無聊 而是容易出錯 你復制著復制者就會遺漏掉某個類 重要的是 我們項目中很多類并不是直接繼承自UIViewController 有的可能是UITableViewController UICollectionViewContr0ller UINavigationController 甚至不常用的UISearchDisPlayController UIPopoverController ?UIPresentController 是不是突然覺得這么多啊啊 ??
這也不是坑的 坑的是將來你混成了大牛 招了個小弟 你告訴他你所有的類都要繼承自我寫的各種父類 新手總是會不經(jīng)意見犯錯誤 有些類忘記繼承了 后期查起來難度非常大 浪費時間 所以這種設(shè)計是不合理的
-
寒哥再次教你黑魔法 Method swizzling
關(guān)于這個是干嘛的 自行百度
這里有一篇來自NSHipster博主的文章 英文
還有一篇解釋runtime的文章傳送門
用方法交叉 我們就可以攔截吸引的方法了 上代碼了
這樣就做到了面向切面編程(AOP)的思想
上代碼
- #import?@interface?UIViewController?(AOP)
- #warning??運行時?改變一下方法?做一些切面編程比如?統(tǒng)計?等等
- ?@end
- ?#import?"UIViewController+AOP.h"
- ?#import?#import??@implementation?UIViewController?(AOP)
- ?+?(void)load?{
- ???static?dispatch_once_t?onceToken;
- ??dispatch_once(&onceToken,?^{
- ????Class?class?=?[self?class];
- ????//?When?swizzling?a?class?method,?use?the?following:
- ????//?Class?class?=?object_getClass((id)self);
- ????swizzleMethod(class,?@selector(viewDidLoad),?@selector(aop_viewDidLoad));
- ????swizzleMethod(class,?@selector(viewDidAppear:),?@selector(aop_viewDidAppear:));
- ????swizzleMethod(class,?@selector(viewWillAppear:),?@selector(aop_viewWillAppear:));
- ????swizzleMethod(class,?@selector(viewWillDisappear:),?@selector(aop_viewWillDisappear:));
- });
- }
- ?void?swizzleMethod(Class?class,?SEL?originalSelector,?SEL?swizzledSelector)???{
- Method?originalMethod?=?class_getInstanceMethod(class,?originalSelector);
- Method?swizzledMethod?=?class_getInstanceMethod(class,?swizzledSelector);
- BOOL?didAddMethod?=
- class_addMethod(class,
- ????????????????originalSelector,
- ????????????????method_getImplementation(swizzledMethod),
- ????????????????method_getTypeEncoding(swizzledMethod));
- if?(didAddMethod)?{
- ????class_replaceMethod(class,
- ????????????????????????swizzledSelector,
- ????????????????????????method_getImplementation(originalMethod),
- ????????????????????????method_getTypeEncoding(originalMethod));
- }?else?{
- ????method_exchangeImplementations(originalMethod,?swizzledMethod);
- }
- ?}
- ?-?(void)aop_viewDidAppear:(BOOL)animated?{
- [self?aop_viewDidAppear:animated];
- }
- ?-(void)aop_viewWillAppear:(BOOL)animated?{
- [self?aop_viewWillAppear:animated];
- #ifndef?DEBUG
- ???[MobClick?beginLogPageView:NSStringFromClass([self?class])];
- #endif
- }
- ?-(void)aop_viewWillDisappear:(BOOL)animated?{
- ????[self?aop_viewWillDisappear:animated];
- #ifndef?DEBUG
- ????[MobClick?endLogPageView:NSStringFromClass([self?class])];
- #endif
- }
- ?-?(void)aop_viewDidLoad?{
- [self?aop_viewDidLoad];
- if?([self?isKindOfClass:[UINavigationController?class]])?{
- ????UINavigationController?*nav?=?(UINavigationController?*)self;
- ????nav.navigationBar.translucent?=?NO;
- ????nav.navigationBar.barTintColor?=?GLOBAL_NAVIGATION_BAR_TIN_COLOR;
- ????nav.navigationBar.tintColor?=?[UIColor?whiteColor];
- ????NSDictionary?*titleAtt?=?@{NSForegroundColorAttributeName:[UIColor?whiteColor]};
- ????[[UINavigationBar?appearance]?setTitleTextAttributes:titleAtt];
- ????[[UIBarButtonItem?appearance]
- ?????setBackButtonTitlePositionAdjustment:UIOffsetMake(0,?-60)
- ?????forBarMetrics:UIBarMetricsDefault];
- }
- //????self.view.backgroundColor?=?[UIColor?whiteColor];
- self.navigationController.interactivePopGestureRecognizer.delegate?=?(id)self;
- ?}
- ?@end
圖片代碼一份 方便觀看
我們充分利用了黑魔法達到了面向切面編程的好處
思想來源這里http://casatwy.com/iosying-yong-jia-gou-tan-viewceng-de-zu-zhi-he-diao-yong-fang-an.html
黑魔法非毒藥 遵守一個規(guī)范寫出來的代碼是不會Crash的 只要能幫我們解決問題就是好東西
黑魔法性能 有瓶頸? 都到runtime的底層了 你還擔心有瓶頸 ?少年安心使用就好了 ? 不服 可以用Time Profiel測試
黑魔法也非萬能 ?像 我們在導航控制器要封裝手勢 統(tǒng)一管理左側(cè)返回按鈕 ?這些東西 還是繼承來得好
技術(shù)就是工具 黑貓,白貓,抓住老鼠就是好貓
#p#
3 網(wǎng)絡(luò)訪問參數(shù)到底用基本數(shù)據(jù)類型還是對象
下面看兩個方法
- ?+?(void)getDataAtPageNo:(NSNumber?*)pageNo?PageSize:(NSNumber?*)pageSize?
- complete:(CompleteBlock)complete?{
- NSMutableDictionary?*param?=?[NSMutableDictionary?dictionary];
- ????if?(pageSize)?{
- ????????[param?setObject:pageSize?forKey:@"pageSize"];
- ???}
- ?[param?setObject:pageNo?forKey:@"pageNo"];
- //?SendRequest
- }
- ?+?(void)getData2AtPageNo:(long?)pageNo?PageSize:(long?)pageSize?
- ?complete:(CompleteBlock)complete?{
- ?????NSMutableDictionary?*param?=?[NSMutableDictionary?dictionary];
- ????????[param?setObject:@(pageSize)?forKey:@"pageSize"];
- ????????[param?setObject:@(pageNo)?forKey:@"pageNo"];
- //?SendRequest
- ?}
在訪問網(wǎng)絡(luò)請求時 對于有參數(shù)的請求 設(shè)計一個方法 主流為以上兩種
-
使用對象當做參數(shù)
-
使用基本數(shù)據(jù)類型做參數(shù)
一般情況下 這并沒有什么大的區(qū)別 但是寒哥給出的意見是Never出現(xiàn)基本數(shù)據(jù)類型
一般情況下 開發(fā)者可能覺得并沒有什么區(qū)別 下面我給大家舉個例子
在設(shè)計一個分頁展示數(shù)據(jù)的時候: 在頁面上的邏輯就是 默認加載第一頁 每頁長度為10 (Server端的同學一般都很友好 默認情況下 不傳每頁的長度就是10個) 但是傳了就會覆蓋掉后臺寫的默認參數(shù) 如傳了20 Server就吐20條數(shù)據(jù)
-
在第一中設(shè)計方案中: 可能在某個控制器中保留一個PageNo PageSize 的對象的成員變量 ,在下拉刷新或者上拉加載的時候 會傳遞對應的參數(shù)給請求方法 , 如果沒有特殊需求的話pageSize 對象可有可無 也就是有可能為nil ,那在對應的param可能就沒有這個參數(shù)傳遞給Server 。
Server 就會交還給我們某頁的20條數(shù)據(jù)
-
在 第二種設(shè)計方案中 : 可能也在某個控制器中保留一個PageNo pageSize的基本數(shù)據(jù)類型的成員變量, 在訪問網(wǎng)絡(luò)請求時交給對應的方法 , 一般沒有特殊需求我們也不會對PageSize專門設(shè)值 但是 基本數(shù)據(jù)類型在OC 和C語言這種傳統(tǒng)的編程語言中是有默認值的 為0,雖然我們沒有給pageSize 賦值 但是默認系統(tǒng)默認給了0這個初始值 那么傳遞到Server的時候 就會覆蓋掉Server 寫的默認pageSize=10 這樣的請求既不會報錯 也不會返回數(shù)據(jù)
超級難調(diào)試
所以在網(wǎng)絡(luò)訪問中 寒哥給出的意見就是Never出現(xiàn)基本數(shù)據(jù)類型
4 用NSNumber比基本數(shù)據(jù)類型的好處 ? 64位適配問題
我們一般都用來當做網(wǎng)絡(luò)請求的參數(shù) 緩存或者展示到頁面
- 對于網(wǎng)絡(luò)請求的參數(shù) 因為NSDictionary只能放對象 所以NSNumber最好的方式
- 緩存 無論緩存到plist 還是KeyArchive 都是需要對象的所以NSNumber也非常合適
- 展示到頁面
我見過這樣給頁面上賦值的朋友
我們看到這樣貌似并沒有什么不妥
但是我們把設(shè)備切換到iPhone5S以下 也就是32位的設(shè)備
注意這里有Warning
為什么呢 我們來看下NSInteger的頭文件
在32下設(shè)備是Int 在64位是long
我們都知道蘋果不允許不支持64位的app上架 但是貌似我們從來沒有為32位和64位做適配
其實不然 在printf 和NSLog 時 對應%d %zd %f 占位符是非常嚴格的 如果不對項目就會造成意外的結(jié)果
其實拿到一個NSNumber我們并不知道他到底是int long unsigned int Bool 直接針對某個類型轉(zhuǎn)換是有風險的 但是其實Clang 給我們提供了個非常好用的Macro @()
NSNumber并不是一個簡單的類 它是cocoa 中 類簇的實現(xiàn)參考資料
http://www.cocoachina.com/ios/20140109/7681.html