盤點一些iOS開發技巧:Swift和Objective-C

jopen 9年前發布 | 23K 次閱讀 Objective-C

摘要:對蘋果開發者來講,2014年是令人難以置信的一年。在這短短的一年中發生了如此多的變化:在充滿吸引力的Swift面前,我們幾乎忘了之前是如何癡迷OC;以及充滿想象力的iOS8和WatchKit,難以想象還有什么API能與之相比。

回顧過去一年發生在我們身邊的事情時,有一點不得不提:對蘋果開發者來講,2014年是令人難以置信的一年。在這短短的一年中(有關APP的開發)發生了如此多的變化:在充滿吸引力的Swift面前,我們幾乎忘了之前是如何癡迷于Objective-C;以及充滿想象力的iOS 8和WatchKit,難以想象還有什么API能與之相比。

盤點一些iOS開發技巧:Swift和Objective-C

NSHipster的慣例:請可愛的童鞋們,在新年的第一天,為大家展示你們(在開發中)常使用的技巧和方法。如今,隨著來自Cupertino(蘋果總部,位于舊金山)和眾多開源社區的一系列API的涌現,媽媽再也不用擔心我們找不到有趣的東西來分享啦!

在此,感謝以下童鞋們所做的貢獻:

Colin Rofls、Cédric Luthi、Florent Pillet、Heath Borders、Joe Zobkiw、Jon Friskics、Justin Miller、Marcin Matczuk、Mikael Konradsson、Nolan O'Brien、Robert Widmann、Sachin Palewar、Samuel Defago、Sebastian Wittenkamp、Vadim Shpakovski、Zak。

成員函數的使用技巧(來自Robert Widmann

在用靜態方式調用Swift類和結構中的成員函數時,通常使用以下格式:

Object->(參數)->Things

比如,你可以用以下兩種方式調用reverse():


[1,2,3,4].reverse( )
Array.reverse([1,2,3,4])




用@()來封裝C字符串(來自Samuel Defago

事實上文字大部分時候是數字和字母的集合,使用C字符串,尤其當我在使用運行時編碼的時候,我常常會忘記用UTF8編碼、以NULL結束:Objective-C字符串封裝:


NSString *propertyAttributesString =
    @(property_getAttributes(class_getProperty([NSObject class], "description")));
// T@"NSString",R,C


AmIBeingDebugged


Nolan O'Brien這篇Q&A技術文檔中讓我們注意到了AmIBeingDebugged函數方法:


#include <assert.h>

include <stdbool.h>

include <sys/types.h>

include <unistd.h>

include <sys/sysctl.h>

static Bool AmIBeingDebugged(void) { int mib[4]; struct kinfo_proc info; size_t size = sizeof(info); info.kp_proc.p_flag = 0; mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); return (info.kp_proc.p_flag & P_TRACED) != 0; }</pre>


使用延遲存儲屬性(來自Colin Rofls

在開發過程中,應該避免使用Optionals類型,更不應該使用隱式解包optionals類型。你想聲明一個var變量卻不想給一個初始值?使用“lazy”吧,唯一要注意的就是:在你的屬性被賦值之前不要調用getter方法即可(童叟無欺!)


lazy var someModelStructure = ExpensiveClass()


假如你僅僅對這var變量調用set方法,而沒有調用getter方法的話,這個被lazy修飾的var變量不會被賦值。例如,用lazy修飾那些直到viewDidLoad時才需要初始化的views變量就會非常合適。


獲取Storyboard視圖容器里的子視圖控制器(來自Vadim Shpakovski

有一個比較方便的方法來獲取故事板視圖容器里的子視圖控制器:


// 1. A property has the same name as a segue identifier in XIB
@property (nonatomic) ChildViewController1 childController1;
@property (nonatomic) ChildViewController2 childController2;
// #pragma mark - UIViewController

  • (void)prepareForSegue:(UIStoryboardSegue *)segue

               sender:(id)sender
    

    { [super prepareForSegue:segue sender:sender];

    // 2. All known destination controllers assigned to properties if ([self respondsToSelector:NSSelectorFromString(segue.identifier)]) {

      [self setValue:segue.destinationViewController forKey:segue.identifier];
    

    } }

  • (void)viewDidLoad { [super viewDidLoad]; // 3. Controllers already available bc viewDidLoad is called after prepareForSegue self.childController1.view.backgroundColor = [UIColor redColor]; self.childController2.view.backgroundColor = [UIColor blueColor]; }</pre>

      <br />
    
    </p>

      重復運行項目,不重復構建項目(來自<a href="/misc/goto?guid=4958861246205178144" target="_blank">Heath Borders</a>)
    
    </p>

      假如你一直在不停地調試同一個問題,你可以在不重復構建的情況下運行你的APP,這樣:“Product>Perform Action>Run without Building” 
    
    </p>

      快速獲取Playground資源(來自<a href="/misc/goto?guid=4958861246296530969" target="_blank">Jon Friskics</a>)
    
    </p>

      Swift里的所有Playground共享相同的數據目錄:/Users/HOME/Documents/Shared Playground Data
    
    </p>

      如果你喜歡使用很多Playgrounds,你將需要在上述共享目錄下為每個Playground新建對應的子目錄,來存儲每個Playground用到的數據;但是那之后你需要告訴每個Playground在哪兒可以獲取其對應的數據。下面是我常用的一個輔助解決方法:
    
    </p>

      <br />
    
    </p>
    func pathToFileInSharedSubfolder(file: String) -> String {
      return XCPSharedDataDirectoryPath + "/" + NSProcessInfo.processInfo().processName + "/" + file
    }

      <br />
    
    </p>

      <br />
    
    </p>

      processName屬性是Playground文件的名字,因此只要你已經在Playground數據共享文件目錄下以相同的名字新建了一個子目錄,那么你可以很容易訪問這些數據,和讀取本地JSON數據一樣:
    
    </p>

      <br />
    
    </p>
      <p>
          <br />
      </p>
    
    var jsonReadError:NSError?
    let jsonData = NSFileManager.defaultManager().contentsAtPath(pathToFileInSharedSubfolder("data.json"))!
    let jsonArray = NSJSONSerialization.JSONObjectWithData(jsonData, options: nil, error: &jsonReadError) as [AnyObject]

          ....或者訪問本地圖片
      </p>
      <p>
          <br />
      </p>
      <p>
          <br />
      </p>
    
    let imageView = UIImageView()
    imageView.image = UIImage(contentsOfFile: pathToFileInSharedSubfolder("image.png"))

          <br />
      </p>
      <p>
          <br />
      </p>
      <p>
          Please attention!本篇文章剩余的部分來自<a href="/misc/goto?guid=4958861246386625152" target="_blank">Cédric Luthi</a>大神的貢獻,他分享了一些比較有用的開發技巧和技術,這些內容足夠自成一篇,值得細細品讀。這里再次感謝Cédric!
      </p>
      <p>
          CocoaPods大揭秘
      </p>
      <p>
          這兒有一個快速的方法來檢查APP里用到的所有pods:
      </p>
      <p>
          <br />
      </p>
    
    $ class-dump -C Pods_ /Applications/Squire.app | grep -o "Pods_\w+"

          <br />
      </p>
      <p>
          CREATE_INFOPLIST_SECTION_IN_BINARY
      </p>
      <p>
          注意Xcode中為命令模式APP(command-line apps)設置的CREATE_INFOLIST_SECTION_IN_BINARY屬性。這比使用-sectcreate__TEXT__info_plist鏈接標志位更加容易,前者還把已經編譯好的Info.plist文件嵌入在二進制編碼中。
      </p>
      <p>
          關于如何向蘋果提需求,它也給我們上了一課,這個特性需求早在2006年的 rdar://4722772 被提出,但直到7年后才被滿足。
      </p>
      <p>
          (譯者注:言外之意是它是反面教材,應該更有技巧的提需求)
      </p>
      <p>
          禁用dylib鉤子(來自<a href="/misc/goto?guid=4958861246473890516" target="_blank">Sam Marshall</a>)
      </p>
      <p>
          Sam Marshall這個技巧可謂是走自己的路,讓黑客無路可走。
      </p>
      <p>
          在你的“Other Linker Flags”里加上下面這行:
      </p>
      <p>
          <br />
      </p>
    
    -Wl,-sectcreate,__RESTRICT,__restrict,/dev/null

          <br />
      </p>
      <p>
          NSBundle -preferredLocalizations
      </p>
      <p>
          某些時候,你需要知道APP當前使用的是什么語言。通常,大家會使用NSLocal+preferredLanguages. 可惜的是這個方法不會告訴你APP實際呈現的文字語種。你僅僅會得到iOS系統里“Settings->General->Language&Region->Preferred Language”列表中的選項,或者OSX系統里“System Preferences->Language & Region->Preferred Languages”列表中的選項。想象一下:優先語言列表中只有{英語,法語},但你的APP僅使用德語;調用[[NSLocal preferredLanguages] firstObject]返回給你的是英語,而不是德語。
      </p>
      <p>
          正確的方法是用[[NSBundle mainBundle] preferredLocalizations]方法。
      </p>
      <p>
          蘋果的開發文檔是這樣說的:
      </p>
      <p>
          一個包含了在bundle中本地化的語言ID的NSString對象的數組,里面的字符串排序是根據用戶的語言偏好設置和可使用的地理位置而來的。
      </p>
      <p>
          NSBundle.h里的備注:
      </p>
      <p>
          一個bundle中本地化的子集,重新排序到當前執行壞境的優先序列里,main bundle的語言順序中最前面的是用戶希望在UI界面上看到的語種。
      </p>
      <p>
          當然你也許需要調用這個方法:
      </p>
      <p>
          <br />
      </p>
    
    NSLocal+canonicalLanguageIdentifierFromString:

          <br />
      </p>
      <p>
          來確保你使用的文字語種是規范的語種。
      </p>
      <p>
          保護SDK頭文件
      </p>
      <p>
          如果你用dmg安裝Xcode,那么看看這篇<a href="/misc/goto?guid=4958861246567293609" target="_blank">Joar Wingfors的文章</a>,它講述了如何通過保留所有權來避免SDK頭文件被意外修改:
      </p>
      <p>
          <br />
      </p>
    
    $ sudo ditto /Volumes/Xcode/Xcode.app /Applications/Xcode.app

          <br />
      </p>
      <p>
          任意類型的實例變量檢測
      </p>
      <p>
          為了達到逆向處理的目的,查詢對象的實例變量是一個常見可靠的途徑。通常調用對象valueForKey:方法就能達到這一目的,除了少數重寫了類方法+accessInstanceVariablesDirectly的類屏蔽了該操作。
      </p>
      <p>
          下面是一個例子:當實例變量有一個為任意類型的屬性時,上述提到的操作無效
      </p>
      <p>
          這是iOS6.1 SDK中MediaPlayer 框架的一段引用:
      </p>
      <p>
          <br />
      </p>
    
    @interface MPMoviePlayerController : NSObject {
      void *_internal;    // 4 = 0x4
      BOOL _readyForDisplay;  // 8 = 0x8
    }

          <br />
      </p>
    
    因為 id internal=[moviePlayerController valueForKey:@”internal”] 無效,下面有一個笨辦法來取得這個變量:
      <p>
          <br />
      </p>
      <p>
          <br />
      </p>
    
    id internal = *((const id*)(void*)((uintptr_t)moviePlayerController + sizeof(Class)));

          <br />
      </p>
      <p>
          <br />
      </p>
      <p>
          注意!不要隨意調用這段代碼,因為ivar的布局可能改變(指針偏移量計算可能出錯)。僅在逆向工程中使用!
      </p>
      <p>
          NSDateFormatter +dateFormatFromTemplate:options:locale:
      </p>
      <p>
          友情提示:假如你調用[NSDateFormatter setDateFormat],而沒有調用[NSDateFormatter dateFormatFromTemplate:options:local:],n那么很可能出錯。
      </p>
      <p>
          蘋果文檔:
      </p>
      <p>
          <br />
      </p>
    
    + (NSString )dateFormatFromTemplate:(NSString )template
    
                           options:(NSUInteger)opts
                            locale:(NSLocale *)locale</pre>       <p>
          <br />
      </p>
      <p>
          不同地區有不同的日期格式。使用這個方法的目的:得到指定地區指定日期字段的一個合適的格式(通常你可以通過currentLocal查看當前所屬地區)
      </p>
      <p>
          下面這個例子給我們表現了英式英語和美式英語不同的日期格式:
      </p>
      <p>
          <br />
      </p>
    
    NSLocale usLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
    NSLocale gbLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"];

NSString dateFormat; NSString dateComponents = @"yMMMMd";

dateFormat = [NSDateFormatter dateFormatFromTemplate:dateComponents options:0 locale:usLocale]; NSLog(@"Date format for %@: %@", [usLocale displayNameForKey:NSLocaleIdentifier value:[usLocale localeIdentifier]], dateFormat);

dateFormat = [NSDateFormatter dateFormatFromTemplate:dateComponents options:0 locale:gbLocale]; NSLog(@"Date format for %@: %@", [gbLocale displayNameForKey:NSLocaleIdentifier value:[gbLocale localeIdentifier]], dateFormat);

// Output: // Date format for English (United States): MMMM d, y // Date format for English (United Kingdom): d MMMM y</pre>


通過調試獲取內部常量

近期,Matthias Tretter在推ter上問到:

有人知道在iOS8里modal viewController presentation的默認動畫時間和跳轉方式嗎?

我們在UIKit的類庫中發現了這樣一個函數:[UITransitionView defaultDurationForTransition:],并在這個方法的位置加一個斷點:

(lldb) br set -n "+[UITransitionView defaultDurationForTransition:]"
模態顯示一個viewController,就會停在這個斷點,輸入finish執行該方法:



(lldb)finish
在defaultDurationForTransition:被執行時,你就能讀到結果(在xmm0寄存器里)



(lldb) register read xmm0 --format float64
    xmm0 = {0.4 0}
回復:默認動畫時間0.4s



DIY 弱關聯對象

不幸的是,關聯對象OBJC_ASSOCIATION_ASSIGN策略不支持引用計數為0的弱引用。幸運的是,你可以很容易實現它,你僅僅需要一個簡單的類,并在這個類里弱引用一個對象:

@interface WeakObjectContainter : NSObject
@property (nonatomic, readonly, weak) id object;
@end
@implementation WeakObjectContainter

  • (instancetype)initWithObject:(id)object { self = [super init]; if (!self) {
      return nil;
    
    } self.object = object; return self; } @end</pre> 然后,通過OBJC_ASSOCIATION_RETAIN(_NONATOMIC)關聯WeakObjectContainter:
      <p>
          <br />
      </p>
      <p>
          <br />
      </p>
    
    objc_setAssociatedObject(self, &MyKey, [[WeakObjectContainter alloc] initWithObject:object], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    用object屬性指向這個所需的引用計數為0的弱引用對象。
      <p>
          <br />
      </p>
      <p>
          <br />
      </p>
    
    id object = [objc_getAssociatedObject(self, &MyKey) object];

          轉自:http://www.csdn.net/article/2015-01-14/2823561-new-years-2015
      </p>
    
    </div> </div>
 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!